Hello folks.
This post aims to collect a ton shit of information on the engine this game runs on (note Death of the Outsider uses the same engine). I've been doing some comparison lately between Doom 2016's engine (id Tech 6) and this one, in hopes of identifying similarities and approaches to enable console or execute console commands/CVars. I was successful to some extent, whereas now I can execute stuff (hope I can also dump the console, even though it seems disabled or I yet don't know how to bring it down; activateConsole 0.5 doesn't seem to work).
You will find a lot of gibberish or information that doesn't make any sense to you, but you can always leave feedback. Remember, this is a collection post, not a tutorial or "how to" (even though at times you may find directions, code samples for breaking, etc.).
I'm currently playing on the latest Steam build to date:
- top bar says Dishonored 2 using VoidEngine v1.77.7.0
- Dishonored2.exe is 114 MB in size (I have it dumped for historical/analysis purposes or AOB scanning)
Tools I use:
- Cheat Engine to hook bits and pieces
- x64dbg attached to the game process after launch
Let the spam begin!
• console Exec function starts here:
• and this is the data of RCX pointer in dump:
• considering there's no actual console to execute something in, I have to emulate the console text buffer with the appropriate information:
p+0x18: pointer to string (leads to 0x28) with trailing
p+0x20: size of string + 1
p+0x28: is where our text will need filling-in
• Cheat Engine quick script to hook the spot and easily edit string and size:
Code: Select all
[ENABLE]
alloc( Hook, 0x1000, Dishonored2.exe )
label( back )
label( pBase )
registersymbol( pBase )
Hook:
mov [pBase],rcx
mov eax,[rcx+20]
mov r14,rcx
jmp back
pBase:
dq 0
Dishonored2.exe+41877EF:
jmp Hook
db 90
back:
[DISABLE]
Dishonored2.exe+41877EF:
db 8B 41 20 49 89 CE
unregistersymbol( pBase )
dealloc( Hook )
{
// ORIGINAL CODE - INJECTION POINT: "Dishonored2.exe"+41877EF
"Dishonored2.exe"+41877DA: CC - int 3
"Dishonored2.exe"+41877DB: CC - int 3
"Dishonored2.exe"+41877DC: CC - int 3
"Dishonored2.exe"+41877DD: CC - int 3
"Dishonored2.exe"+41877DE: CC - int 3
"Dishonored2.exe"+41877DF: CC - int 3
"Dishonored2.exe"+41877E0: 41 56 - push r14
"Dishonored2.exe"+41877E2: B8 40 14 00 00 - mov eax,00001440
"Dishonored2.exe"+41877E7: E8 D4 25 4D 01 - call Dishonored2.exe+5659DC0
"Dishonored2.exe"+41877EC: 48 29 C4 - sub rsp,rax
// ---------- INJECTING HERE ----------
"Dishonored2.exe"+41877EF: 8B 41 20 - mov eax,[rcx+20]
"Dishonored2.exe"+41877F2: 49 89 CE - mov r14,rcx
// ---------- DONE INJECTING ----------
"Dishonored2.exe"+41877F5: C1 E0 03 - shl eax,03
"Dishonored2.exe"+41877F8: 85 C0 - test eax,eax
"Dishonored2.exe"+41877FA: 0F 8E E2 02 00 00 - jng Dishonored2.exe+4187AE2
"Dishonored2.exe"+4187800: CC - int 3
"Dishonored2.exe"+4187801: 89 9C 24 50 14 00 00 - mov [rsp+00001450],ebx
"Dishonored2.exe"+4187808: 48 89 AC 24 58 14 00 00 - mov [rsp+00001458],rbp
"Dishonored2.exe"+4187810: 48 89 B4 24 60 14 00 00 - mov [rsp+00001460],rsi
"Dishonored2.exe"+4187818: 48 89 BC 24 68 14 00 00 - mov [rsp+00001468],rdi
"Dishonored2.exe"+4187820: 31 ED - xor ebp,ebp
"Dishonored2.exe"+4187822: 41 39 6E 10 - cmp [r14+10],ebp
}
• note size of my string + 1 is 0x1B:
• once set, x64dbg breaks and I can continue the trace; parsing's finished here:
• execution continues with calling a member function, at this location:
• inside this function, things are pretty much self-explanatory after the analysis:
• the engine loops a list of available console commands to find the one I've sent for processing; the list of all available ones:
Spoiler
testStatsEnd
testStatsBegin
playEvents
recordEvents
deleteGenerated
leaveGameToMainMenu
leaveGame
disconnect
restartmaphere
restartmap
map
campaign
stripStrings
reloadTextLanguage
vid_restart
find
writeConfig
exit
quit
reloadModels
listModels
rp
testImage
colorGradingShot
envshot
rawscreenshot
screenshot
cubeshot
buddha
god
resetViewParms
clearDebugPoints
popDebugPoint
pushDebugPoint
randomTest
entityListenerStats
debugEntityByName
prevAI
nextAI
nextActiveAI
gameError
clearLights
popLight
listLines
blinkline
removeline
addarrow
addline
reloadEntity
killEntity
damage
trigger
teleport
setviewpos
getviewpos
where
reexportDecls
touchDecl
unregisterDeclFolder
listDecls
writeImage
listImages
copy
path
dirtree
dir
makeDeclTree
arkIggySubtitleLevel
arkSwitchToBindingSet
arkSwitchToDefaultBindingSet
arkBind
arkBindSecondary
arkUnbind
arkUnbindCurrentSet
arkUnbindKeyboard
arkUnbindMouse
arkUnbindJoypad
arkUnbindAll
arkListBindings
idStudio
writeEntitiesFile
writeEntitiesFileWithError
clear
print
history
clearHistory
con_watch
con_unwatch
toggle
cvarAdd
cvarRandom
addWrap
addClamp
cvarMultiply
reset
listCvars
cvar_restart
cvar_resetcheats
cvarsModified
scheduleVideo
ark_settings_save
RestartFromMemoryCheckpoint
RestartFromCheckpoint
SaveGame
LoadGame
TestFx
Ansel_PlayerVisible
wait
parse
echo
vstr
exec
listCmds
testStatsBegin
playEvents
recordEvents
deleteGenerated
leaveGameToMainMenu
leaveGame
disconnect
restartmaphere
restartmap
map
campaign
stripStrings
reloadTextLanguage
vid_restart
find
writeConfig
exit
quit
reloadModels
listModels
rp
testImage
colorGradingShot
envshot
rawscreenshot
screenshot
cubeshot
buddha
god
resetViewParms
clearDebugPoints
popDebugPoint
pushDebugPoint
randomTest
entityListenerStats
debugEntityByName
prevAI
nextAI
nextActiveAI
gameError
clearLights
popLight
listLines
blinkline
removeline
addarrow
addline
reloadEntity
killEntity
damage
trigger
teleport
setviewpos
getviewpos
where
reexportDecls
touchDecl
unregisterDeclFolder
listDecls
writeImage
listImages
copy
path
dirtree
dir
makeDeclTree
arkIggySubtitleLevel
arkSwitchToBindingSet
arkSwitchToDefaultBindingSet
arkBind
arkBindSecondary
arkUnbind
arkUnbindCurrentSet
arkUnbindKeyboard
arkUnbindMouse
arkUnbindJoypad
arkUnbindAll
arkListBindings
idStudio
writeEntitiesFile
writeEntitiesFileWithError
clear
history
clearHistory
con_watch
con_unwatch
toggle
cvarAdd
cvarRandom
addWrap
addClamp
cvarMultiply
reset
listCvars
cvar_restart
cvar_resetcheats
cvarsModified
scheduleVideo
ark_settings_save
RestartFromMemoryCheckpoint
RestartFromCheckpoint
SaveGame
LoadGame
TestFx
Ansel_PlayerVisible
wait
parse
echo
vstr
exec
listCmds
• note how in the loop, RDI+8 holds the pointer to the string, while RDI+10 contains the actual function address to be executed, passed on later to RAX for JMP RAX
• else, if it's a CVar, the CALL QWORD PTR [RAX+0x78] will do the job of finding it and processing it for you
That's as much as I have for now.
BR,
Sun