Been studying this for a while; finally figured it out. The table's been updated with a script that lets you run all of these:
Code: Select all
ply.vehicle.burn -- sets on fire the vehicle you're in
ply.input.disable -- does nothing
ply.input.disable_except_look -- does nothing
ply.input.enable -- does nothing
ply.jesus.enable -- enables god mode
ply.jesus.disable -- disables god mode
ply.jesus.toggle -- toggles god mode
ply.ragdoll_and_stumble.enable
ply.ragdoll_and_stumble.disable
ply.jumping.enable -- does nothing
ply.jumping.disable -- does nothing
ply.grapple.enable -- does nothing
ply.grapple.disable -- does nothing
ply.weapon_pickup.enable -- does nothing
ply.weapon_pickup.disable -- does nothing
ply.pda.enable -- does nothing
ply.pda.disable -- does nothing
ply.ammo.givemax -- replenishes all ammo to max (creates an invisible beacon)
ply.explosives.remove_all -- does nothing
equipweapon -- equips given weapon
ply.pause -- player vanishes; camera locked
ply.unpause -- restores above
ply.unlimitedammo.enable -- enables unlimited magazine ammo (infinite symbol @ bottom-left)
ply.unlimitedammo.disable -- disables the above
ply.unequipweapon -- unequips current weapon
ply.health.takedamage -- damages player
ply.health.takedamage.track_event
ply.check_achievement.sanctuary -- does nothing
ply.check_achievement.i_can_show_you_the_world -- enables this achievement (reveal map?)
ply.award_achievement.i_can_see_my_house_from_here -- enables this achievement (reveal map?)
moon_gravity_on -- enables weird pull gravity (happens on jump)
moon_gravity_off -- disables the above
Note not all have an effect; also, engine might disable them when you move from game world to main menu and backwards.. or if a map loads or something.
BR,
Sun
[ 12 Dec 2018 - Update #1 ]
Added the table:
[ 11 Dec 2018 - First Release ]
Hello folks.
Started a bit of digging of my own in this engine; can see it's not as modular as Anvil or Dunia, but resembles something I've seen in Shadow of the Tomb Raider (or any in the series) or Dark Souls 3. Am guessing engine modules can be shared or SDK adapted from one title to another, under the same umbrella, the difference being the graphics engine; which pretty much ends-up naming the whole product. Like APEX engine here.
I'm using the CPY version for the purpose of this topic; alongside: Cheat Engine and x64dbg.
So, ran the game, dumped it to disk and found all string references. Checking through I found these:
Choosing one of them and following it in disassembler led me to find even more:
Heard someone was looking for gravity
Alright. All nice and dandy looking, but still how to execute such commands or how to even open up a console in-game? Well.. first things first. I initially checked the function with all those command references; and by "checked" I mean setting a breakpoint at its prologue and testing if x64dbg breaks on it in various moments of time. First moment in time: launching the game through the debugger. Why like that.. because I had a feeling the module gets initialized at run-time. And yes, x64dbg did break and I could trace the code. Another moment in time when the function might break is when you enter or leave the game world. But that didn't work out. So.. it only happens when you run the game from x64dbg.
I then wanted to know who CALLs this function; so I found who:
The reason I already have some breaks in there is I've progressed more with the analysis that I now know which spots to break on The logic of the above function is this: past the "MOV ECX, 0xB28" instruction + the CALL a block of memory is allocated/initialized. This will represent our base pointer for the next part In my case, the address is this:
base_ptr == 0x00000000539A1400
With this as parameter, we then enter our known function:
Code: Select all
00000001409B0014 | BA 01000000 | MOV EDX,1 |
00000001409B0019 | 48:8BC8 | MOV RCX,RAX |
00000001409B001C | E8 2FEBFCFF | CALL justcause4.14097EB50 | <-- call to your function
Notice my RDI == base_ptr. Then we see this block:
Code: Select all
000000014097F434 | 48:8D97 B8090000 | LEA RDX,QWORD PTR DS:[RDI+9B8] |
000000014097F43B | 48:8BCF | MOV RCX,RDI |
000000014097F43E | E8 7D0E70FF | CALL justcause4.1400802C0 |
000000014097F443 | 41:B1 01 | MOV R9B,1 |
000000014097F446 | 41:B8 FF000000 | MOV R8D,FF |
000000014097F44C | 48:8D15 B54B2901 | LEA RDX,QWORD PTR DS:[141C14008] | 0000000141C14008:"ply.input.disable"
000000014097F453 | 48:8D8F B8090000 | LEA RCX,QWORD PTR DS:[RDI+9B8] |
000000014097F45A | E8 11A470FF | CALL justcause4.140089870 |
Once we get past the CALL at 14097F45A, we see this:
Well, that to me looks like a pointer. If we adjust the display to show addresses, we see this:
Execute the function till the RET and then check your block:
The reason I told you to do that is the fact that I dumped the list of correspondences, string to offset:
Code: Select all
+9B8 -> 0000000141C14008:"ply.input.disable"
+9C0 -> 0000000141C14020:"ply.input.disable_except_look"
+9B0 -> 0000000141C14040:"ply.input.enable"
+9C8 -> 0000000141C14058:"ply.jesus.enable"
+9D0 -> 0000000141C14070:"ply.jesus.disable"
+9D8 -> 0000000141C14088:"ply.jesus.toggle"
+9E0 -> 0000000141C140A0:"ply.ragdoll_and_stumble.enable"
+9E8 -> 0000000141C140C0:"ply.ragdoll_and_stumble.disable"
+9F0 -> 0000000141BAD320:"ply.jumping.enable"
+9F8 -> 0000000141C140E0:"ply.jumping.disable"
+A00 -> 0000000141C140F8:"ply.grapple.enable"
+A08 -> 0000000141C14110:"ply.grapple.disable"
+A10 -> 0000000141C14128:"ply.weapon_pickup.enable"
+A18 -> 0000000141C14148:"ply.weapon_pickup.disable"
+A20 -> 0000000141C14168:"ply.pda.enable"
+A28 -> 0000000141C14178:"ply.pda.disable"
+AE0 -> 0000000141C14188:"ply.inventory.give"
+A30 -> 0000000141C141A0:"equipweapon"
+A38 -> 0000000141C141B0:"unequipweapon"
+A40 -> 0000000141C141C0:"ply.unequipweapon"
+A48 -> 0000000141C141D8:"ply.vehicle.burn"
+A50 -> 0000000141C141F0:"ply.pause"
+A58 -> 0000000141C14200:"ply.unpause"
+A60 -> 0000000141C14210:"freeze_frames"
+AC0 -> 0000000141C14220:"ply.unlimitedammo.enable"
+AC8 -> 0000000141C14240:"ply.unlimitedammo.disable"
+AE0 -> 0000000141C14260:"ply.health.takedamage"
+AD8 -> 0000000141C14278:"ply.health.takedamage.track_event"
+A78 -> 0000000141C08DE0:"ply.ammo.givemax"
+AF0 -> 0000000141C142A0:"ply.check_achievement.sanctuary"
+AF8 -> 0000000141C142C0:"ply.check_achievement.i_can_show_you_the_world"
+B00 -> 0000000141C142F0:"ply.award_achievement.i_can_see_my_house_from_here"
+A70 -> 0000000141C14328:"ply.explosives.remove_all"
+B08 -> 0000000141C14350:"moon_gravity_on"
+B10 -> 0000000141C14360:"moon_gravity_off"
- you are at main menu and hit CONTINUE or NEW GAME
- you're in-game and hit Escape, then RETURN TO MAIN MENU
Notice those offsets? Also notice those TEST AL,AL instructions? First test of mine was to see when the function breaks, which of those CALLs returns 1 (TRUE). That will tell you which command got executed. So.. when you hit CONTINUE at main menu, this location will return TRUE:
Code: Select all
00000001409A9543 | 48:8D93 F0090000 | LEA RDX,QWORD PTR DS:[RBX+9F0] |
00000001409A954A | 48:8BCF | MOV RCX,RDI |
00000001409A954D | E8 3EAD6DFF | CALL justcause4.140084290 |
00000001409A9552 | 84C0 | TEST AL,AL |
00000001409A9554 | 0F85 490B0000 | JNE justcause4.1409AA0A3 |
Now, when you hit RETURN TO MAIN MENU and the LOADING animation starts, function breaks and this one returns TRUE:
Code: Select all
00000001409A947E | 48:8D93 D0090000 | LEA RDX,QWORD PTR DS:[RBX+9D0] |
00000001409A9485 | 48:8BCF | MOV RCX,RDI |
00000001409A9488 | E8 03AE6DFF | CALL justcause4.140084290 |
00000001409A948D | 84C0 | TEST AL,AL |
00000001409A948F | 74 1D | JE justcause4.1409A94AE |
00000001409A9491 | 80BB 28030000 00 | CMP BYTE PTR DS:[RBX+328],0 |
00000001409A9498 | 0F84 050C0000 | JE justcause4.1409AA0A3 |
00000001409A949E | 33D2 | XOR EDX,EDX |
00000001409A94A0 | 48:8B4B 68 | MOV RCX,QWORD PTR DS:[RBX+68] |
00000001409A94A4 | E8 6703B3FF | CALL justcause4.1404D9810 |
00000001409A94A9 | E9 F50B0000 | JMP justcause4.1409AA0A3 |
So.. what if I force the code flow to run something else when I hit CONTINUE? Like "ply.unlimitedammo.enable". Let's see what happens.
Code: Select all
00000001409A9552 | 84C0 | TEST AL,AL | <-- break here
00000001409A9554 | 0F85 490B0000 | JNE justcause4.1409AA0A3 |
Code: Select all
00000001409A9CEA | 48:8D93 C00A0000 | LEA RDX,QWORD PTR DS:[RBX+AC0] |
00000001409A9CF1 | 48:8BCF | MOV RCX,RDI |
00000001409A9CF4 | E8 97A56DFF | CALL justcause4.140084290 |
00000001409A9CF9 | 84C0 | TEST AL,AL | <-- break here
00000001409A9CFB | 74 10 | JE justcause4.1409A9D0D |
00000001409A9CFD | B2 01 | MOV DL,1 |
00000001409A9CFF | 48:8B4B 68 | MOV RCX,QWORD PTR DS:[RBX+68] |
00000001409A9D03 | E8 A843B3FF | CALL justcause4.1404DE0B0 |
00000001409A9D08 | E9 96030000 | JMP justcause4.1409AA0A3 |
My RCX is 0x00000000AC880000. Follow that in dump, then enter the first pointer you see there; the one at 0x0 offset. Once you do, you will see a list of addresses, then this:
So you're looking at a CCharacter-type structure. Some might call it a class
Let's see what happens in our sub_1404DE0B0:
Code: Select all
00000001404DE0B0 | E9 BB686108 | JMP justcause4.148AF4970 |
..
0000000148AF4970 | 8891 00220000 | MOV BYTE PTR DS:[RCX+2200],DL |
0000000148AF4976 | C3 | RET |
That concludes the proof of concept. Wanna do more? Follow what I wrote above and you'll be fine. Or wait for my table.
BR,
Sun
P.S.: There are no results I could find on google regarding the Just Cause series and cheats/console commands (e.g.: search for "ply.unlimitedammo.enable" or variations; see if you find it). Aside from trainers, there's no research I could find being posted someplace. So.. I get to say "you know where you read it first". And save the date I'll keep an eye out for any tables/trainers using the stuff I detail here, surfacing after this article's been aired.
How to use this cheat table?
- Install Cheat Engine
- Double-click the .CT file in order to open it.
- Click the PC icon in Cheat Engine in order to select the game process.
- Keep the list.
- Activate the trainer options by checking boxes or setting values from 0 to 1