Baldur's Gate 3
- asthebloody
- Table Makers
- Posts: 224
- Joined: Fri Oct 20, 2017 2:03 am
- Reputation: 186
Re: Baldur's Gate 3
With the information from previous page you can do this:
Whenever you move the mouse over some shit that can't be killed (aka "infinite HP"), you will see this:
0xFFC0000000000000 is the Engine's default "invalid" value
EDIT: Hmm, those are just visual. Meh
BR,
Sun
Whenever you move the mouse over some shit that can't be killed (aka "infinite HP"), you will see this:
0xFFC0000000000000 is the Engine's default "invalid" value
EDIT: Hmm, those are just visual. Meh
BR,
Sun
Re: Baldur's Gate 3
Looks like all of those strings are just references to tracing statements in the those specific function. Useful I guess. Are Components the underlying data or the visual indicators or something else? Have you ever used Ghidra? Works really well on this game in that it does not scan forever. Makes it easy to do references and get decent C code.
Edit: Oh see your update. Visual only.
If you just want where health is updated its here but I assume you already know that.
My experience was strange. Pinning the value in CE did not actually help but alt-tabbing out and back into game somehow made the value get picked up. Will note that CE is behaving strangely with this game and does not seem to update as I expect it to.
Edit 2: Regarding Console. Looks like they use lua based on strings like "lua_debug>" so the strings see like DebugGuaranteeCriticalSuccess look like they might be populating a lua table I'm thinking. Still don't see the binding for the underlying function but might be informative.
Edit: Oh see your update. Visual only.
If you just want where health is updated its here but I assume you already know that.
Spoiler
// ORIGINAL CODE - INJECTION POINT: "bg3.exe"+229F929
"bg3.exe"+229F8FB: 41 8D 50 02 - lea edx,[r8+02]
"bg3.exe"+229F8FF: 48 8D 4C 24 38 - lea rcx,[rsp+38]
"bg3.exe"+229F904: E8 57 05 F5 FE - call bg3.exe+11EFE60
"bg3.exe"+229F909: 48 8D 4C 24 38 - lea rcx,[rsp+38]
"bg3.exe"+229F90E: 83 7C 24 44 40 - cmp dword ptr [rsp+44],40
"bg3.exe"+229F913: 48 0F 4F 4C 24 38 - cmovg rcx,[rsp+38]
"bg3.exe"+229F919: 48 8B 01 - mov rax,[rcx]
"bg3.exe"+229F91C: 48 0F BA E8 01 - bts rax,01
"bg3.exe"+229F921: 48 89 01 - mov [rcx],rax
"bg3.exe"+229F924: 48 8B 44 24 28 - mov rax,[rsp+28]
// ---------- INJECTING HERE ----------
"bg3.exe"+229F929: 89 78 50 - mov [rax+50],edi
// ---------- DONE INJECTING ----------
"bg3.exe"+229F92C: 48 8B 03 - mov rax,[rbx]
"bg3.exe"+229F92F: 48 89 44 24 78 - mov [rsp+78],rax
"bg3.exe"+229F934: 48 8B 5E 38 - mov rbx,[rsi+38]
"bg3.exe"+229F938: 48 85 DB - test rbx,rbx
"bg3.exe"+229F93B: 74 2E - je bg3.exe+229F96B
"bg3.exe"+229F93D: 48 63 46 48 - movsxd rax,dword ptr [rsi+48]
"bg3.exe"+229F941: 48 8D 0C C0 - lea rcx,[rax+rax*8]
"bg3.exe"+229F945: 48 8D 3C CB - lea rdi,[rbx+rcx*8]
"bg3.exe"+229F949: 48 3B DF - cmp rbx,rdi
"bg3.exe"+229F94C: 74 1D - je bg3.exe+229F96B
"bg3.exe"+229F94E: 66 90 - nop 2
"bg3.exe"+229F8FB: 41 8D 50 02 - lea edx,[r8+02]
"bg3.exe"+229F8FF: 48 8D 4C 24 38 - lea rcx,[rsp+38]
"bg3.exe"+229F904: E8 57 05 F5 FE - call bg3.exe+11EFE60
"bg3.exe"+229F909: 48 8D 4C 24 38 - lea rcx,[rsp+38]
"bg3.exe"+229F90E: 83 7C 24 44 40 - cmp dword ptr [rsp+44],40
"bg3.exe"+229F913: 48 0F 4F 4C 24 38 - cmovg rcx,[rsp+38]
"bg3.exe"+229F919: 48 8B 01 - mov rax,[rcx]
"bg3.exe"+229F91C: 48 0F BA E8 01 - bts rax,01
"bg3.exe"+229F921: 48 89 01 - mov [rcx],rax
"bg3.exe"+229F924: 48 8B 44 24 28 - mov rax,[rsp+28]
// ---------- INJECTING HERE ----------
"bg3.exe"+229F929: 89 78 50 - mov [rax+50],edi
// ---------- DONE INJECTING ----------
"bg3.exe"+229F92C: 48 8B 03 - mov rax,[rbx]
"bg3.exe"+229F92F: 48 89 44 24 78 - mov [rsp+78],rax
"bg3.exe"+229F934: 48 8B 5E 38 - mov rbx,[rsi+38]
"bg3.exe"+229F938: 48 85 DB - test rbx,rbx
"bg3.exe"+229F93B: 74 2E - je bg3.exe+229F96B
"bg3.exe"+229F93D: 48 63 46 48 - movsxd rax,dword ptr [rsi+48]
"bg3.exe"+229F941: 48 8D 0C C0 - lea rcx,[rax+rax*8]
"bg3.exe"+229F945: 48 8D 3C CB - lea rdi,[rbx+rcx*8]
"bg3.exe"+229F949: 48 3B DF - cmp rbx,rdi
"bg3.exe"+229F94C: 74 1D - je bg3.exe+229F96B
"bg3.exe"+229F94E: 66 90 - nop 2
Edit 2: Regarding Console. Looks like they use lua based on strings like "lua_debug>" so the strings see like DebugGuaranteeCriticalSuccess look like they might be populating a lua table I'm thinking. Still don't see the binding for the underlying function but might be informative.
Last edited by tfigment on Thu Oct 08, 2020 3:35 am, edited 1 time in total.
Re: Baldur's Gate 3
^ Game uses client-server logic. Sometimes it takes 1-2s till data is synced and that's why your CE "lags" Yeah, I'll check the write spot and compare against what the mouse over shows.
EDIT: Seems to work only for static objects. The "Mouse Over Hook". Hovering over Imps, for example, doesn't get any pointer. So it's the "Type" that's filtering the data.
EDIT: Seems to work only for static objects. The "Mouse Over Hook". Hovering over Imps, for example, doesn't get any pointer. So it's the "Type" that's filtering the data.
Re: Baldur's Gate 3
Just to be clear on that health pointer. What I did was in initial area. Take fire damage. Find the location. Change the value I showed back to original value. UI will still show like 8 HP or whatever but the value is 14. Next time I get fire damage the UI and that value update to like 13 because only 1 pt damage so it took the 14 max value override but UI only updated once I was healed or damaged.
Re: Baldur's Gate 3
^ I guess you didn't have time to read all of my posts. I explained in the exact same manner as you what I did Yes, HP updates when events occur. That sync.
Re: Baldur's Gate 3
Guess not. I read enough that it seemed like you were going after the god mode flag which is generally different approach if its in built.
Re: Baldur's Gate 3
God mode is in the same structure as health values. +50, +54, and +9C
Re: Baldur's Gate 3
hey, you are awesome I can only make sence of about 10 to 20% of what you are doing. I tried to "freeze " my health with 4bite search (it works most of the time but once I restart the game or load, i have to do it again).
unfortunately even though you write kind of a walk through I am not able to do it on my own.
I hope that there is a way to make a table. Looking forward to it!! And thanks again for doing this!
unfortunately even though you write kind of a walk through I am not able to do it on my own.
I hope that there is a way to make a table. Looking forward to it!! And thanks again for doing this!
Re: Baldur's Gate 3
Gotta love "contributions" with already mentioned crap. Guys/gals, please read my shit? Thanks!
^ That's on page 1...
^ That's on page 1...
Re: Baldur's Gate 3
I wasn't contributing. I stole God Mode from you. Was just reiterating.
Re: Baldur's Gate 3
Now I understand your interest for this title -> [Link]
-
- Expert Cheater
- Posts: 143
- Joined: Sun Sep 24, 2017 1:53 am
- Reputation: 32
Re: Baldur's Gate 3
Interesting work. Are they still using Granny for animation/mesh for this game? I've worked on tools like that a lot in the past (mostly Morrowind,Obivion,Skyrim,NWN2,FO3). I wrote a 3dsmax importer/exporter for NWN2 (I think) and animation was in Granny. I liked the format since skeleton was usually included making import easier but I remember having issues figuring out some of the heavily compressed rotation controllers in the GR2 files. Not sure I wanted to try for this game yet but was thinking about it and seems like there is no need.fireundubh wrote: ↑Thu Oct 08, 2020 8:35 pmThat's old stuff! In the DOS2 mod world, I'm probably better known for [Link]. I also wrote the CLI for Norbyte's [Link], which should support BG3 file formats soon-ish.
Anyway I guess I'll stop "contributing" at this point and play the game a little.
Re: Baldur's Gate 3
Alright.. Been studying the Divinity Engine these past few days and learned quite a lot. The below is my interpretation and it's not to be considered a manual, thorough explanation or how the developers intended this to work. I'd appreciate it if you consider it for what it is and not make of it a mistake-free approach. Text is subject to criticism, adjustments and any feedback from your end. Note that I will still be using 4.1.83.3931. I know the game has updated, though I won't spend time again to re-offset everything to the latest build. That's life, suck it. You can aob it later. Want my executables, let me know and I'll upload them somewhere. They work fine, as the update only changed the executables, not other data parts (hope developers don't add stupid checks to force you into running only the latest).
First-up, I tried to understand how the HealthComponent is acquired and where from. There isn't any spot I could find myself (perhaps there is, I dunno, haven't found one) where Player information is stored like in other Engines. As such, I've set an exception breakpoint on the HealthComponent structure and let the Caustic Brine damage me. And I got this list, like I mentioned earlier in this post (note those addresses are from 4.1.83.2651; if you want them fresh, just find your HP, then find out who writes to it, navigate to the structure base, select a big ass range and set an exception breakpoint; get hit and you should see that list).
So the function in which this is obtained is here:
It runs the moment you get any type of damage. It runs for the enemy as well!
Before I continue with the HealthComponent acquiring logic, I took another approach. I decided to scan for a random value/address, 8 bytes (QWORD) when I clicked to select each member of my party:
Why? Because I am most certain there is an address, somewhere, where, once you change playable character, something will change. Considering posts from previous page, I was looking for values that resemble those hashes, like here. After doing anything possible in-game to narrow down the number of addresses, I got his many:
None of them in there are static, so don't bother checking them out on your end. Point of the matter is I started inspecting them. And I found this in one of them:
That's my Player name. So then I scrolled up a bit to check which structure I'm in (forming up the member-functions ptr) and got this layout:
So that led me to this layout:
Quick pause - if you want to find your spot, then simply scan the game's memory for this array:
Where you have to make the following adjustments:
- 44 00 D0 24 00 00 00 00 - stay the same
- 53 00 75 00 6E 00 42 00 65 00 61 00 6D 00 represent "SunBeam" in UNICODE; change it to your player's name in unicode
- 00 00 07 - the last byte represents the string size -> len( "SunBeam" ) = 0x7
So when I select (click the portrait to select character) Tav, this happens:
The id is 0x0001000000000396.
Then when I select Us, this happens:
The id is 0x000100000000037E.
Then when I select Lae'zel, this happens:
The id is 0x00010000000003C0.
And who is our base pointer, based on its member-functions? Looking at the references, I gathered it fiddles with a BindingExpression:
Then I thought "but what if I don't have 3 characters in my party to click the portraits of? I cannot unselect a character and re-select it by clicking its portrait". So then I've set an access breakpoint on that id to see what's reading it/using it. And got this:
And out of it:
You may notice I've already marked various lines with comments as I've parkoured the analysis. At that prologue, if you check RCX, you will see this:
What I've pixelated is my user id Notice that I've also started labeling some member-functions based on what the errors they'd throw internally:
So.. in short.. when you start a game, there's a client and a server created. The communication is done through messages and there's a handshake occurring to validate them, hence the lag you're experiencing when you do things in-game. As in damage happening over 0.5s or 1s from the actual action you see. Much like how Diablo 3 works, syncing server and client's actions. All server related functions are called esv (e.g.: esv::GameServer::<function_name> -> esv::GameServer::OnNewIncomingConnection). All client related functions are called ecl (e.g.: ecl::JoinProtocol::<function_name> -> ecl::JoinProtocol::ProcessMsg). There are some local server functions called ls. Or "listening"? No idea on that part. Anything you do in-game is a message sent between client and server. Whether or not it's a multi-player game or a local one, there's always a server. Not the full-fledged meaning of a server, as the regular user knows. You catch my drift. Anything in the game world has a GUID. This isn't unique to the object it represents, you can have 2 identical objects with different GUIDs. They look like this: da072fe7-fdd5-42ae-9139-8bd4b9fca406 in the Engine itself.
Why I dragged you through all of this.. the id that you see changing as you click on Character portraits is used in the HealthComponent address acquiring. To get the selected Character's id, we need to find our Player. And that player information can be retrieved from esv::GameServer
So.. esv::GameServer -> Player -> selected Character_id -> any Component we need for said Character.
Now.. if you head out of the function I pointed out earlier..
..you will land here, where I have additional comments:
Notice the "class esv::Character>(void)"? That's why the stuff I presented on the previous page is useful; the DWORD value and how it ties back to what it represents? Handy now, is it?
Then:
That is the pointer I've talked about in the first post of this topic: leading to a World instance (EntityWorld).
Then the next part..
..gets, as result in RAX, this address (for me): 00000238E5006D88. Looking at it in dump, I see the following:
Then this:
So 00000238E5006D88 adjusted to -0x8 -> 00000238E5006D80.
Now.. if we go back to the function where HealthComponent is acquired and try to combine what we learned of with the function flow, I see the following:
RAX and RDX contain the same value/address -> 00000238E5006D80 From here, RBX is populated with [RDX+10], which is:
So what's put into RBX is 0x02800001000000E4. This RBX is then used here..
..and continuing here:
Which seems to get a pointer, in my case: 0x00007FF3DDD18770.
Then:
Which gets this value 0x3AC0000100000078. This is checked against 0xFFC0000000000000 here:
Then the rest of the code in the branch and our HealthComponent in RAX
Much to post, last part ambiguous, I know. Will post a script so you can get the idea soon.
First-up, I tried to understand how the HealthComponent is acquired and where from. There isn't any spot I could find myself (perhaps there is, I dunno, haven't found one) where Player information is stored like in other Engines. As such, I've set an exception breakpoint on the HealthComponent structure and let the Caustic Brine damage me. And I got this list, like I mentioned earlier in this post (note those addresses are from 4.1.83.2651; if you want them fresh, just find your HP, then find out who writes to it, navigate to the structure base, select a big ass range and set an exception breakpoint; get hit and you should see that list).
So the function in which this is obtained is here:
It runs the moment you get any type of damage. It runs for the enemy as well!
Before I continue with the HealthComponent acquiring logic, I took another approach. I decided to scan for a random value/address, 8 bytes (QWORD) when I clicked to select each member of my party:
Why? Because I am most certain there is an address, somewhere, where, once you change playable character, something will change. Considering posts from previous page, I was looking for values that resemble those hashes, like here. After doing anything possible in-game to narrow down the number of addresses, I got his many:
None of them in there are static, so don't bother checking them out on your end. Point of the matter is I started inspecting them. And I found this in one of them:
That's my Player name. So then I scrolled up a bit to check which structure I'm in (forming up the member-functions ptr) and got this layout:
So that led me to this layout:
Quick pause - if you want to find your spot, then simply scan the game's memory for this array:
Code: Select all
44 00 D0 24 00 00 00 00 53 00 75 00 6E 00 42 00 65 00 61 00 6D 00 00 00 07
- 44 00 D0 24 00 00 00 00 - stay the same
- 53 00 75 00 6E 00 42 00 65 00 61 00 6D 00 represent "SunBeam" in UNICODE; change it to your player's name in unicode
- 00 00 07 - the last byte represents the string size -> len( "SunBeam" ) = 0x7
So when I select (click the portrait to select character) Tav, this happens:
The id is 0x0001000000000396.
Then when I select Us, this happens:
The id is 0x000100000000037E.
Then when I select Lae'zel, this happens:
The id is 0x00010000000003C0.
And who is our base pointer, based on its member-functions? Looking at the references, I gathered it fiddles with a BindingExpression:
Then I thought "but what if I don't have 3 characters in my party to click the portraits of? I cannot unselect a character and re-select it by clicking its portrait". So then I've set an access breakpoint on that id to see what's reading it/using it. And got this:
And out of it:
You may notice I've already marked various lines with comments as I've parkoured the analysis. At that prologue, if you check RCX, you will see this:
What I've pixelated is my user id Notice that I've also started labeling some member-functions based on what the errors they'd throw internally:
So.. in short.. when you start a game, there's a client and a server created. The communication is done through messages and there's a handshake occurring to validate them, hence the lag you're experiencing when you do things in-game. As in damage happening over 0.5s or 1s from the actual action you see. Much like how Diablo 3 works, syncing server and client's actions. All server related functions are called esv (e.g.: esv::GameServer::<function_name> -> esv::GameServer::OnNewIncomingConnection). All client related functions are called ecl (e.g.: ecl::JoinProtocol::<function_name> -> ecl::JoinProtocol::ProcessMsg). There are some local server functions called ls. Or "listening"? No idea on that part. Anything you do in-game is a message sent between client and server. Whether or not it's a multi-player game or a local one, there's always a server. Not the full-fledged meaning of a server, as the regular user knows. You catch my drift. Anything in the game world has a GUID. This isn't unique to the object it represents, you can have 2 identical objects with different GUIDs. They look like this: da072fe7-fdd5-42ae-9139-8bd4b9fca406 in the Engine itself.
Why I dragged you through all of this.. the id that you see changing as you click on Character portraits is used in the HealthComponent address acquiring. To get the selected Character's id, we need to find our Player. And that player information can be retrieved from esv::GameServer
So.. esv::GameServer -> Player -> selected Character_id -> any Component we need for said Character.
Now.. if you head out of the function I pointed out earlier..
..you will land here, where I have additional comments:
Notice the "class esv::Character>(void)"? That's why the stuff I presented on the previous page is useful; the DWORD value and how it ties back to what it represents? Handy now, is it?
Code: Select all
00007FF7F0618891 | 48:8B5C24 28 | MOV RBX,QWORD PTR SS:[RSP+28] | id -> 0x00010000000003C0 (Lae'zel)
00007FF7F0618896 | 4C:8B05 8B275503 | MOV R8,QWORD PTR DS:[7FF7F3B6B028] | 0xFFFFFFFFFFFFFFFF
00007FF7F061889D | 49:3BD8 | CMP RBX,R8 | if id is invalid, skip
00007FF7F06188A0 | 74 6B | JE bg3_dx11.7FF7F061890D |
Code: Select all
00007FF7F06188A2 | 48:8B05 17196403 | MOV RAX,QWORD PTR DS:[7FF7F3C5A1C0] | g_World
00007FF7F06188A9 | 48:8B50 18 | MOV RDX,QWORD PTR DS:[RAX+18] |
Then the next part..
Code: Select all
00007FF7F06188AD | 48:6305 40B66503 | MOVSXD RAX,DWORD PTR DS:[7FF7F3C73EF4] | "class esv::Character>(void)"
00007FF7F06188B4 | 48:8D0C40 | LEA RCX,QWORD PTR DS:[RAX+RAX*2] |
00007FF7F06188B8 | 48:C1E1 06 | SHL RCX,6 |
00007FF7F06188BC | 48:038A 38160000 | ADD RCX,QWORD PTR DS:[RDX+1638] |
00007FF7F06188C3 | 48:8B09 | MOV RCX,QWORD PTR DS:[RCX] |
00007FF7F06188C6 | 48:8B01 | MOV RAX,QWORD PTR DS:[RCX] |
00007FF7F06188C9 | 41:B0 01 | MOV R8B,1 |
00007FF7F06188CC | 48:8D5424 28 | LEA RDX,QWORD PTR SS:[RSP+28] |
00007FF7F06188D1 | FF50 40 | CALL QWORD PTR DS:[RAX+40] |
00007FF7F06188D4 | 48:85C0 | TEST RAX,RAX |
Then this:
Code: Select all
00007FF7F06188D9 | 48:B9 EDDEEDF151A5C1DE | MOV RCX,DEC1A551F1EDDEED |
00007FF7F06188E3 | 48:3BC1 | CMP RAX,RCX |
00007FF7F06188E6 | 74 19 | JE bg3_dx11.7FF7F0618901 |
00007FF7F06188E8 | 48:8D48 F8 | LEA RCX,QWORD PTR DS:[RAX-8] | notice the slight adjustment of our RAX
00007FF7F06188EC | 48:85C9 | TEST RCX,RCX |
00007FF7F06188EF | 74 10 | JE bg3_dx11.7FF7F0618901 |
00007FF7F06188F1 | 48:8BD7 | MOV RDX,RDI |
00007FF7F06188F4 | E8 D7CBBFFF | CALL bg3_dx11.7FF7F02154D0 |
00007FF7F06188F9 | 84C0 | TEST AL,AL |
00007FF7F06188FB | 0F84 E8000000 | JE bg3_dx11.7FF7F06189E9 |
Now.. if we go back to the function where HealthComponent is acquired and try to combine what we learned of with the function flow, I see the following:
RAX and RDX contain the same value/address -> 00000238E5006D80 From here, RBX is populated with [RDX+10], which is:
So what's put into RBX is 0x02800001000000E4. This RBX is then used here..
..and continuing here:
Which seems to get a pointer, in my case: 0x00007FF3DDD18770.
Then:
Which gets this value 0x3AC0000100000078. This is checked against 0xFFC0000000000000 here:
Code: Select all
00007FF7F025A992 | 49:3BC4 | CMP RAX,R12 |
00007FF7F025A995 | 74 2D | JE bg3_dx11.7FF7F025A9C4 |
00007FF7F025A997 | 48:8B8F 38160000 | MOV RCX,QWORD PTR DS:[RDI+1638] |
Much to post, last part ambiguous, I know. Will post a script so you can get the idea soon.
Who is online
Users browsing this forum: No registered users