WowClassic 1.15.7.60277 Offsets menu

User Tag List

Results 1 to 5 of 5
  1. #1
    dreadcraft's Avatar Member
    Reputation
    12
    Join Date
    Jun 2018
    Posts
    33
    Thanks G/R
    30/11
    Trade Feedback
    0 (0%)
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)

    WowClassic 1.15.7.60277 Offsets

    Code:
    # 1.15.7.60277
    object_manager = 0x385F0E8
    player_guid = 0x3874C10
    mouseover_guid = 0x398EA38
    chat_frame_open = 0x0 # unknown since 1.15.4.x
    zone_text = 0x398DCA0
    subzone_text = zone_text + 0x8
    minimap_zone_text = subzone_text + 0x8
    player_target_guid = 0x36571A8
    last_target_guid = player_target_guid + 0x10
    camera_manager = 0x386CF50
    nothing special just posting these here for @scimmy in case it's helpful to them and possibly anyone else.
    IDA should show you a nice XREF for the function/subroutine at 0x1405C4500 which is tied into a bunch of useful XMM_WORDs (various target_GUIDs) including player_target_guid

    WowClassic 1.15.7.60277 Offsets
  2. #2
    chaosrage's Avatar Site Donator
    Reputation
    24
    Join Date
    Dec 2007
    Posts
    77
    Thanks G/R
    8/2
    Trade Feedback
    1 (100%)
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Hey dread, thanks for posting these.

    I'm a beginner just now getting into memory reading, and I was trying to find offsets in my own classic wow client as a learning experience.
    So far I haven't been able to do it. I was using chat gpt as a helper when I didn't understand something.

    Do you have any resources you can share with me to get me started on how to find my own offsets so that I can learn how?
    I unpacked my WoW client using a mem dump program I found in another thread, and was looking around in IDA for the zone_text offset.

    My small goal was to find the offset for zone_text and print the zone I was in by reading it. But I couldn't seem to get the correct address.

  3. #3
    Makkah's Avatar Active Member Authenticator enabled
    Reputation
    45
    Join Date
    Jun 2024
    Posts
    67
    Thanks G/R
    10/29
    Trade Feedback
    0 (0%)
    Mentioned
    1 Post(s)
    Tagged
    0 Thread(s)
    Hello,

    I've identified the function that contains the ZoneText used by lua_GetZoneText.

    To make things easier for future updates, I've also created a pattern you can use to locate it yourself.

    Code:
    Pattern -> E8 ?? ?? ?? ?? 4C 8D 05 ?? ?? ?? ?? 48 89 45 18 33 D2 48 8B CB E8 ?? ?? ?? ?? 4C 8B 45 08 84 C0 0F 84 86 05 00 00 66 0F 1F 44 00 00 90 EB 5B
    Screenshot-2025-05-16-055331.png

  4. Thanks dreadcraft, chaosrage (2 members gave Thanks to Makkah for this useful post)
  5. #4
    dreadcraft's Avatar Member
    Reputation
    12
    Join Date
    Jun 2018
    Posts
    33
    Thanks G/R
    30/11
    Trade Feedback
    0 (0%)
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Originally Posted by chaosrage View Post
    Hey dread, thanks for posting these.

    I'm a beginner just now getting into memory reading, and I was trying to find offsets in my own classic wow client as a learning experience.
    So far I haven't been able to do it. I was using chat gpt as a helper when I didn't understand something.

    Do you have any resources you can share with me to get me started on how to find my own offsets so that I can learn how?
    I unpacked my WoW client using a mem dump program I found in another thread, and was looking around in IDA for the zone_text offset.

    My small goal was to find the offset for zone_text and print the zone I was in by reading it. But I couldn't seem to get the correct address.
    Hey mate, I think that Makkah gave you an amazing gift there.
    I'm assuming that you're using a static analysis tool like IDAPro or Ghidra on the unpacked binary that will allow you to benefit from his byte signature.

    Regarding ZoneText specifically, there are always multiple ways to go about doing something. One path is to use known API functions (Makkah mentioned Script_GetZoneText) that in theory should invoke the member you want to read.

    Another function that invokes the pointer to the ZoneText string is Script_GetChannelName. That's what I'm using currently.
    A lot of these "lua/Script" functions have usage strings for API/AddOn developers. If you use IDA's Strings subview, you can look at all of them by typing "Usage."

    Script_GetChannelName was pointed out to me by QOP. GetChannelName in Strings subview will show you XREFS to relevant functions.
    There is another interesting debug function that I don't know the name of, but it contains string-formatted outputs of various pieces of data through-out. If you string search for "Zone:\t\t%s" you will see what I mean, and I believe that closely following it will be the pointer for ZoneText, the ASM looking something like "mov r9, cs:qword_14390B730"

    Once you have the pointer (here it would be 0x390B730), you should have no trouble reading the string at the address it points to. It may return unexpected values if your player character is in an instance or on a flight path.
    Last edited by dreadcraft; 05-17-2025 at 02:15 AM.

  6. Thanks Makkah, chaosrage (2 members gave Thanks to dreadcraft for this useful post)
  7. #5
    chaosrage's Avatar Site Donator
    Reputation
    24
    Join Date
    Dec 2007
    Posts
    77
    Thanks G/R
    8/2
    Trade Feedback
    1 (100%)
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Originally Posted by Makkah View Post
    Code:
    Pattern -> E8 ?? ?? ?? ?? 4C 8D 05 ?? ?? ?? ?? 48 89 45 18 33  D2 48 8B CB E8 ?? ?? ?? ?? 4C 8B 45 08 84 C0 0F 84 86 05 00 00 66 0F 1F  44 00 00 90 EB 5B
    Screenshot-2025-05-16-055331.png
    thank you Makkah, this will be very helpful, i just have a couple questions if you have time. i understand that this pattern will work simply by searching for it in IDA, but when i go to the zone text address in IDA, the hex view for this address shows as
    000000014398DCD0 ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ????????????????
    so the pattern doesn't work for me because IDA cant see it. i understand WoW is obfuscated, but i dumped the client using a dump tool i found here, memdump i think. was i supposed to be running the game when i did it, or did i use the wrong tool? all i did was drag my wow client into memdump and opened it in IDA. sorry if this seems like an obvious question.



    Originally Posted by dreadcraft View Post
    .
    hi dread, thank you for the reply. after i made my post i did a lot of research here and in blogs and such about memory reading. i actually was able to manually find some offsets for the current build, 60663. in the older build i was using your offsets as my goal, i think the way i found the zone text offset was through a string that was called GetZoneText. i fiddled around in the Xrefs until i finally found your offset address.

    once i figure out why my client shows all ???s for my data in hex view ill switch over to using a pattern or a python script for most offsets probably, after i find each offset myself manually for learning purposes. ill have to try your method of looking at 'Usage'. i do have a lot of questions about the object manager as well as how to find the location of a nearby unit or item. but thats for another thread probably.


    id like to return the favor to you by sharing the small amount of offsets i have found for manually in the last few days for 60663 while learning:

    Code:
     60663
    ZONE_TEXT_OFFSET = 0x39C7C78;
    SUBZONE_TEXT_OFFSET = ZONE_TEXT_OFFSET + 0x8;
    MINIMAP_TEXT_OFFSET = ZONE_TEXT_OFFSET + 0x10;
    PLAYER_GUID_OFFSET = 0x38AEBF0;
    OBJECT_MANAGER_OFFSET = 0x38990E8;
    and here is a python script i had AI make me to automatically find player_guid offset:

    Code:
     
    
    # -*- coding: utf-8 -*-
    
    import idaapi
    import ida_ua
    import idautils
    import idc
    
    # ---------------------------------------------------------------------------
    # CONFIG
    # ---------------------------------------------------------------------------
    TARGET_OFFSETS = {
        0x0, 0x8, 0x10, 0x20, 0x30, 0x40,
        0x44, 0x48, 0x4A, 0x4C, 0x4E, 0x50,
        0x58, 0x60, 0x4F0, 0x4F8, 0x500
    }
    
    MATCH_THRESHOLD = 0.80    # e.g. 80%
    LOOKBACK_BYTES  = 0x80    # scan 128 bytes before each call
    
    # In Windows x64 calling convention, RCX is typically the 1st function arg
    def find_rcx_index():
        for idx in range(256):
            reg_name = idaapi.get_reg_name(idx, 8)
            if reg_name and reg_name.lower() == "rcx":
                return idx
        return None
    
    RCX_IDX = find_rcx_index()
    
    
    # ---------------------------------------------------------------------------
    # FUNCTION BOUNDARIES
    # ---------------------------------------------------------------------------
    def get_func_start(ea):
        f = idaapi.get_func(ea)
        return f.start_ea if f else idc.BADADDR
    
    def get_func_end(ea):
        f = idaapi.get_func(ea)
        return f.end_ea if f else idc.BADADDR
    
    
    # ---------------------------------------------------------------------------
    # 1) Identify the "init function" by offset pattern
    # ---------------------------------------------------------------------------
    def collect_base_regs(func_ea):
        """
        Check the first ~20 instructions of func, looking for 'mov <reg>, rcx'.
        We'll treat <reg> as a potential base reg as well. Always include RCX if found.
        """
        possible_bases = set()
        if RCX_IDX is not None:
            possible_bases.add(RCX_IDX)
    
        count = 0
        for insn_ea in idautils.FuncItems(func_ea):
            if count > 20:
                break
            count += 1
    
            mnem = idc.print_insn_mnem(insn_ea).lower()
            if mnem != "mov":
                continue
    
            insn = ida_ua.insn_t()
            if ida_ua.decode_insn(insn, insn_ea) == 0:
                continue
    
            dst, src = insn.ops[0], insn.ops[1]
            # e.g. mov rdi, rcx
            if dst.type == ida_ua.o_reg and src.type == ida_ua.o_reg:
                if src.reg == RCX_IDX:
                    possible_bases.add(dst.reg)
    
        return possible_bases
    
    def gather_write_offsets(func_ea):
        """
        For each instruction in func, if it is a memory write of form [reg+disp],
        and reg is in the "base regs," collect disp. Return set of offsets found.
        """
        offsets = set()
        base_regs = collect_base_regs(func_ea)
        if not base_regs:
            return offsets
    
        for insn_ea in idautils.FuncItems(func_ea):
            mnem = idc.print_insn_mnem(insn_ea).lower()
            # Typical memory-write instructions
            if not (mnem.startswith("mov") or mnem in ("movaps","movups","vmovaps","vmovups","movdqa")):
                continue
    
            insn = ida_ua.insn_t()
            if ida_ua.decode_insn(insn, insn_ea) == 0:
                continue
    
            dst = insn.ops[0]
            if dst.type == ida_ua.o_displ and dst.reg in base_regs:
                offsets.add(dst.addr)
    
        return offsets
    
    def function_matches_init_pattern(func_ea):
        """
        If a function writes to >= MATCH_THRESHOLD * len(TARGET_OFFSETS),
        consider it a match (the "init function").
        """
        found_offsets = gather_write_offsets(func_ea)
        if not found_offsets:
            return False
        # How many of our known offsets does this function write?
        match_count = len(TARGET_OFFSETS & found_offsets)
        ratio = float(match_count) / float(len(TARGET_OFFSETS))
        return ratio >= MATCH_THRESHOLD
    
    def find_init_functions():
        """
        Scan all functions in the database. Return any that match the offset pattern.
        """
        matches = []
        for fva in idautils.Functions():
            if function_matches_init_pattern(fva):
                matches.append(fva)
        return matches
    
    
    # ---------------------------------------------------------------------------
    # 2) Caller Analysis: find pointers loaded into RCX
    # ---------------------------------------------------------------------------
    def reg_name(reg_idx):
        """Utility to get a register name from index."""
        return idaapi.get_reg_name(reg_idx, 8) or ""
    
    def read_qword(addr):
        """Try to read a 64-bit value at 'addr'. Return None if invalid/out of range."""
        import ida_bytes
        try:
            return ida_bytes.get_qword(addr)
        except:
            return None
    
    def parse_rcx_load(ea):
        """
        If the instruction at 'ea' sets RCX, parse the pointer from:
          - mov rcx, <imm>
          - mov rcx, ds:AbsoluteAddr
          - mov rcx, [rip + disp]  => read pointer from that memory location
          - lea rcx, [rip + disp]  => address = next_ea + disp
        Return the pointer or None if unrecognized.
        """
        mnem = idc.print_insn_mnem(ea).lower()
        if mnem not in ("mov","lea"):
            return None
    
        insn = ida_ua.insn_t()
        if ida_ua.decode_insn(insn, ea) == 0:
            return None
    
        dst, src = insn.ops[0], insn.ops[1]
        if dst.type != ida_ua.o_reg or dst.reg != RCX_IDX:
            return None
    
        # 1) mov rcx, <imm>
        if src.type == ida_ua.o_imm:
            val = src.value
            return val if val > 0x10000 else None
    
        # 2) mov rcx, ds:AbsoluteAddr
        if src.type == ida_ua.o_mem:
            return src.addr  # e.g. 0x143874C10
    
        # 3) mov/lea rcx, [rip + disp]
        if src.type == ida_ua.o_displ and reg_name(src.reg).lower() == "rip":
            next_ea = ea + insn.size
            target = next_ea + src.addr
            if mnem == "lea":
                # lea rcx, [rip + disp] => rcx = (next_ea + disp)
                return target if target > 0x10000 else None
            else:
                # mov rcx, [rip + disp] => we read a pointer from 'target'
                ptr_val = read_qword(target)
                if ptr_val and ptr_val > 0x10000:
                    return ptr_val
                return None
    
        return None
    
    def find_pointers_in_callers(func_ea, lookback=LOOKBACK_BYTES):
        """
        For each code xref to 'func_ea', look up to 'lookback' bytes before the call
        for an instruction that sets RCX. Return all discovered pointers.
        """
        pointers = []
        if RCX_IDX is None:
            return pointers
    
        for xref in idautils.CodeRefsTo(func_ea, 0):
            call_ea = xref
            fn_start = get_func_start(call_ea)
            scan_start = max(call_ea - lookback, fn_start)
    
            ea = scan_start
            while ea < call_ea:
                ptr_val = parse_rcx_load(ea)
                if ptr_val and ptr_val > 0x10000:
                    pointers.append(ptr_val)
                ea = idc.next_head(ea)
    
        return list(set(pointers))
    
    
    # ---------------------------------------------------------------------------
    # MAIN
    # ---------------------------------------------------------------------------
    def main():
        if RCX_IDX is None:
            print("[!] Could not locate RCX register index. Aborting.")
            return
    
        print("[*] Searching for init function(s) that write known structure offsets...")
        init_funcs = find_init_functions()
        if not init_funcs:
            print("[-] No matching functions found. Maybe adjust the threshold or offsets.")
            return
    
        print("[+] Found %d candidate init function(s):" % len(init_funcs))
        for f in init_funcs:
            print("    0x%X" % f)
    
        all_pointers = []
        for f in init_funcs:
            # For each init function, gather all pointers passed in RCX by its callers
            ptrs = find_pointers_in_callers(f, lookback=LOOKBACK_BYTES)
            all_pointers.extend(ptrs)
    
        # Deduplicate
        all_pointers = list(set(all_pointers))
        if not all_pointers:
            print("[-] No pointers found among the callers. The code may load RCX differently.")
            return
    
        print("[*] Potential addresses discovered (possibly the Player GUID or base):")
        for p in all_pointers:
            print("    0x%X" % p)
    
        print("[*] Done.")
    
    if __name__ == "__main__":
        main()
    Last edited by chaosrage; 05-18-2025 at 12:25 AM.

  8. Thanks dreadcraft (1 members gave Thanks to chaosrage for this useful post)

Similar Threads

  1. WowClassic 1.15.6.59415 Offsets
    By mazer in forum WoW Memory Editing
    Replies: 2
    Last Post: 03-01-2025, 07:24 AM
  2. WowClassic 1.15.6.58912 Offsets
    By mazer in forum WoW Memory Editing
    Replies: 2
    Last Post: 02-26-2025, 11:58 AM
  3. WowClassic 1.15.6.58797 Offsets
    By pickleback in forum WoW Memory Editing
    Replies: 15
    Last Post: 01-29-2025, 07:29 AM
  4. WowClassic 1.15.5.58555 Offsets
    By mazer in forum WoW Memory Editing
    Replies: 2
    Last Post: 01-20-2025, 08:00 AM
  5. WowClassic 1.15.5.57979 Offsets
    By dreadcraft in forum WoW Memory Editing
    Replies: 1
    Last Post: 01-06-2025, 03:27 PM
All times are GMT -5. The time now is 07:31 AM. Powered by vBulletin® Version 4.2.3
Copyright © 2025 vBulletin Solutions, Inc. All rights reserved. User Alert System provided by Advanced User Tagging (Pro) - vBulletin Mods & Addons Copyright © 2025 DragonByte Technologies Ltd.
Google Authenticator verification provided by Two-Factor Authentication (Free) - vBulletin Mods & Addons Copyright © 2025 DragonByte Technologies Ltd.
Digital Point modules: Sphinx-based search