Posts Old School RuneScape Steam version - Knowledge dump on definition stuff
Post
Cancel

Old School RuneScape Steam version - Knowledge dump on definition stuff

Welcome to the fourth part of my OSRS reversing series. I’ll be trying to dump a lot of “essential” stuff at a quick pace (for the amount of stuff there is to do) this blog.

  • Finding Item, object and NPC definitions in memory.
  • Fixing version stuff for game modes and quests.
  • Some NPC and player struct stuff.

Tools used: ReClass.NET, x64dbg and Ghidra

Other sources: rs-cache Rust crate type definitions to find what data object definitions should contain.

Contents of this blogpost are based on the 26.2.2021 build of osclient.exe

Previous parts:

Part 1: Old School RuneScape Steam version - Players and NPCs

Part 2: Old School RuneScape Steam version - Overhead text, world to screen, and ground items

Part 3: Old School RuneScape Steam version - Finding all things 3D

Items & Interactables

Items or interactable objects don’t point to the definitions, but they have ID’s. Where do we get started here? Well I remember we found renderlist_add_object in the last blog entry, meaning we can start investigating where that function is called. By repeating the function patching thing we did but this time backwards from the function (excluding the functions that handle players and npc’s), I quickly figured out that FUN_00115800 was responsible of adding static map entities to the renderlist. Here I went on a nice trip to figure out where the static objects come from (I tried using this method before the renderlist thing).

The function “chain” I went through was FUN_000a17a0 -> FUN_001152f0 -> FUN_00115800 -> renderlist_add_interactable -> renderlist_add_object

Let’s start with the important part of FUN_000a17a0, with renamed stuff.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
index = 0;
if (world_chunk_count != 0) {
    index_10 = 0;
    do {
        lVar7 = chunks;
        chunk = *(longlong *)(chunks + 8 + index_10);
        if (chunk != 0) {
            uVar15 = *(uint *)(probs_coords + index * 4);
            FUN_001152f0
                (chunk,((int)uVar15 >> 8) * 0x40 - loaded_chunk_origin_y,
                 (uVar15 & 0xff) * 0x40 - loaded_chunk_origin_x,renderlist);
            *(undefined8 *)(*(longlong *)(lVar7 + 8 + index_10) + 0x18) = 0;
        }
        index = index + 1;
        index_10 = index_10 + 0x10;
    } while (index < world_chunk_count);
}

After a bit of inspecting it turned out that here we had an array of “map chunks” to be loaded, this function is called every time in OSRS when you move far enough and get a small “loading” freeze. You could technically hook the function here and iterate map objects when loaded, but externally it did not work out due to some of the data getting overwritten before you had the chance to read it. But I do not suggest this for just static objects especially due to the fact that you will need to handle dynamic objects separately too.

Let’s then look at FUN_001152f0 after a bit of refactoring.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
/* WARNING: Removing unreachable block (ram,0x00115425) */
void add_chunk_list_to_renders(longlong chunk,int x,int y,longlong renderlist)
{
  short sVar1;
  ushort uVar2;
  int iVar3;
  ulonglong uVar4;
  longlong lVar5;
  uint uVar6;
  int pos_y;
  int pos_x;
  int iVar7;
  undefined8 uVar8;
  int object_id;
  byte misc_flags;
  
  *(undefined8 *)(chunk + 0x18) = 0;
  object_id = -1;
  if (*(longlong *)(chunk + 8) != 0) {
    uVar8 = 0x8000;
    do {
      pos_y = FUN_0012e620(chunk);
      if (pos_y == 0) {
        return;
      }
      object_id = object_id + pos_y;
      uVar6 = 0;
      while( true ) {
        misc_flags = *(byte *)(*(longlong *)(chunk + 0x18) + *(longlong *)(chunk + 0x10));
        uVar2 = (ushort)misc_flags;
        if (misc_flags < 0x80) {
          uVar4 = *(longlong *)(chunk + 0x18) + 1;
          *(ulonglong *)(chunk + 0x18) = uVar4;
        }
        else {
          sVar1 = FUN_00031a20(chunk);
          uVar4 = *(ulonglong *)(chunk + 0x18);
          uVar2 = sVar1 + (short)uVar8;
        }
        if (uVar2 == 0) break;
        uVar6 = (uVar6 - 1) + (uint)uVar2;
        *(ulonglong *)(chunk + 0x18) = uVar4 + 1;
        pos_x = ((int)uVar6 >> 6 & 0x3fU) + x;
        misc_flags = *(byte *)(uVar4 + *(longlong *)(chunk + 0x10));
        pos_y = (uVar6 & 0x3f) + y;
        iVar7 = (int)uVar6 >> 0xc;
        if ((((0 < pos_x) && (0 < pos_y)) && (pos_x < 0x67)) && (pos_y < 0x67)) {
          iVar3 = iVar7 + -1;
          if ((height_stuff[(longlong)pos_y + ((longlong)pos_x + 0x68) * 0x68] & 2) == 0) {
            iVar3 = iVar7;
          }
          lVar5 = 0;
          if (-1 < iVar3) {
            lVar5 = DAT_01886180 + (longlong)iVar3 * 0x28;
          }
          // misc_flags & 3 would be orientation I believe and misc_flags >> 2 is some sort of object type ID.
          FUN_00115800(iVar7,pos_x,pos_y,object_id,misc_flags & 3,misc_flags >> 2,renderlist,lVar5);
        }
        uVar8 = 0x8000;
      }
    } while (uVar4 < *(ulonglong *)(chunk + 8));
  }
  return;
}

It’s still a god damn mess, but it took me a while as I reimplemented the entire function in my client to render FUN_00115800 parameters in the world for easier inspection on mass, using x64dbg to dump the parameters is of course possible, but very hard to get anything concrete out of it due to the sheer volume of calls with “hard” to figure out parameters.

In the end “useless” for object listing due to the renderlist proving to be a better method. But what it did help us with is identifying FUN_00115800 parameters which will help us figure out object definitions. Let’s rename some stuff and we get the following for FUN_00115800

1
2
void renders_objects(uint param_1,uint pos_x,uint pos_y,undefined4 object_id,uint object_orientation
                    ,int object_type,longlong renderlist,longlong param_8,ulonglong param_9)

Now, I really want to know where object_id is used, because a key to the definition is likely the ID.

And right in the beginning we see the following:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
  uVar26 = (undefined4)((ulonglong)in_stack_fffffffffffffef0 >> 0x20);
  local_res8[0] = param_1;
  local_pos_x[0] = pos_x;
  local_pos_y[0] = pos_y;
  local_object_id[0] = object_id; // < - INTERESTING
  cVar1 = FUN_0009d7a0();
  if (((cVar1 != '\0') &&
      (uVar7 = FUN_00077990(&DAT_01c01960,0,local_pos_x[0],local_pos_y[0]), (uVar7 & 2) == 0)) &&
     (uVar7 = FUN_00077990(&DAT_01c01960,local_res8[0],local_pos_x[0],local_pos_y[0]),
     (uVar7 & 0x10) != 0)) {
    return;
  }
  if ((int)local_res8[0] < (int)DAT_0043de00) {
    DAT_0043de00 = local_res8[0];
  }
  FUN_000d2a30(local_object_id[0]);  // < - INTERESTING

Let’s look at FUN_000d2a30 then shall we? I made it a bit cleaner by retyping what was DAT_0046e620 as a longlong *.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
ulonglong * FUN_000d2a30(int object_id)

{
  // OMITTED LOCAL VARIABLE DEFINITIONS
  puVar6 = (ulonglong *)PTR_0046e620[(ulonglong)(longlong)object_id % (DAT_0046e628 & 0xffffffff)];
  while (puVar6 != (ulonglong *)0x0) {
    if ((longlong)object_id == *puVar6) goto LAB_000d2a72;
    puVar6 = (ulonglong *)puVar6[8];
  }
  puVar6 = (ulonglong *)PTR_0046e620[DAT_0046e628];
LAB_000d2a72:
  if (puVar6 == (ulonglong *)PTR_0046e620[DAT_0046e628]) {
    puVar7 = &DAT_020fe0e8;
  }
 // ...
}

Let’s open up the trusty old ReClass to see whats up at PTR_0046e620 and DAT_0046e628.

Data at PTR_0046e620 Data at PTR_0046e620

What we had there was a pointer array and its max size. And as we can see from the decompiler output every index (object id) that is looked up from the array goes through a object_id % size operation, which would mean that even objects with id’s in the tens of thousands would be fine to look up. I’m next to a trusty tree with the ID 1276 I always tend to use for testing so I’ll try and look its ID up from the list.

We found a ladder Definition we found was for a Ladder

Isn’t that interesting? By going to 1276 we’ve arrived at a Ladder for some reason. But some keen eyed readers may have noticed where this is going based on the snippet above. Let’s look at it again.

1
2
3
4
  while (puVar6 != (ulonglong *)0x0) {
    if ((longlong)object_id == *puVar6) goto LAB_000d2a72;
    puVar6 = (ulonglong *)puVar6[8];
  }

So while we don’t get a null pointer at puVar6, we check if our object_id matches *puVar6, meaning the first variable of the struct pointed to, which in this case as we can see in the ReClass image is the ID of a ladder, and if they aren’t a match, like 1276 and 17384 aren’t, we should read the pointer from puVar6[8] which is the same as puVar6 + 0x40 to get to the next definition.

Next definition The next pointer at puVar6[8] got us to the Tree definition

Success. We found the name for our object.

Here’s a few snippets that should let you read id’s as you please, this code includes some of the structure definition for objects too.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
// If you want to try and find more details from the structure
// This is a good reference: https://docs.rs/rs-cache/0.3.3/rscache/def/osrs/
class object_definition
{
public:
	int64_t object_id; //0x0000
	char pad_0004[48]; //0x0008
	char* object_name; //0x0038
	char pad_0040[120]; //0x0040
	int32_t x; //0x00B8
	int32_t y; //0x00BC
	int32_t z; //0x00C0
	char pad_00C4[76]; //0x00C4
	class N00001786 options[5]; //0x0110
	char pad_01B0[56]; //0x01B0
	int32_t version;
	char pad[4];
	class N00001384* version_array; //0x01F0
	int32_t version_index; //0x01F8
	int32_t version_index_2; //0x01FC
}; //Size: 0x0200

class definition_node
{
public:
	int64_t object_id; //0x0000
	char pad_0008[8]; //0x0008
	void* definition; //0x0010
	char pad_0018[40]; //0x0018
	definition* next_ptr; //0x0040
}; //Size: 0x0048

class definition_arr
{
public:
	definition_node* definition_arr; //0x0000
	int32_t definition_arr_size; //0x0008
}; //Size: 0x000C

// ...
// Note: memoryman is just a very simple ReadProcessMemory wrapper. 
// There are plenty of them around if you want to find one.

definition_arr defs;
memoryman.read(modulebase + 0x046e620, defs);
std::vector<uintptr_t> definition_ptr_array(defs.definition_arr_size);
memoryman.read((uintptr_t)defs.definition_arr, definition_ptr_array[0], defs.definition_arr_size * sizeof(uintptr_t));

// ...

definition_node node;
memoryman.read(definition_ptr_array[object_id % definition_ptr_array.size()], node);
while (node.object_id != objectid && node.next_ptr)
{
	memoryman.read((uintptr_t)node.next_ptr, node);
}
object_definition obj;
memoryman.read((uintptr_t)node.definition, obj);

I’d keep definition_arr and definition_node structs close to heart, they are used for all other definitions too. You’ll likely run in to similar code in OSRS a lot with different definitions.

Now let’s do this again with item definitions! Or not, because it’s been literally over a month and I honestly can’t remember where I found it. But it’s FUN_000d73b0 , it’s being referenced in tons of different places so I could probably make some method up, but just go look at it.

1
2
3
4
5
6
7
// item_defs = DAT_004cc920
// item_defs_size = DAT_004cc928
puVar12 = (ulonglong *)item_defs[uVar13 % (item_defs_size & 0xffffffff)];
while (puVar12 != (ulonglong *)0x0) {
    if (uVar13 == *puVar12) goto LAB_000d7408;
    puVar12 = (ulonglong *)puVar12[8];
}

It’s the same method, new location. For items I haven’t got much of the definition reversed for you due to not having to troubleshoot, and the fact that I read my item definitions from a file, but you can easily go look at different definitions to figure it out, you may want to do this as it enables you to for example disable action menu options like bone burial etc. if you overwrite string pointers. But you can use the following to test with identical code to what was done with object definitions.

1
2
3
4
5
6
7
class item_definition
{
public:
	int32_t item_id; //0x0000
	char pad_0004[4]; //0x0004
	char* name; //0x0008
}; //Size: 0x0010

NPC definition

This is going to be easy, thank god. In fact so easy that I’m not once again writing that much. The NPC object from part 1 of the series points to the definition! Why didn’t they think of this for the items and static objects :/..

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// If you want to try and find more details from the structure
// This is a good reference: https://docs.rs/rs-cache/0.3.3/rscache/def/osrs/
class npc_definition
{
public:
	int32_t id; //0x0000
	char pad_0004[4]; //0x0004
	char* name; //0x0008
	char* name_end_ptr; //0x0010
	char* N000065E9; //0x0018
	char pad_0020[232]; //0x0020
	class action actions[5]; //0x0108
	int32_t visible_on_minimap; //0x01A8
	int32_t combat_lvl; //0x01AC
	char pad_01B0[40]; //0x01B0
	int32_t version; //0x01D8
	char pad_01DC[4]; //0x01DC
	int32_t* version_array; //0x01E0
	int32_t version_index; //0x01E8
	int32_t version_index_2; //0x01EC
	int32_t interactable; //0x01F0
	int32_t pet; //0x01F4
}; //Size: 0x01F8

class npc
{
public:
	char pad_0000[8]; //0x0000
	int unk_check;
	char pad[4];
	int position_x; //0x0010
	int position_y; //0x0014
	int rotation; //0x0018
	char pad_001C[0x12c]; //0x001C
	int height; // 0x148
	char pad_014C[516]; //0x0014C
	npc_definition* npc_definition; //0x0350
}; //Size: 0x0358

Definition versions

But it’s not all fun and games though. If you implemented object and NPC definitions you may now notice that there are some objects that don’t have proper definitions for them, and thats what the variables with “version” in the name were for.

Uh oh, some NPCs have invalid definitions Uh oh, some NPCs have invalid definitions

Pretty sure that gray guy and the booth in front of them should be like any other right? Well not exactly. The game has something I can only describe as definition versions. For example that banker, or actually 1 booth and 1 banker in every bank in the game has multiple versions, there are also invisible NPCs around in cities and some quest NPCs have issues too, but why? Well there’s a few reasons related to the fact that the OSRS game code apparently doesn’t allow not transmitting certain NPCs or objects based on game mode or quest states. Examples:

  1. Deadman mode, an alternative game mode for OSRS. The invisible NPCs roaming around towns are actually high level guards roaming around, but on regular game mode they will have definitions that will hide the NPC from players.
  2. Deadman mode once again, but in this case the bankers and bank booths. In “DMM” every bank has a special wizard banker with a special bank booth, which need different definitions used for the different game modes.
  3. If an NPCs interactions or model needs to change after a quest or something else account based, they will use different definition.

I’ve investigated this again ages ago so here is some quick information dumping on the subject. We start with this in the render_npcs function of part 1.

1
(cVar8 = FUN_000d7250(*(longlong *)(npc_object + 0x350))

and the function is as following:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
bool definition_version(longlong npc_definition)

{
  uint uVar1;
  
  if (*(longlong *)(npc_definition + 0x1d8) == 0) {
    return true;
  }
  if (*(int *)(npc_definition + 0x1e8) == -1) {
    if (*(int *)(npc_definition + 0x1ec) == -1) goto LAB_000d72b9;
    uVar1 = PTR_01894b78[*(int *)(npc_definition + 0x1ec)];
  }
  else {
    uVar1 = FUN_000be990(*(int *)(npc_definition + 0x1e8));
  }
  if ((-1 < (int)uVar1) && ((int)uVar1 < *(int *)(npc_definition + 0x1d8))) {
    return *(int *)(*(longlong *)(npc_definition + 0x1e0) + (longlong)(int)uVar1 * 4) != -1;
  }
LAB_000d72b9:
  return *(int *)(*(longlong *)(npc_definition + 0x1e0) + -4 +
                 *(longlong *)(npc_definition + 0x1d8) * 4) != -1;
}

The first PTR_01894b78 works pretty well for NPC versions iirc. but for game mode changes we need to look at FUN_000be990

I’ve also found that I’ve not had to implement anything past those, haven’t ran in to edge cases as of now.

So for the next function this is the relevant part

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
uint FUN_000be990(undefined4 param_1)

{
  longlong *plVar1;
  int *piVar2;
  int iVar3;
  longlong *plVar4;
  uint uVar5;
  longlong *local_18;
  int *local_10;
  
  FUN_000bb1e0(&local_18,param_1);
  plVar4 = local_18;
  uVar5 = (&DAT_00339560)[local_10[2] - local_10[1]] &
          PTR_01894b78[*local_10] >> ((byte)local_10[1] & 0x1f);
}

This function is also used for game objects in FUN_000d4eb0. I also noticed that here there’s not much done with the new definition ID the function gets. But this function is also called in FUN_000d71b0 which is called by another function that iterates NPCs, this other function does the same stuff essentially but also calls FUN_000d5d40 which has the familiar definition lookup code.

So in a nutshell what we want to re-implement now are the following:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
longlong ** FUN_000d71b0(longlong param_1,longlong **param_2)
{
  uint uVar1;
  int new_index;
  
  if (*(int *)(param_1 + 0x1e8) == -1) {
    if (*(int *)(param_1 + 0x1ec) != -1) {
      uVar1 = version_2_array[*(int *)(param_1 + 0x1ec)];
      goto LAB_000d71ed;
    }
  }
  else {
    uVar1 = version_1_lookup(*(int *)(param_1 + 0x1e8));
LAB_000d71ed:
    if ((-1 < (int)uVar1) && ((int)uVar1 < *(int *)(param_1 + 0x1d8) + -1)) {
      new_index = *(int *)(*(longlong *)(param_1 + 0x1e0) + (longlong)(int)uVar1 * 4);
      goto LAB_000d721e;
    }
  }
  new_index = *(int *)(*(longlong *)(param_1 + 0x1e0) + -4 + *(longlong *)(param_1 + 0x1d8) * 4);
LAB_000d721e:
  if (new_index != -1) {
    read_new_npc_definition(param_2,new_index);
    return param_2;
  }
  *param_2 = (longlong *)0x0;
  param_2[1] = (longlong *)0x0;
  return param_2;
}

ulonglong * FUN_000d4eb0(longlong param_1)

{
  uint uVar1;
  ulonglong *puVar2;
  int object_id;
  longlong in_GS_OFFSET;
  
  if (*(int *)(param_1 + 0x1f8) == -1) {
    if (*(int *)(param_1 + 0x1fc) != -1) {
      uVar1 = version_2_array[*(int *)(param_1 + 0x1fc)];
      goto LAB_000d4ee6;
    }
  }
  else {
    uVar1 = version_1_lookup(*(int *)(param_1 + 0x1f8));
LAB_000d4ee6:
    if ((-1 < (int)uVar1) && ((int)uVar1 < *(int *)(param_1 + 0x1e8) + -1)) {
      object_id = *(int *)(*(longlong *)(param_1 + 0x1f0) + (longlong)(int)uVar1 * 4);
      goto LAB_000d4f17;
    }
  }
  object_id = *(int *)(*(longlong *)(param_1 + 0x1f0) + -4 + *(longlong *)(param_1 + 0x1e8) * 4);
LAB_000d4f17:
  if (object_id != -1) {
    puVar2 = read_new_object_definition(object_id);
    return puVar2;
  }
  if (*(int *)(**(longlong **)(in_GS_OFFSET + 0x58) + 0x60) < _DAT_020fe030) {
    _Init_thread_header(&DAT_020fe030);
    if (_DAT_020fe030 == -1) {
      _DAT_020fe038 = ZEXT816(0);
      atexit(FUN_00317200);
      FUN_002b9e64(&DAT_020fe030);
    }
  }
  return &DAT_020fe038;
}

If you want to do it yourself you absolutely can, I just wanted to document the where and why. Here’s a proof of concept for you though. Very little to be changed to use with object definitions. Just definitions_array and the struct type iirc.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
// NOTE, MAY HAVE ISSUES, UNTESTED CLARITY READABILITY CHANGES FOR BLOG
// DM ME ON TWITTER TO NOTIFY YOU SPOT SOMETHING
// AGAIN POC, REFACTOR HOW IT WOULD MAKE SENSE FOR YOUR USE
class version_data
{
public:
	uint32_t version_id; //0x0000
	uint32_t mask_index_1; //0x0004
	uint32_t mask_index_2; //0x0008
}; //Size: 0x000C
class definition_node
{
public:
	int64_t object_id; //0x0000
	char pad_0008[8]; //0x0008
	void* definition; //0x0010
	char pad_0018[40]; //0x0018
	definition* next_ptr; //0x0040
}; //Size: 0x0048

class definition_arr
{
public:
	definition_node* definition_arr; //0x0000
	int32_t definition_arr_size; //0x0008
}; //Size: 0x000C
std::vector<uintptr_t> version_array;
std::vector<uintptr_t> definitions_array;
std::vector<uint32_t> mask_array(32);
// Update before iterating entities:
definition_arr ver;
memoryman.read(modulebase + 0x0044e780, ver);
definition_arr npcdet;
memoryman.read(modulebase + 0x004c9f20, npcdet);

if (version_array.size() != ver.definition_arr_size) // unsure if the array sizes can change on runtime or are they constant
    version_array.resize(ver.definition_arr_size);
memoryman.read((uintptr_t)ver.definition_arr, version_array[0], ver.definition_arr_size * sizeof(uintptr_t));

if (definitions_array.size() != npcdet.definition_arr_size)
    definitions_array.resize(npcdet.definition_arr_size);
memoryman.read((uintptr_t)npcdet.definition_arr, definitions_array[0], npcdet.definition_arr_size * sizeof(uintptr_t));

memoryman.read(modulebase + 0x0339560, mask_array[0], 32 * sizeof(uint32_t));

// When reading individual entities:

uint32_t get_new_index(const npc_definition& definition)
{
    uint32_t uVar1;
    int new_index = -1;
    if (definition.version_index == -1) 
    {
        if (definition.version_index_2 != -1) 
        {
            uintptr_t arr_ptr;
            memoryman.read(modulebase + 0x01894b78, arr_ptr);
            memoryman.read((arr_ptr + definition.version_index_2 * 4), uVar1);
            memoryman.read(((uintptr_t)definition.version_array + uVar1 * 4), new_index);
        }
    }
    else 
    {
        definition_node ver;
        memoryman.read(version_array[definition.version_index % version_array.size()], ver);
        // reusing struct, whatever even if not object id's
        while (ver.object_id != definition.version_index && ver.next_ptr)
        {
            memoryman.read((uintptr_t)ver.next_ptr, ver);
        }
        version_data data;
        uintptr_t arr_ptr;
        memoryman.read(modulebase + 0x01894b78, arr_ptr);
        memoryman.read((uintptr_t)ver.definition, data);
        memoryman.read(arr_ptr + data.version_id * 4, uVar1);
        uVar1 = mask_array[data.mask_index_2 - data.mask_index_1] & uVar1 >> ((uint8_t)data.mask_index_1 & 0x1f);
        memoryman.read(((uintptr_t)definition.version_array + uVar1 * 4), new_index);
    }
}
void fix_version_definitions(npc_definition& definition)
{

    if (definition.version != 0)
    {
        auto new_index = get_new_index(definition);
        if (new_index != -1)
        {
            definition_node det;
            memoryman.read(definitions_array[new_index % definitions_array.size()], det);

            while (det.npc_id != new_index && det.next_ptr)
            {
                memoryman.read((uintptr_t)det.next_ptr, det);
            }
            memoryman.read((uintptr_t)det.definition, definition);
        }
    }
}

Localplayer reclass dump

Here’s a reclass project file that contains a bit of interesting player stuff, likely the same for NPCs (besides the definitions) but unconfirmed.

Sharing these structs as a reclass project as it contains hitsplat stuff and health bars, and you may want to figure out where those are handled so you can use ReClass’s “Find out what writes to this address” feature.

Using a write breakpoint will tell you where code writes to an address Using a write breakpoint will tell you where code writes to an address osrs_blog.rcnet

Conclusion

This was just dumping a lot of essential stuff I really wanted to get out but never really felt like they warranted full blog posts due to being a lot of smaller parts of the whole.

Feedback, complaints, whatever are easiest sent to @alert_insecure on twitter or alternatively email atte@reversing.games if you’re old school like that.

This post is licensed under CC BY 4.0 by the author.
Contents

Trending Tags