Game Vendor: Steam
Game Version: 1.01
Game Process: ds.exe
Game File Version: 1.0.1.0
Hello folks. New game, different engine. This time around: Decima Engine.
Started playing the game for a little bit, past the intro with Norman falling off his dirt bike and running around for a while. Then stopped for a sec to dump the game's executable. For that I've used Task Explorer 64-bit (part of the CFF Explorer Suite found [Link]). Opened it up with x64dbg and checked the string references (it takes a bit to load them up) looking for some relevant words: debug, god, cheat, console, etc. And found this interesting lead:
Now I understand people are going to try to replicate what I describe here. Please understand you need to also have some decent knowledge on reverse-engineering, portable executables and what the hell the tools I'm using are and what they're used for. I am explaining this upfront because I have a feeling there will be a lot of "how do I make my tool look like yours?" questions, which I'm very sorry to say, I don't have patience for anymore. Having said that, let's continue (without the "how do you know this or that?" questions).
Followed that reference and saw these:
The reason I marked those LEAs is because they are functions. Hovering the mouse over the pointer in the [] brackets shows this:
So.. from here.. one could just.. you know.. SET A BREAKPOINT on that? This is the function, in CE-display format:
Code: Select all
ds.exe+3131B10 - 40 53 - push rbx
ds.exe+3131B12 - 48 83 EC 20 - sub rsp,20 { 32 }
ds.exe+3131B16 - 48 8B D9 - mov rbx,rcx
ds.exe+3131B19 - 48 85 C9 - test rcx,rcx
ds.exe+3131B1C - 74 34 - je ds.exe+3131B52
ds.exe+3131B1E - 48 8B 01 - mov rax,[rcx]
ds.exe+3131B21 - FF 90 D0000000 - call qword ptr [rax+000000D0]
ds.exe+3131B27 - 48 85 C0 - test rax,rax
ds.exe+3131B2A - 74 19 - je ds.exe+3131B45
ds.exe+3131B2C - 80 78 78 00 - cmp byte ptr [rax+78],00 { 0 }
ds.exe+3131B30 - 74 13 - je ds.exe+3131B45
ds.exe+3131B32 - 80 78 79 00 - cmp byte ptr [rax+79],00 { 0 }
ds.exe+3131B36 - 75 0D - jne ds.exe+3131B45
ds.exe+3131B38 - 48 8B 05 B1393804 - mov rax,[ds.exe+74B54F0] { (5ABE9CC4200) }
ds.exe+3131B3F - 83 78 24 01 - cmp dword ptr [rax+24],01 { 1 }
ds.exe+3131B43 - 7D 15 - jnl ds.exe+3131B5A
ds.exe+3131B45 - 48 8B 03 - mov rax,[rbx]
ds.exe+3131B48 - 48 8B CB - mov rcx,rbx
ds.exe+3131B4B - FF 50 70 - call qword ptr [rax+70]
ds.exe+3131B4E - 84 C0 - test al,al
ds.exe+3131B50 - 75 08 - jne ds.exe+3131B5A
ds.exe+3131B52 - 32 C0 - xor al,al
ds.exe+3131B54 - 48 83 C4 20 - add rsp,20 { 32 }
ds.exe+3131B58 - 5B - pop rbx
ds.exe+3131B59 - C3 - ret
ds.exe+3131B5A - B0 01 - mov al,01 { 1 }
ds.exe+3131B5C - 48 83 C4 20 - add rsp,20 { 32 }
ds.exe+3131B60 - 5B - pop rbx
ds.exe+3131B61 - C3 - ret
Code: Select all
ds.exe+3131B38 - 48 8B 05 B1393804 - mov rax,[ds.exe+74B54F0] { (5ABE9CC4200) }
ds.exe+3131B3F - 83 78 24 01 - cmp dword ptr [rax+24],01 { 1 }
Looking at it, kinda resembles the "IsGodMode" function:
Code: Select all
ds.exe+312BE90 - 40 53 - push rbx
ds.exe+312BE92 - 48 83 EC 20 - sub rsp,20 { 32 }
ds.exe+312BE96 - 48 8B 01 - mov rax,[rcx]
ds.exe+312BE99 - 48 8B D9 - mov rbx,rcx
ds.exe+312BE9C - FF 90 D0000000 - call qword ptr [rax+000000D0]
ds.exe+312BEA2 - 48 85 C0 - test rax,rax
ds.exe+312BEA5 - 74 21 - je ds.exe+312BEC8
ds.exe+312BEA7 - 80 78 78 00 - cmp byte ptr [rax+78],00 { 0 }
ds.exe+312BEAB - 74 1B - je ds.exe+312BEC8
ds.exe+312BEAD - 80 78 79 00 - cmp byte ptr [rax+79],00 { 0 }
ds.exe+312BEB1 - 75 15 - jne ds.exe+312BEC8
ds.exe+312BEB3 - 48 8B 05 36963804 - mov rax,[ds.exe+74B54F0] { (5ABE9CC4200) }
ds.exe+312BEBA - 83 78 24 02 - cmp dword ptr [rax+24],02 { 2 }
ds.exe+312BEBE - 75 08 - jne ds.exe+312BEC8
ds.exe+312BEC0 - B0 01 - mov al,01 { 1 }
ds.exe+312BEC2 - 48 83 C4 20 - add rsp,20 { 32 }
ds.exe+312BEC6 - 5B - pop rbx
ds.exe+312BEC7 - C3 - ret
ds.exe+312BEC8 - 48 8B CB - mov rcx,rbx
ds.exe+312BECB - 48 83 C4 20 - add rsp,20 { 32 }
ds.exe+312BECF - 5B - pop rbx
ds.exe+312BED0 - E9 3B6FFCFE - jmp ds.exe+20F2E10
Then I thought "isn't this maybe a GetPlayer function?" If all that's different between the two is that left-side block above, then the rest of the code above it should get the player.. or build up the player from some g_Game pointer. So then I remembered Anvil Engine where the member-functions table would contain (most likely) a function that leads to a name for the structure class or inheritance.
So.. I set a breakpoint at the prologue of the function above:
Code: Select all
ds.exe+312BE90 - 40 53 - push rbx <-- here
ds.exe+312BE92 - 48 83 EC 20 - sub rsp,20 { 32 }
ds.exe+312BE96 - 48 8B 01 - mov rax,[rcx]
ds.exe+312BE99 - 48 8B D9 - mov rbx,rcx
ds.exe+312BE9C - FF 90 D0000000 - call qword ptr [rax+000000D0]
ds.exe+312BEA2 - 48 85 C0 - test rax,rax
ds.exe+312BEA5 - 74 21 - je ds.exe+312BEC8
ds.exe+312BEA7 - 80 78 78 00 - cmp byte ptr [rax+78],00 { 0 }
ds.exe+312BEAB - 74 1B - je ds.exe+312BEC8
ds.exe+312BEAD - 80 78 79 00 - cmp byte ptr [rax+79],00 { 0 }
ds.exe+312BEB1 - 75 15 - jne ds.exe+312BEC8
ds.exe+312BEB3 - 48 8B 05 36963804 - mov rax,[ds.exe+74B54F0] { (5ABE9CC4200) }
ds.exe+312BEBA - 83 78 24 02 - cmp dword ptr [rax+24],02 { 2 }
ds.exe+312BEBE - 75 08 - jne ds.exe+312BEC8
ds.exe+312BEC0 - B0 01 - mov al,01 { 1 }
ds.exe+312BEC2 - 48 83 C4 20 - add rsp,20 { 32 }
ds.exe+312BEC6 - 5B - pop rbx
ds.exe+312BEC7 - C3 - ret
ds.exe+312BEC8 - 48 8B CB - mov rcx,rbx
ds.exe+312BECB - 48 83 C4 20 - add rsp,20 { 32 }
ds.exe+312BECF - 5B - pop rbx
ds.exe+312BED0 - E9 3B6FFCFE - jmp ds.exe+20F2E10
Now this is where it gets interesting. Follow that pointer in CE's dump and set view to 8-bytes hexadecimal:
Then enter the first pointer you see there with Space key (0000000143959500). You will see this table of pointers. This is called the member-functions table (or virtual functions table - "vftable"). One of the functions in here will more than likely lead to a NAME. And from testing I found that it's actually the first pointer. Follow me:
And then I started looking around in the memory space of 1444FFD30. And found that if I check the pointer at offset 0x38, I will find this:
So.. I hope this is a general rule for Decima: in any object-pointer the member-function at 0x0 holds the pointer to the named-class (or type). I've adapted the Assassin's Creed Origins script I posted a while ago for Anvil to Decima
Code: Select all
function GetName( input )
local p = readQword( input ) -- pointer to virtual functions table
local f = readQword( p + 0x00 ) -- function at 0x0
--[[ hopefully all of these 0x00 functions will look like this:
lea rax,[ds.exe+offset]
ret
]]
-- making sure there's no JMP to JMP
if readBytes( f + 0x00, 1 ) == 0xE9 then
f = f + readInteger( f + 0x1, true ) + 0x5
if readBytes( f + 0x00, 1 ) == 0xE9 then
f = f + readInteger( f + 0x1, true ) + 0x5
end
end
-- to make sure, let's check the ASM for a LEA RAX,[] (488D05xxxxxxxx)
if readBytes( f + 0x00, 1 ) == 0x48 then
if readBytes( f + 0x01, 1 ) == 0x8D then
if readBytes( f + 0x02, 1 ) == 0x05 then
local addr = f + readInteger( f + 0x3, true ) + 0x7
local strA = readString( readQword( addr + 0x38 ) )
local addr = readQword( readQword( addr + 0x58 ) )
local strB = readString( readQword( addr + 0x38 ) )
print( string.format( "Name:\t%s\r\nType:\t%s\r\n", strA, strB ) )
print( "" )
print( "* * *")
end
end
end
end
GetName( 0x0000011542E4BEF0 )
So this is the run-down of the function we looked at:
Code: Select all
ds.exe+312BE90 - 40 53 - push rbx
ds.exe+312BE92 - 48 83 EC 20 - sub rsp,20 { 32 }
ds.exe+312BE96 - 48 8B 01 - mov rax,[rcx] // DSPlayerEntity
ds.exe+312BE99 - 48 8B D9 - mov rbx,rcx
ds.exe+312BE9C - FF 90 D0000000 - call qword ptr [rax+000000D0]
ds.exe+312BEA2 - 48 85 C0 - test rax,rax // PlayerGame
ds.exe+312BEA5 - 74 21 - je ds.exe+312BEC8
ds.exe+312BEA7 - 80 78 78 00 - cmp byte ptr [rax+78],00 { 0 }
ds.exe+312BEAB - 74 1B - je ds.exe+312BEC8
ds.exe+312BEAD - 80 78 79 00 - cmp byte ptr [rax+79],00 { 0 }
ds.exe+312BEB1 - 75 15 - jne ds.exe+312BEC8
ds.exe+312BEB3 - 48 8B 05 36963804 - mov rax,[ds.exe+74B54F0] { (5ABE9CC4200) } // DebugSettings
ds.exe+312BEBA - 83 78 24 02 - cmp dword ptr [rax+24],02 { 2 }
ds.exe+312BEBE - 75 08 - jne ds.exe+312BEC8
ds.exe+312BEC0 - B0 01 - mov al,01 { 1 }
ds.exe+312BEC2 - 48 83 C4 20 - add rsp,20 { 32 }
ds.exe+312BEC6 - 5B - pop rbx
ds.exe+312BEC7 - C3 - ret
ds.exe+312BEC8 - 48 8B CB - mov rcx,rbx
ds.exe+312BECB - 48 83 C4 20 - add rsp,20 { 32 }
ds.exe+312BECF - 5B - pop rbx
ds.exe+312BED0 - E9 3B6FFCFE - jmp ds.exe+20F2E10
More, later
EDIT #1: It looks like not all of them have the GetName at 0x00. If it's a sub-entity, then the function is at 0x08. Follow it through the JMP then find the string at 0x38. An example for you to debug:
Code: Select all
ds.exe+2A5AB67 - 48 8B 87 E8440000 - mov rax,[rdi+000044E8] // DSPlayerEntity
ds.exe+2A5AB6E - 48 85 C0 - test rax,rax // DSPlayerInventoryComponent
ds.exe+2A5AB71 - 74 43 - je ds.exe+2A5ABB6
ds.exe+2A5AB73 - 48 8B 80 304C0000 - mov rax,[rax+00004C30] // DSBackpackEntity <-- this one is retrieved at 0x08 in [DSPlayerInventoryComponent + 0x00]
ds.exe+2A5AB7A - 33 C9 - xor ecx,ecx
ds.exe+2A5AB7C - 48 85 C0 - test rax,rax
ds.exe+2A5AB7F - 48 8D 50 E0 - lea rdx,[rax-20] // but the ptr reference is then fixed here, so script still works here, with RDX as your address
EDIT #2: This code handles the DSCollectibleLocator? Is there such a feature?
Code: Select all
ds.exe+2A5AB8C - 0FB6 92 B0170000 - movzx edx,byte ptr [rdx+000017B0]
ds.exe+2A5AB93 - 8B 8B 403E0300 - mov ecx,[rbx+00033E40] // rbx == DSCollectibleLocator
ds.exe+2A5AB99 - 83 EA 01 - sub edx,01 { 1 }
ds.exe+2A5AB9C - 74 0F - je ds.exe+2A5ABAD
ds.exe+2A5AB9E - 83 FA 07 - cmp edx,07 { 7 }
ds.exe+2A5ABA1 - 74 05 - je ds.exe+2A5ABA8
ds.exe+2A5ABA3 - 83 E1 CF - and ecx,-31 { 207 }
ds.exe+2A5ABA6 - EB 08 - jmp ds.exe+2A5ABB0
ds.exe+2A5ABA8 - 83 C9 10 - or ecx,10 { 16 }
ds.exe+2A5ABAB - EB 03 - jmp ds.exe+2A5ABB0
ds.exe+2A5ABAD - 83 C9 20 - or ecx,20 { 32 }
ds.exe+2A5ABB0 - 89 8B 403E0300 - mov [rbx+00033E40],ecx
EDIT #3: Found some nifty correlations in the game's code. So here goes:
Getting DSPlayerEntity:
Code: Select all
ds.exe+2A5AA30 - 48 8B 0D 89B6A304 - mov rcx,[ds.exe+74960C0] { (5ABE9DCBE50) }
ds.exe+2A5AA37 - 33 D2 - xor edx,edx
ds.exe+2A5AA39 - E8 02FC70FF - call ds.exe+216A640
ds.exe+2A5AA3E - 48 8B C8 - mov rcx,rax // DSPlayerEntity
Code: Select all
ds.exe+2A5AB67 - 48 8B 87 E8440000 - mov rax,[rdi+000044E8] // DSPlayerEntity
ds.exe+2A5AB6E - 48 85 C0 - test rax,rax // DSPlayerInventoryComponent
ds.exe+2A5AB71 - 74 43 - je ds.exe+2A5ABB6
ds.exe+2A5AB73 - 48 8B 80 304C0000 - mov rax,[rax+00004C30]
ds.exe+2A5AB7A - 33 C9 - xor ecx,ecx
ds.exe+2A5AB7C - 48 85 C0 - test rax,rax
ds.exe+2A5AB7F - 48 8D 50 E0 - lea rdx,[rax-20] // DSBackpackEntity
Code: Select all
ds.exe+24FD340 - 48 89 5C 24 18 - mov [rsp+18],rbx
ds.exe+24FD345 - 48 89 7C 24 20 - mov [rsp+20],rdi
ds.exe+24FD34A - 41 56 - push r14
ds.exe+24FD34C - 48 83 EC 20 - sub rsp,20 { 32 }
ds.exe+24FD350 - 48 8B F9 - mov rdi,rcx // DSPlayerSystem
ds.exe+24FD353 - E8 F86D1600 - call ds.exe+2664150 // it doesn't depend on the rcx above! > DSPlayerEntity
ds.exe+24FD358 - 48 8B D8 - mov rbx,rax
ds.exe+24FD35B - E8 206C1600 - call ds.exe+2663F80 // it doesn't depend on the rcx above! > CameraEntity
ds.exe+24FD360 - 4C 8B F0 - mov r14,rax
Code: Select all
ds.exe+2444D50 - 48 8B 81 20050000 - mov rax,[rcx+00000520] // DSPlayerEntity
ds.exe+2444D57 - 48 85 C0 - test rax,rax // DSPlayerState
ds.exe+2444D5A - 75 01 - jne ds.exe+2444D5D
ds.exe+2444D5C - C3 - ret
ds.exe+2444D5D - 0FB6 80 4D0B0000 - movzx eax,byte ptr [rax+00000B4D]
ds.exe+2444D64 - C3 - ret
Code: Select all
ds.exe+30325FE - 48 8B 05 A3CD4804 - mov rax,[ds.exe+74BF3A8] { (5ABF7097800) }
..
ds.exe+303262A - 48 8B 1D 0FDD4804 - mov rbx,[ds.exe+74C0340] { (5ABF7097800) }
Code: Select all
ds.exe+31319D0 - 33 D2 - xor edx,edx
ds.exe+31319D2 - 48 85 C9 - test rcx,rcx
ds.exe+31319D5 - 75 03 - jne ds.exe+31319DA
ds.exe+31319D7 - 8B C2 - mov eax,edx
ds.exe+31319D9 - C3 - ret
ds.exe+31319DA - 48 8B 89 30030000 - mov rcx,[rcx+00000330]
ds.exe+31319E1 - 48 85 C9 - test rcx,rcx
ds.exe+31319E4 - 48 8D 41 E0 - lea rax,[rcx-20]
ds.exe+31319E8 - 48 0F44 C2 - cmove rax,rdx
ds.exe+31319EC - C3 - ret
Code: Select all
ds.exe+3131A40 - 40 57 - push rdi
ds.exe+3131A42 - 48 83 EC 20 - sub rsp,20 { 32 }
ds.exe+3131A46 - 48 8B F9 - mov rdi,rcx
ds.exe+3131A49 - 48 85 C9 - test rcx,rcx
ds.exe+3131A4C - 74 53 - je ds.exe+3131AA1
ds.exe+3131A4E - 48 89 5C 24 30 - mov [rsp+30],rbx
ds.exe+3131A53 - 48 8D 99 88020000 - lea rbx,[rcx+00000288]
ds.exe+3131A5A - 48 8B CB - mov rcx,rbx
ds.exe+3131A5D - FF 15 7D0A6000 - call qword ptr [ds.exe+37324E0] { ->ntdll.dll+465E0 }
ds.exe+3131A63 - 85 C0 - test eax,eax
ds.exe+3131A65 - 75 09 - jne ds.exe+3131A70
ds.exe+3131A67 - 48 8B CB - mov rcx,rbx
ds.exe+3131A6A - FF 15 780A6000 - call qword ptr [ds.exe+37324E8] { ->ntdll.dll+1B380 }
ds.exe+3131A70 - 48 8B 07 - mov rax,[rdi]
ds.exe+3131A73 - 48 8B CF - mov rcx,rdi
ds.exe+3131A76 - FF 90 D0000000 - call qword ptr [rax+000000D0] // PlayerGame from DSPlayerEntity
ds.exe+3131A7C - 48 8B F8 - mov rdi,rax
ds.exe+3131A7F - 48 85 DB - test rbx,rbx
ds.exe+3131A82 - 74 09 - je ds.exe+3131A8D
ds.exe+3131A84 - 48 8B CB - mov rcx,rbx
ds.exe+3131A87 - FF 15 4B0A6000 - call qword ptr [ds.exe+37324D8] { ->ntdll.dll+3A980 }
ds.exe+3131A8D - 48 8B 5C 24 30 - mov rbx,[rsp+30]
ds.exe+3131A92 - 48 85 FF - test rdi,rdi
ds.exe+3131A95 - 74 0A - je ds.exe+3131AA1
ds.exe+3131A97 - 0FB6 47 78 - movzx eax,byte ptr [rdi+78] // reads this
ds.exe+3131A9B - 48 83 C4 20 - add rsp,20 { 32 }
ds.exe+3131A9F - 5F - pop rdi
ds.exe+3131AA0 - C3 - ret
ds.exe+3131AA1 - 32 C0 - xor al,al
ds.exe+3131AA3 - 48 83 C4 20 - add rsp,20 { 32 }
ds.exe+3131AA7 - 5F - pop rdi
ds.exe+3131AA8 - C3 - ret
Code: Select all
ds.exe+3131B10 - 40 53 - push rbx
ds.exe+3131B12 - 48 83 EC 20 - sub rsp,20 { 32 }
ds.exe+3131B16 - 48 8B D9 - mov rbx,rcx
ds.exe+3131B19 - 48 85 C9 - test rcx,rcx
ds.exe+3131B1C - 74 34 - je ds.exe+3131B52
ds.exe+3131B1E - 48 8B 01 - mov rax,[rcx]
ds.exe+3131B21 - FF 90 D0000000 - call qword ptr [rax+000000D0]
ds.exe+3131B27 - 48 85 C0 - test rax,rax
ds.exe+3131B2A - 74 19 - je ds.exe+3131B45
ds.exe+3131B2C - 80 78 78 00 - cmp byte ptr [rax+78],00 { 0 }
ds.exe+3131B30 - 74 13 - je ds.exe+3131B45
ds.exe+3131B32 - 80 78 79 00 - cmp byte ptr [rax+79],00 { 0 }
ds.exe+3131B36 - 75 0D - jne ds.exe+3131B45
ds.exe+3131B38 - 48 8B 05 B1393804 - mov rax,[ds.exe+74B54F0] { (5ABE9CC4200) }
ds.exe+3131B3F - 83 78 24 01 - cmp dword ptr [rax+24],01 { 1 }
ds.exe+3131B43 - 7D 15 - jnl ds.exe+3131B5A
ds.exe+3131B45 - 48 8B 03 - mov rax,[rbx]
ds.exe+3131B48 - 48 8B CB - mov rcx,rbx
ds.exe+3131B4B - FF 50 70 - call qword ptr [rax+70]
ds.exe+3131B4E - 84 C0 - test al,al
ds.exe+3131B50 - 75 08 - jne ds.exe+3131B5A
ds.exe+3131B52 - 32 C0 - xor al,al
ds.exe+3131B54 - 48 83 C4 20 - add rsp,20 { 32 }
ds.exe+3131B58 - 5B - pop rbx
ds.exe+3131B59 - C3 - ret
ds.exe+3131B5A - B0 01 - mov al,01 { 1 }
ds.exe+3131B5C - 48 83 C4 20 - add rsp,20 { 32 }
ds.exe+3131B60 - 5B - pop rbx
ds.exe+3131B61 - C3 - ret
Getting DSPlayerLifeComponent from DSPlayerEntity:
Code: Select all
ds.exe+2505A40 - 48 85 C9 - test rcx,rcx
ds.exe+2505A43 - 74 20 - je ds.exe+2505A65
ds.exe+2505A45 - 48 8B 81 00450000 - mov rax,[rcx+00004500] // rcx == DSPlayerEntity
ds.exe+2505A4C - 48 85 C0 - test rax,rax // result in rax is DSPlayerLifeComponent
ds.exe+2505A4F - 74 14 - je ds.exe+2505A65
ds.exe+2505A51 - C5FA1088 6C 010000 - vmovss xmm1,[rax+0000016C]
ds.exe+2505A59 - C5F857C0 - vxorps xmm0,xmm0,xmm0
ds.exe+2505A5D - C5F82FC8 - vcomiss xmm1,xmm0,xmm0
ds.exe+2505A61 - 0F97 C0 - seta al
ds.exe+2505A64 - C3 - ret
ds.exe+2505A65 - 32 C0 - xor al,al
ds.exe+2505A67 - C3 - ret
Code: Select all
ds.exe+25059F0 - 48 8B 89 E8440000 - mov rcx,[rcx+000044E8]
Code: Select all
ds.exe+32AAF80 - 48 8B 0D 39B11E04 - mov rcx,[ds.exe+74960C0] { (6DC9AB08C00) }
ds.exe+32AAF87 - 33 D2 - xor edx,edx
ds.exe+32AAF89 - E9 92F6EBFE - jmp ds.exe+216A620
..
..
ds.exe+216A620 - 48 63 C2 - movsxd rax,edx
ds.exe+216A623 - 48 03 C0 - add rax,rax
ds.exe+216A626 - 8B 44 C1 08 - mov eax,[rcx+rax*8+08]
ds.exe+216A62A - C3 - ret
Sun