Baldur's Gate 3

Add topics here with methods, analysis, code snippets, mods etc. for a certain game that normally won't make it in the Tables or Requests sections.
User avatar
SunBeam
Administration
Administration
Posts: 4702
Joined: Sun Feb 04, 2018 7:16 pm
Reputation: 4287

Baldur's Gate 3

Post by SunBeam »

Game Name: Baldur's Gate 3
Game Vendor: Steam
Game Version: 4.1.83.2651
Game Process: bg3.exe
Game File Version: 4.1.83.2651



Hello folks, just got this game installed. I'll post my research in this topic; feel free to join in.

First-up, the game's using an Engine I've not seen so far - Divinity Engine:

Code: Select all

bg3.exe+3C7818D - 48 8D 0D 4CCF2801     - lea rcx,[bg3.exe+4F050E0] { ("4.1.83.2651") }
..
bg3.exe+3C781B3 - 48 8D 0D 36CF2801     - lea rcx,[bg3.exe+4F050F0] { ("The Divinity Engine") }
Health can be scanned for as a DWORD. It won't change as you alter it in Cheat Engine, which is probably why it throws most of you off when attempting it. Running a simple break and trace for it when walking over a surface filled with Caustic Brine triggers this chain:

Image

So I went all the way back to this spot:

Code: Select all

00007FF7916BCD83 | 48:8B05 961AB003   | MOV RAX,QWORD PTR DS:[7FF7951BE820] |
00007FF7916BCD8A | 48:8D9424 88000000 | LEA RDX,QWORD PTR SS:[RSP+88]       |
00007FF7916BCD92 | 48:898C24 88000000 | MOV QWORD PTR SS:[RSP+88],RCX       |
00007FF7916BCD9A | 48:8B48 18         | MOV RCX,QWORD PTR DS:[RAX+18]       |
As usual, trying to find as much as I can about pointers/structure and their dev-given names from context and/or member-functions. So I looked at that:

Image

Then entered that:

Image

Then one more time:

Image

Inside I saw this:

Image
class ecs::EntityWorld **__cdecl ls::MallocInterface<class ecs::EntityWorld *>::Allocate(unsigned __int64)
So I think it's safe to say that:

mov rax,[bg3.exe+5B2E820] { (155A6C84CE0) }
mov rcx,[rax+18]
--> rcx == a EntityWorld instance (first one created, so I'll assume this is World)

God Mode can be achieved by fiddling with this:

Code: Select all

bg3.exe+24BBAD9 - 80 BE 9C000000 00     - cmp byte ptr [rsi+0000009C],00 { 0 }
I got it by setting an exception breakpoint over the Health structure, of a considerable size, and watching what pops up as the Caustic Brine damages the Player. And this was the list:

Image

Then I tried that CMP, setting the value to 0x1 and no more damage taken. You can still hear the yell though :) Working on a Perfect God Mode and getting some structure names and relationships.

[ meanwhile the game has updated, so we're now on 4.1.83.3931 ]

User avatar
Csimbi
RCE Fanatics
RCE Fanatics
Posts: 878
Joined: Sat Apr 29, 2017 9:04 pm
Reputation: 1202

Re: Baldur's Gate 3

Post by Csimbi »

;-)

djkunn
Expert Cheater
Expert Cheater
Posts: 69
Joined: Tue Sep 19, 2017 1:19 am
Reputation: 12

Re: Baldur's Gate 3

Post by djkunn »

I really think you will deserve more than a cookie when you make that for us :D

fireundubh
Expert Cheater
Expert Cheater
Posts: 141
Joined: Sun Sep 24, 2017 1:53 am
Reputation: 31

Re: Baldur's Gate 3

Post by fireundubh »

If you use this launch parameter...

Code: Select all

--logPath "F:\SteamLibrary\steamapps\common\Baldurs Gate 3"
You'll get gold.log and network.log in the game dir. Once you exit the game, they'll be populated. These logs will show you some semblance of a human readable stack for at least the client-server arch.

From there, you can find that bg3_dx11.exe+11AF420 is called by many ecl and esv class methods to log them. It also looks like that even when there's no log call, the FQN is still stored as a string in each method.

Also, for now, the reason why we should care about bg3_dx11.exe is simple: the Vulkan version is unstable and the developers recommend running the DX11 version, at least until the next hotfix.

User avatar
SunBeam
Administration
Administration
Posts: 4702
Joined: Sun Feb 04, 2018 7:16 pm
Reputation: 4287

Re: Baldur's Gate 3

Post by SunBeam »

^ The LariLauncher automatically launches bg3.exe. I'm assuming you run the DX11 version by manually starting bg3_dx11.exe.. Else I don't see some switch, as the launcher has nothing in the settings.

fireundubh
Expert Cheater
Expert Cheater
Posts: 141
Joined: Sun Sep 24, 2017 1:53 am
Reputation: 31

Re: Baldur's Gate 3

Post by fireundubh »

In the launcher, there's a gear icon next to the Play button. Click that for the dropdown.

Also, hotfix #1 just went up. ([Link])

User avatar
SunBeam
Administration
Administration
Posts: 4702
Joined: Sun Feb 04, 2018 7:16 pm
Reputation: 4287

Re: Baldur's Gate 3

Post by SunBeam »

fireundubh wrote:
Wed Oct 07, 2020 7:08 pm
...
Aha!

Image

I was looking at the other gear, bottom-left.

User avatar
xICEMANx117
Expert Cheater
Expert Cheater
Posts: 117
Joined: Mon Oct 16, 2017 4:16 am
Reputation: 17

Re: Baldur's Gate 3

Post by xICEMANx117 »

this seems awesome, i cant wait to see what you can do with making a trainer or a table with this, all this looks foreign to me i wish i understood this better. Thank you Sunbeam

User avatar
SunBeam
Administration
Administration
Posts: 4702
Joined: Sun Feb 04, 2018 7:16 pm
Reputation: 4287

Re: Baldur's Gate 3

Post by SunBeam »

Meanwhile the game has updated on my end as well: 4.1.83.3931. Switched to the DX11 version, so I'll continue with the bg3_dx11 executable. Found this while at it:

Image

Lame or not? Seems like they plug in modules - the games - into the engine as new game titles. Or maybe it's just the same Engine used for that game with additional quirks, much like they did with Marvel's Avengers (same Engine used for 'Rise of the Tomb Raider').

User avatar
TemptingIcarus
Expert Cheater
Expert Cheater
Posts: 396
Joined: Thu Aug 16, 2018 11:32 pm
Reputation: 53

Re: Baldur's Gate 3

Post by TemptingIcarus »

Hey Sunbeam, forgive my ignorance. But I was using CE earlier to try to test some stuff in the character creator. I think some of the stats are on 4 bytes. Anyway, I noticed that when using Vulkan, CE won't even work for me. I'm on the GOG Version. Does Vulkan stop CE from working properly?

User avatar
SunBeam
Administration
Administration
Posts: 4702
Joined: Sun Feb 04, 2018 7:16 pm
Reputation: 4287

Re: Baldur's Gate 3

Post by SunBeam »

^ The first part of this topic was done using the Vulkan version. Scanning and debugging works fine, I found no issues. So no, I doubt CE has a problem with Vulkan.

Meanwhile, not much progress with structure names, but I'll keep at it. Found a lead on QWORD hashes and Class names. But, if you want a Perfect God Mode, here's the deal. I've redone the scan for HP, on DWORD. Found the address and ran a 'break and trace'. Left the branch here:

Image

Then scrolled up to the prologue of the function. And put a RET there. I noticed that the game would still display what kind of damage is applied to you, but no amount, nor yelling from the character. So I'd say it's a good hook spot:

Code: Select all

bg3_dx11.exe+24BC290 - 48 8B C4              - mov rax,rsp <-- prologue
bg3_dx11.exe+24BC293 - 55                    - push rbp
bg3_dx11.exe+24BC294 - 53                    - push rbx
bg3_dx11.exe+24BC295 - 56                    - push rsi
bg3_dx11.exe+24BC296 - 57                    - push rdi
bg3_dx11.exe+24BC297 - 41 54                 - push r12
bg3_dx11.exe+24BC299 - 41 55                 - push r13
bg3_dx11.exe+24BC29B - 41 56                 - push r14
bg3_dx11.exe+24BC29D - 41 57                 - push r15
bg3_dx11.exe+24BC29F - 48 8D A8 88F6FFFF     - lea rbp,[rax-00000978]
bg3_dx11.exe+24BC2A6 - 48 81 EC 380A0000     - sub rsp,00000A38
bg3_dx11.exe+24BC2AD - 0F29 70 A8            - movaps [rax-58],xmm6
bg3_dx11.exe+24BC2B1 - 0F29 78 98            - movaps [rax-68],xmm7
bg3_dx11.exe+24BC2B5 - 48 8B 05 AC056203     - mov rax,[bg3_dx11.exe+5ADC868]
bg3_dx11.exe+24BC2BC - 48 33 C4              - xor rax,rsp
bg3_dx11.exe+24BC2BF - 48 89 85 00090000     - mov [rbp+00000900],rax
bg3_dx11.exe+24BC2C6 - 4C 89 4C 24 50        - mov [rsp+50],r9
bg3_dx11.exe+24BC2CB - 4D 8B E8              - mov r13,r8
bg3_dx11.exe+24BC2CE - 4C 8B FA              - mov r15,rdx
bg3_dx11.exe+24BC2D1 - 48 89 4C 24 58        - mov [rsp+58],rcx
bg3_dx11.exe+24BC2D6 - 48 8B 85 A8090000     - mov rax,[rbp+000009A8]
What to make of the location:

- rcx -- didn't quite check it
- [rdx] -- hash of who's dealing the damage amount
- [r8] -- hash of who's receiving the damage amount

You will notice that whenever you're damaged by elemental Entities (Fire, Caustic Brine, etc.) [rdx] is FFC0000000000000, while [r8] is the hash value that you'd see at HP base + 0x8.

Here's a run-down:

- set a breakpoint @ "bg3_dx11.exe+229FE09":

Code: Select all

bg3_dx11.exe+229FE09 - 89 78 50              - mov [rax+50],edi
- take damage and check RAX's memory space:

Image

- so my HP (or Character, don't know yet) hash is 0280000200000720
- now go to "bg3_dx11.exe+24BC290" and set a breakpoint
- when it breaks, check [rdx] and [r8]; in my case:

Image

Image

Now.. when I shoot something, like this:

Image

This is what happens at that address ("bg3_dx11.exe+24BC290"):

Image

Image

So in this scenario, our Player (id: 0x0280000200000720) is damaging a Brain Jar (id: 0x0280000200002990). If you RET the function @ "bg3_dx11.exe+24BC290", you will notice no one dies, meaning this is shared. That's why we're looking at Entity (I think) ids. We'll use them as filters. So the goal here is finding the Player id somewhere hopefully stored statically.

More to follow.

fireundubh
Expert Cheater
Expert Cheater
Posts: 141
Joined: Sun Sep 24, 2017 1:53 am
Reputation: 31

Re: Baldur's Gate 3

Post by fireundubh »

SunBeam wrote:
Wed Oct 07, 2020 9:12 pm
So the goal here is finding the Player id somewhere hopefully stored statically.
That could be difficult. In Divinity Original Sin 2, there were many UUIDs for the player because, at character creation, you could choose to play as one of several Origin characters or as one of several Custom characters. At character creation, there's also a UUID for the CharacterCreationDummy, which gets switched out with your character choice.

You can find statically stored UUIDs formatted as handles. These are just dashless UUIDs prefixed with an `h`. But that's probably not very useful. Osiris (the scripting language) provides a number of ways to check if a character is the player. For example, CharacterIsPlayer and IsPlayer. But any character in the game can be a player character; they just need the CHAR_PLAYER flag. There are also CHAR_PARTY, CHAR_DEAD, and CHAR_IMMORTAL flags.

User avatar
SunBeam
Administration
Administration
Posts: 4702
Joined: Sun Feb 04, 2018 7:16 pm
Reputation: 4287

Re: Baldur's Gate 3

Post by SunBeam »

^ Yeah, found that EntityWorld pointer is used to retrieve various PlayerUUID-typed structures, based on a hardcoded DWORD. These are typically stored in static addresses.

Example:

Code: Select all

00007FF7AD474A8A | 48:8D4E 30  | LEA RCX,QWORD PTR DS:[RSI+30] |
00007FF7AD474A8E | E8 AD2432FF | CALL bg3_dx11.7FF7AC796F40    |
rcx == p; p[0x00] = EntityWorld = [[[bg3_dx11.exe+5B5A1C0]+18]]; p[0x08] = hash

Inside that CALL though, you will find this:

Image

Then there are CALLs that land on structurally-similar functions (a wrapper is ran first, then the core function), like so:

Image

Image

Notice how the static address here is different.

So if you find all such functions.. feeding an RCX buffer made up of 2 QWORDs, first being the EntityWorld pointer, next being PlayerUUID, you get various Player-related structures :) We just need to find a few more bearings to properly understand WTF is going on here.

tfigment
Table Makers
Table Makers
Posts: 638
Joined: Sat Apr 15, 2017 12:49 am
Reputation: 801

Re: Baldur's Gate 3

Post by tfigment »

Apparently for DoS2 Larian had a remote console for the editor called ReCon [1]. I would not be shocked if that is how they do debug with the game as well though they say this tool is not mean for the game. Hope for ingame console might go down alittle but then again the fact that WidgetToggleDebugConsole and UseInGameConsole exist may mean there is in game console we can enable.

[1] [Link]


Edit: Sort of looks like the game uses temporary memory/workspaces a lot probably for multithreading and multiplayer. That makes some things harder to locate if true. Also it seems like a lot of the UI behaves strangely when underlying data changes meaning that enable/visible flags are only updated on change and does not reevaluate underlying state a lot.

I changed my HP and UI do not reflect the new value until healed or damaged again but it used the correct value. Even UI was showing different value after opening screens so not clear what I was seeing.

I think I did have a calls stack with memcpy or something reading the HP value lending further evidence that they maybe doing copies but perhaps that was a "realloc" operation.

User avatar
SunBeam
Administration
Administration
Posts: 4702
Joined: Sun Feb 04, 2018 7:16 pm
Reputation: 4287

Re: Baldur's Gate 3

Post by SunBeam »

I've managed to find my way through it :D Apparently those DWORDs I've mentioned are indicators and lead straight to what they're referring :) Watch diz:

Code: Select all

00007FF7AD881EFA | 8B15 F4832803 | MOV EDX,DWORD PTR DS:[7FF7B0B0A2F4] | 0xEB
00007FF7AD881F00 | 49:8BCE       | MOV RCX,R14                         |
00007FF7AD881F03 | E8 D8EF3401   | CALL bg3_dx11.7FF7AEBD0EE0          |
00007FF7AD881F08 | 84C0          | TEST AL,AL                          |
Find all references to 0x7FF7B0B0A2F4 and you'll get a shitload of results:

Image

All those LEAs will point to instances of functions having the same structure. Scroll to the bottom to find all the instructions using the value stored at 0x7FF7B0B0A2F4:

Image

Now.. if you go to any of the top LEAs, you will see this:

Image

We're looking at one of the initializers for "struct eoc::HealthComponent>(void)" :) So.. 0xEB (stored in 0x7FF7B0B0A2F4) is the identifier for exactly what the string says. Wanna find out where the HealthComponent is being acquired with the help of that DWORD value? Set breakpoints on all non-LEA instructions in your references list and start testing ;)

Using the above logic, I found these spots that should come in handy:

Entity::GetHealthComponent
Image

Set a break at the above, then hold Ctrl and target an object; preferably one that doesn't have infinite HP.

The next piece of code will get the HealthComponent of anything you hover the mouse over:

Code: Select all

00007FF7AC8D08CE | 4C:6325 93F62304        | MOVSXD R12,DWORD PTR DS:[7FF7B0B0FF68] | 0xD3 -- on mouse over
00007FF7AC8D08D5 | 4C:8D8424 98000000      | LEA R8,QWORD PTR SS:[RSP+98]           |
00007FF7AC8D08DD | 48:8D5424 28            | LEA RDX,QWORD PTR SS:[RSP+28]          |
00007FF7AC8D08E2 | 44:89A424 98000000      | MOV DWORD PTR SS:[RSP+98],R12D         |
00007FF7AC8D08EA | 49:8BCD                 | MOV RCX,R13                            |
00007FF7AC8D08ED | 4C:8BF8                 | MOV R15,RAX                            |
00007FF7AC8D08F0 | 48:8BEF                 | MOV RBP,RDI                            |
00007FF7AC8D08F3 | E8 487A3002             | CALL bg3_dx11.7FF7AEBD8340             |
00007FF7AC8D08F8 | 48:8B5424 28            | MOV RDX,QWORD PTR SS:[RSP+28]          |
00007FF7AC8D08FD | 48:B8 000000000000C0FF  | MOV RAX,FFC0000000000000               |
00007FF7AC8D0907 | 4C:8B6C24 58            | MOV R13,QWORD PTR SS:[RSP+58]          |
00007FF7AC8D090C | 48:3BD0                 | CMP RDX,RAX                            |
00007FF7AC8D090F | 74 26                   | JE bg3_dx11.7FF7AC8D0937               |
00007FF7AC8D0911 | 48:8B86 38160000        | MOV RAX,QWORD PTR DS:[RSI+1638]        |
00007FF7AC8D0918 | 4F:8D0464               | LEA R8,QWORD PTR DS:[R12+R12*2]        |
00007FF7AC8D091C | 49:C1E0 06              | SHL R8,6                               |
00007FF7AC8D0920 | 49:8B0C00               | MOV RCX,QWORD PTR DS:[R8+RAX]          |
00007FF7AC8D0924 | 48:8B01                 | MOV RAX,QWORD PTR DS:[RCX]             |
00007FF7AC8D0927 | 48:895424 30            | MOV QWORD PTR SS:[RSP+30],RDX          |
00007FF7AC8D092C | 48:8D5424 30            | LEA RDX,QWORD PTR SS:[RSP+30]          |
00007FF7AC8D0931 | FF50 20                 | CALL QWORD PTR DS:[RAX+20]             | <-- gets HealthComponent
00007FF7AC8D0934 | 48:8BE8                 | MOV RBP,RAX                            |
Other things I've mapped, that might be useful:

Image

Another example of Components:

Code: Select all

00007FF7AD1028C6 | 4C:6325 53D7A003 | MOVSXD R12,DWORD PTR DS:[7FF7B0B10020] | 0x0112: "struct eoc::ValueComponent>(void)"
00007FF7AD10293C | 4C:6325 E9D6A003 | MOVSXD R12,DWORD PTR DS:[7FF7B0B1002C] | 0x0119: "struct eoc::WieldingComponent>(void)"
00007FF7AD10298D | 4C:6325 ECD5A003 | MOVSXD R12,DWORD PTR DS:[7FF7B0B0FF80] | 0x00DB: "struct eoc::LockComponent>(void)"
00007FF7AD1029E5 | 4C:6325 50D5A003 | MOVSXD R12,DWORD PTR DS:[7FF7B0B0FF3C] | 0x00C3: "struct eoc::ExperienceConfigComponent>(void)"
00007FF7AD102A36 | 4C:6325 6BD4A003 | MOVSXD R12,DWORD PTR DS:[7FF7B0B0FEA8] | 0x009A: "struct eoc::ActionResourcesComponent>(void)"
00007FF7AD102A83 | 4C:6325 AED4A003 | MOVSXD R12,DWORD PTR DS:[7FF7B0B0FF38] | 0x00C2: "struct eoc::ExperienceComponent>(void)"
00007FF7AD102ADB | 4C:6325 3AD5A003 | MOVSXD R12,DWORD PTR DS:[7FF7B0B1001C] | 0x0111: "struct eoc::UseComponent>(void)"
00007FF7AD102B2C | 4C:6325 79D3A003 | MOVSXD R12,DWORD PTR DS:[7FF7B0B0FEAC] | 0x009E: "struct eoc::ArmorComponent>(void)"
00007FF7AD102B79 | 4C:6325 A8D4A003 | MOVSXD R12,DWORD PTR DS:[7FF7B0B10028] | 0x0114: "struct eoc::WeaponComponent>(void)"
00007FF7AD102BC6 | 4C:6325 EFD2A003 | MOVSXD R12,DWORD PTR DS:[7FF7B0B0FEBC] | 0x00A3: "struct eoc::BaseHpComponent>(void)"
00007FF7AD102C13 | 4C:6325 BAD3A003 | MOVSXD R12,DWORD PTR DS:[7FF7B0B0FFD4] | 0x00FD: "struct eoc::StatusImmunitiesComponent>(void)"
00007FF7AD102C6B | 4C:633D 76D3A003 | MOVSXD R15,DWORD PTR DS:[7FF7B0B0FFE8] | 0x0102: "struct eoc::SurfacePathInfluencesComponent>(void)"
00007FF7AD102CC4 | 4C:6325 F5D2A003 | MOVSXD R12,DWORD PTR DS:[7FF7B0B0FFC0] | 0x00F6: "struct eoc::SenseComponent>(void)"
00007FF7AD102D19 | 4C:6325 E8D0A003 | MOVSXD R12,DWORD PTR DS:[7FF7B0B0FE08] | 0x00FC: "struct eoc::StatsComponent>(void)"
All of the above are read in one single function :) Happy debugging :P

Post Reply

Who is online

Users browsing this forum: No registered users