Skip to content

Instantly share code, notes, and snippets.

@apple1417
Last active June 25, 2021 13:39
Show Gist options
  • Save apple1417/111a6d7f3a4b786d4752e3b458617e26 to your computer and use it in GitHub Desktop.
Save apple1417/111a6d7f3a4b786d4752e3b458617e26 to your computer and use it in GitHub Desktop.

Finding BL3 load remover pointers

All this only applies to the Steam version. The Epic version likely works similarly, but I can't test.

This might not be the best way of doing these things, but knowing the process might be helpful for finding similar ones in other unreal games.

I originally found the pointers on OAK-PATCHWIN64-79 (aka the cartel event patch) if you want to double check addresses. Unfortuantly I wasn't able to find nice AOB scans to easily convert them to other versions, but having knowledge of how they should work already makes it easier.

Main Menu

Originally I searched for simple bool pointers for this, but they always had other issues causing unwanted pauses. The final approach I went with is to check against the actual World object - the main menu is just it's own world like any other level.

To find this you want to start by downloading Universal UE4 Unlocker. This in general has a few neat tools you might want to use, but we'll mostly be using the object dumper.

The first thing to do is to find the name of the menu world. For BL3 I could just google a level name reference, but if you apply this onto another game you might not have that luxury. Instead, inject UUU, then in console run getall World:

results

Now there are a number of loaded worlds you could pick from, but the one I went with (and that the reference uses) is the one ending with _P.

Once you have the level name, in UUU under "Available features" you can dump object info.

dump button

This will create a file in the same directory as the game's exe holding the info. Importantly, this file contains the address of each unreal object. Search for World MenuMap_P.MenuMap_P, substituting whatever the world name actually is if applicable.

world in dump

With the address of this object you can just do your normal pointer scans in cheat engine. Changing levels destorys that world object and creates a new one, best to include that in your scans so that you truely have a current world pointer.

The best pointer I ended up finding was Borderlands3.exe+05F91C30 +0 +0.

Once you have a pointer to the object the next issue is distinguishing it - how do we know we're on the main menu vs any other map. My approach was to find the object name.

Firstly, to find out more about how unreal objects worked I looked through the Borderlands PythonSDK source code. As I write this all the BL3 info is still on it's own branch.

In unreal object names are FNames, which are actually an index into a global GNames table. This index will only be repeated if another FName uses the same string, so it's unique enough for our needs. These indexes were also consistent over game restarts, meaning I could safely use them.

By letting the cheat engine structure dissector autoguess the object field types and watching them change, as well as just by checking the sdk source, it seemed very likely that this index is at offset 0x18. But I still wanted to double check.

guessed field offsets

The first step is to find the GNames table - I downloaded and ran Unreal Finder Tool. In BL3, each entry in the GNames table points to indivdual chunks, which each point to individual FName entries (apparently 0x4000 pointers ber chunk). All the names I was checking had low enough indexes to be in the first chunk. Each pointer is 8 bytes big, so index 4682 is at an offset of 0x9250.

finder tool/names table results

It worked out. I tried a few other worlds to be safe and they were all fine too. So the final pointer (for this version of the game) is Borderlands3.exe+05F91C30 +0 +18, and we're in the main menu when it's equal to 4682 (aka 0x124A).

Later I converted this to a sigscan, by looking at accesses to the base address of the pointer, and finding this piece of unique code:

disassembly

4C 8D 0C 40
48 8B 05 ????????
4A 8D 0C C8

Loading

Finding the loading pointer is a bit more boring. I started with the simple standard method:

  1. Enter a loading screen, pause the game with CE
  2. Scan for 1
  3. Unpause the game and finish the load
  4. Scan for 0
  5. Repeat

The only noteworthy thing is that with the current world pointer you can work out a few other times you should scan for 1 too. At the start of the load it still points to the old world for a bit, in the middle it gets zero'd, and near the end it points to the new world - all while still in the load. I scanned for 1 in all three of these regions.

Now I was actually able to narrow it quite far down, to the point where I could watch accesses on each address and decide what I liked best. The one I ended up going with had the following accesses.

nice clean mov 0/1s and a cmp 0

Interestingly at the same time this is set it also unsets a pointer with a symbol Borderlands3.AmdPowerXpressRequestHighPerformance. Googling around this seems to force the game to use the highest power gpu, so disabling it during a load screen doesn't seem that out of the question, it just made me trust this address more.

Now while pointer scanning for this address something very peculiar happened - the exact same address was used every single time. I even restarted my PC in the middle and still got the same address - 20990E2C. This means the quality of my pointers probably suffered a bit.

The shortest pointers I found were as follows.

pointers

Interestingly not a single one ended with +9C.

I ignored the first pointer because it seems way too coincidental - it stores 20990000, there could easily be a constant somewhere in the program that it's reading from. Out of the four two-offset pointers, two didn't have any hits when watching for accesses on the base address, meaning they could easily be a coincidence too. The other two each had a single reference, into seperate but very similar functions. One was full of strings referencing GbxGameInstance, the other OakGameInstance.

GbxGameInstance strings

I arbitrarily settled with the GbxGameInstance one, Borderlands3.exe+0683FDC0 +F8 +9DC.

Later I started sigscanning for this function specifically, using the mov calls at the bottom.

C7 44 24 28 0C000010
C7 44 24 20 D0010000

Finding it again after a breaking patch

The 2021-06-24 patch broke this pointer slightly. The sigscans stopped working, and unfortuantly, there are thousands of matches when you try make it a little more generic. The one thing difference between each matched function is the exact strings it references. So I opened up cheat engine's referenced strings menu, let it scan for quite a while, then searched for GbxGameInstance.

single match

This led directly back to the function, where I could see the second mov instruction changed to use a 1F0, changing the sigscan to:

C7 44 24 28 0C000010
C7 44 24 20 F0010000

This still didn't work however, the offsets changed a little too. I could see the +F8 was still a valid pointer, so I did a quick 0/1 scan in the 0x1000 bytes following it, and quickly found the new final offset to be +A7C.

Simplified Guide

  1. Do an array of bytes scan across non-writable memory for 4C 8D 0C 40 48 8B 05 ???????? 4A 8D 0C C8
  2. Find the address in the mov call in the middle of the match
  3. Add the offsets +0 +18, this is the current level pointer
  4. Do another array of bytes scan across non-writable memory for C7 44 24 28 0C000010 C7 44 24 20 F0010000
  5. Find the first call inside the function that aob scan landed in - the selected one here
  6. Add offsets +F8 +9DC, this is the loading pointer
  7. Check the exe file's version, and what the current level pointer is equal to in the menu, then load everything into the autosplitter config file
  8. Open up debugview and LiveSplit, and filter down to just watching LiveSplit's output
  9. Make sure all the pointers work as expected - notably map changes should happen while still paused for loading
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment