Been looking high and low for the relationship between player and boat
Finally found what I was looking for
In
Black Flag (x86) and
Rogue (x64) the developer cheat functions are still built-in, even though you cannot access them through some visual menu. There's the so-called
God Mode feature that checks if your player is attached to a ship or not. Based on this, the option toggles player or ship god on, but not both at the same time.
So I started my analysis from those premises and found the logic by which the function works
Will use some advanced language in the below, so am really sorry if you cannot get all of it.
Let's start from
Rogue (since Odyssey is Anvil x64). There is a table I've written for this game, so look it up on the forums (if you cannot find it, PM me). Load the game till main menu, load the table, open ACC.exe process and enable the first script. Then head in-game and this will happen:
So
pContext in my case is
5C936A90. Let's see who this is via the Lua helper script (adjusted for ACC):
Code: Select all
function _readInteger( Input )
-- thanks, Pox!
local Value = readInteger( Input )
if Value < 0x80000000 then return Value
else return Value - 0x100000000 end
end
function GetName( input )
local addr = readQword( input )
addr = addr + 0x28 -- GetName
addr = readQword( addr )
if readBytes( addr, 1 ) == 0xE9 then
addr = addr + _readInteger( addr + 0x1 ) + 0x5
end
addr = addr + _readInteger( addr + 0x3 ) + 0x7
addr = readQword( addr )
print( string.format( "IStruct: 0x%X", input ) )
print( string.format( "IName: 0x%X", addr ) )
local str = readString( readQword( addr + 0x18 ) )
print( string.format( "ObjStr: %s", str ) )
addr = readInteger( addr + 0x24 )
print( string.format( "ObjHash: 0x%X", addr ) )
end
GetName( 0x5C936A90 )
So:
OK. Let's find where "God Mode" reference is:
Now let's head to the actual function:
And now the explanations when debugging the above function via the
Cheat Handler script in the table, while attached to the ship's wheel:
Now let's see what happens inside that "check if player is attached" function:
Out of this function, this happens next:
So the relationship kind of goes like this: GetWorld -> pWorld; pWorld + offset = pEntity; GetBhvAssassin( pEntity ) = pBhvAssassin; pBhvAssassin + offset = pHuman; then we use the Entity or EntityGroup stored in pHuman when attaching ship wheel to find our ship
Back to Odyssey, I found that what I initially thought it was called GetBhvAssassin is actually a generic function that returns a certain pointer/object if given the appropriate Entity parameter
That means I can use
GetBhvAssassin to get BhvBoatVehicle. And from that BoatVehicleActor and other stuff
God Mode and Invisibility can then be turned on for the
Adrestia as well just like for the player, using the member-functions
So:
GetWorld ->
Code: Select all
[ACOdyssey.exe+18332B0 - 48 8B 05 61CFE003 - mov rax,[ACOdyssey.exe+5640218] { [0AA016C0] }
ACOdyssey.exe+18332B7 - 48 85 C0 - test rax,rax
ACOdyssey.exe+18332BA - 74 08 - je ACOdyssey.exe+18332C4
ACOdyssey.exe+18332BC - 48 8B 80 D0000000 - mov rax,[rax+000000D0]
ACOdyssey.exe+18332C3 - C3 - ret
ACOdyssey.exe+18332C4 - C3 - ret
Returns 00000001B13CCEC0 on my end. Then this +
0x90 gives the pointer to Entity == 000000090DE54270.
With this in mind:
GetBhvAssassin ->
Code: Select all
ACOdyssey.exe+13D4B50 - E9 4BCEB30C - jmp ACOdyssey.exe+DF119A0
And you get 000000090DE64200. Then this +
0x30 gives the pointer to Human == 000000090DE6AA40.
And then Human +
0x348 holds the pointer to ship's EntityGroup pointer
You can then feed this to GetBhvAssassin and you'll get the BhvBoatVehicle pointer.
Code: Select all
IStruct: 0x91B7D99E0
IName: 0x1451D7310
ObjStr: BhvBoatVehicle
ObjHash: 0x6A709086
Inside this structure, offset 0x28 holds the pointer to BoatVehicleAI and 0x30 the pointer to BoatVehicleActor; these can be used in the various hooks you've seen in Cielos' table to get one-sided effects
And now for the grand finale; this is
TogglePlayerGodMode:
Code: Select all
ACOdyssey.exe+28BAF00 - 40 53 - push rbx
ACOdyssey.exe+28BAF02 - 48 83 EC 20 - sub rsp,20
ACOdyssey.exe+28BAF06 - 0FB6 DA - movzx ebx,dl
ACOdyssey.exe+28BAF09 - E8 F2D7B1FE - call GetCharacterAI
ACOdyssey.exe+28BAF0E - 0FB6 D3 - movzx edx,bl
ACOdyssey.exe+28BAF11 - 48 89 44 24 40 - mov [rsp+40],rax
ACOdyssey.exe+28BAF16 - 48 8D 4C 24 40 - lea rcx,[rsp+40]
ACOdyssey.exe+28BAF1B - E8 B00198FF - call ACOdyssey.exe+223B0D0
ACOdyssey.exe+28BAF20 - 48 83 C4 20 - add rsp,20 { 32 }
ACOdyssey.exe+28BAF24 - 5B - pop rbx
ACOdyssey.exe+28BAF25 - C3 - ret
Break there, fire Ikaros (with V), then V again to call it. CE will break (ToggleGod is called twice, with 0x1 and 0x0 parameters when Ikaros returns; ask Ubi why). Now if you swap your player's Entity pointer with the ship's EntityGroup pointer (in RCX; __this ptr) and trace the code, you'll end-up here:
Code: Select all
ACOdyssey.exe+2E0A6F0 - 40 53 - push rbx
ACOdyssey.exe+2E0A6F2 - 48 83 EC 20 - sub rsp,20
ACOdyssey.exe+2E0A6F6 - 48 8B 49 F8 - mov rcx,[rcx-08] // -0x8
ACOdyssey.exe+2E0A6FA - 0FB6 DA - movzx ebx,dl
ACOdyssey.exe+2E0A6FD - 48 8B 01 - mov rax,[rcx]
ACOdyssey.exe+2E0A700 - FF 90 30020000 - call qword ptr [rax+00000230] // [1]
ACOdyssey.exe+2E0A706 - 88 58 08 - mov [rax+08],bl // +0x8
ACOdyssey.exe+2E0A709 - 33 C0 - xor eax,eax
ACOdyssey.exe+2E0A70B - 48 83 C4 20 - add rsp,20
ACOdyssey.exe+2E0A70F - 5B - pop rbx
ACOdyssey.exe+2E0A710 - C3 - ret
[1]
ACOdyssey.exe+2DFB660 - 48 8D 81 88010000 - lea rax,[rcx+00000188] // +0x188
ACOdyssey.exe+2DFB667 - C3 - ret
So we have a sub 0x8, then a LEA with 0x188, then a +0x8. Result is offset 0x190. Remember this?
->
link to post. So now you have ToggleGodMode for ship as well, if given the proper __this pointer. For invisibility simply set BhvBoatVehicle +
0xA6 to 1.
Similarly you can turn your horse invincible/invisible (
BhvHorse is the name of the player's horse).
Lastly, check this out:
Code: Select all
ACOdyssey.exe+2E26907 - 48 8B 47 08 - mov rax,[rdi+08]
ACOdyssey.exe+2E2690B - 48 8B 48 18 - mov rcx,[rax+18]
ACOdyssey.exe+2E2690F - 48 85 C9 - test rcx,rcx
ACOdyssey.exe+2E26912 - 74 08 - je ACOdyssey.exe+2E2691C
ACOdyssey.exe+2E26914 - E8 07507DFE - call ACOdyssey.exe+15FB920 // rcx == pWorld
ACOdyssey.exe+100B9A90 - 53 - push rbx // GetNavalManager
ACOdyssey.exe+100B9A91 - 48 83 EC 20 - sub rsp,20 { 32 }
ACOdyssey.exe+100B9A95 - 48 B8 FFFFFFFFFFFFFF7F - mov rax,7FFFFFFFFFFFFFFF { -1 }
ACOdyssey.exe+100B9A9F - 48 89 CB - mov rbx,rcx
ACOdyssey.exe+100B9AA2 - 48 39 81 00190000 - cmp [rcx+00001900],rax
ACOdyssey.exe+100B9AA9 - 75 4D - jne ACOdyssey.exe+100B9AF8
ACOdyssey.exe+100B9AAB - 65 48 8B 04 25 58000000 - mov rax,gs:[00000058] { 88 }
ACOdyssey.exe+100B9AB4 - 8B 15 8ECBD4F5 - mov edx,[ACOdyssey.exe+5E06648] { [00000000] }
ACOdyssey.exe+100B9ABA - B9 F42C0000 - mov ecx,00002CF4 { 11508 }
ACOdyssey.exe+100B9ABF - 48 8B 14 D0 - mov rdx,[rax+rdx*8]
ACOdyssey.exe+100B9AC3 - 8B 04 11 - mov eax,[rcx+rdx]
ACOdyssey.exe+100B9AC6 - 39 05 60E757F5 - cmp [ACOdyssey.exe+563822C],eax { [800000AE] }
ACOdyssey.exe+100B9ACC - 7F 37 - jg ACOdyssey.exe+100B9B05
ACOdyssey.exe+100B9ACE - 8B 15 54E757F5 - mov edx,[ACOdyssey.exe+5638228] { [AC23FDEE] }
ACOdyssey.exe+100B9AD4 - 48 8D 0D 451328F5 - lea rcx,[ACOdyssey.exe+533AE20] { [03CAFF43] }
ACOdyssey.exe+100B9ADB - E8 407090F0 - call ACOdyssey.exe+9C0B20
ACOdyssey.exe+100B9AE0 - 48 89 C2 - mov rdx,rax
ACOdyssey.exe+100B9AE3 - 48 89 D9 - mov rcx,rbx
ACOdyssey.exe+100B9AE6 - E8 356054F1 - call ACOdyssey.exe+15FFB20
ACOdyssey.exe+100B9AEB - 48 89 83 00190000 - mov [rbx+00001900],rax
ACOdyssey.exe+100B9AF2 - 48 83 C4 20 - add rsp,20 { 32 }
ACOdyssey.exe+100B9AF6 - 5B - pop rbx
ACOdyssey.exe+100B9AF7 - C3 - ret
ACOdyssey.exe+100B9AF8 - 48 8B 81 00190000 - mov rax,[rcx+00001900]
ACOdyssey.exe+100B9AFF - 48 83 C4 20 - add rsp,20 { 32 }
ACOdyssey.exe+100B9B03 - 5B - pop rbx
ACOdyssey.exe+100B9B04 - C3 - ret
ACOdyssey.exe+100B9B05 - 48 8D 0D 20E757F5 - lea rcx,[ACOdyssey.exe+563822C] { [800000AE] }
ACOdyssey.exe+100B9B0C - E8 4F90A2F3 - call ACOdyssey.exe+3AE2B60
ACOdyssey.exe+100B9B11 - 83 3D 14E757F5 FF - cmp dword ptr [ACOdyssey.exe+563822C],-01 { 255 }
ACOdyssey.exe+100B9B18 - 75 B4 - jne ACOdyssey.exe+100B9ACE
ACOdyssey.exe+100B9B1A - 48 8D 0D BF15DDF3 - lea rcx,[ACOdyssey.exe+3E8B0E0] { ["NavalManager"] }
ACOdyssey.exe+100B9B21 - E8 1A8190F0 - call ACOdyssey.exe+9C1C40
ACOdyssey.exe+100B9B26 - 48 8D 0D FFE657F5 - lea rcx,[ACOdyssey.exe+563822C] { [800000AE] }
ACOdyssey.exe+100B9B2D - 89 05 F5E657F5 - mov [ACOdyssey.exe+5638228],eax { [AC23FDEE] }
ACOdyssey.exe+100B9B33 - E8 C88FA2F3 - call ACOdyssey.exe+3AE2B00
ACOdyssey.exe+100B9B38 - EB 94 - jmp ACOdyssey.exe+100B9ACE
NavalManager_loop
ACOdyssey.exe+2F3AEB7 - 41 8B AE 80000000 - mov ebp,[r14+00000080]
ACOdyssey.exe+2F3AEBE - 45 33 FF - xor r15d,r15d
ACOdyssey.exe+2F3AEC1 - 49 8B 7E 78 - mov rdi,[r14+78]
ACOdyssey.exe+2F3AEC5 - C1 ED 11 - shr ebp,11 { 17 }
ACOdyssey.exe+2F3AEC8 - C1 E5 03 - shl ebp,03 { 3 }
ACOdyssey.exe+2F3AECB - 48 03 EF - add rbp,rdi
ACOdyssey.exe+2F3AECE - 48 3B FD - cmp rdi,rbp
ACOdyssey.exe+2F3AED1 - 0F84 85000000 - je ACOdyssey.exe+2F3AF5C
You can find all ship EntitGroup pointers via the above iterator, stored in NavalManager structure
Then from those, calling GetBhvAssassin/GetBhvBoarVehicle, you get the actual ship structures. Check the member-functions for read/write health functions
Then you can easily write an
instant kill iterator that sinks all surrounding ships
BR,
Sun