Game Vendor: Steam
Game Version: v24037 [ see top-right at main menu ]
Game Process: BC.exe
Been a while since I posted a table, so I decided to go big I've been playing this game for 1-2 weeks now, quite entertaining, with only Debug Keys enabled. Aside from that, there's a bunch of DEVELOPER COMMANDS which I found worth investigating. The internet doesn't contain a lot of information on them, neither the 1-2 topics here on FRF. Some are simplistic and to the point, some require some extensive knit-picking and diving into Unity's .assets and .dlls
Let it be known upfront that this topic is not about the usual options you've seen in trainers or tables. If you're looking for that, please find a different topic/table/trainer to suit your needs. You WON'T FIND IT HERE. So don't go "not working, please update", as you'll be ignored. The purpose of this topic is to have a go at some aspects in a different way and maybe you'd learn a few things in the process. The reason I've decided to do this -- to answer several questions:
a) Why don't I train Unity games? R: Because, as you will see, Unity games are SIMPLE to train. There's no freakin' magic behind it once you are equipped with several tools that help you in the process.
b) How did you figure out this and that? R: Well, common sense and mostly googling. Plus applied human logic to get from point A to point B to point C and so on. Again, it's not something only knowledgeable people can do: it's something YOU too can do! So don't go "yeah, but you know this shit, you have the knowledge".
Since I foresee some crappy replies, here's a dedicated paragraph: 1) "but this game is old, nub": It is. Although I don't like Unity, I liked the game's logic/mechanics. Reminds me of Disciples series. Plus what you're about to read isn't on any forum. 2) "while you train old games, I trained 293872198 AAA+ games": I don't train games. I don't do it for a living. So take that logic someplace else. You know who you are.
Alright, let's get to it.
---------------------------------------------
DEBUG KEYS / DEVELOPER CONSOLE
---------------------------------------------
As Csimbi mentioned here, you can enable the Debug Keys and Developer Console in this game. Before finding the various topics on FRF, I went my usual way, which I will explain below. The tools you'll need to deal with Unity games are many in numbers. I prefer Telerik JustDecompile which you can download [Link]. That, plus Cheat Engine 7.1+. We'll be using some other tool a bit later, but that's just because of a certain necessity. We'll talk about it when I get to it.
Having JustDecompile open, you'll want to open up Assembly-CSharp.dll, which can be found in the game folder (e.g.: F:\SteamLibrary\steamapps\common\Battle Chasers Nightwar\BC_Data\Managed). This will be your usual approach with ANY Unity game. Open up the Assebly-CSharp.dll and start searching for shit Which is what I did as well; here's an example:
If you look for 'debugkeys', you will see the following:
You can see on the first column a function name: HandleDebugKeys. On the 3rd column a method called Update(). Inspecting the first HandleDebugKeys (Method Call) in the list -- the 2nd entry, bottom-to-top, in the image above -- we see this:
So:
Code: Select all
private void Update()
{
this.HandleDebugKeys();
foreach (CombatEventParams currentCombatEvent in this.currentCombatEvents)
{
currentCombatEvent.enabled = !this.suppressCombatEvents;
}
if (this.state == BattleManager.State.Active)
{
this.Update_Active();
}
else if (this.state == BattleManager.State.Complete || this.state == BattleManager.State.WaveTransition)
{
this.Update_Inactive();
}
}
From here, let's see what HandleDebugKeys does:
Code: Select all
private void HandleDebugKeys()
{
if (GameKey.GetDebugButtonDown(this.playerInput, 28))
{
CombatFighter fighterUnderCursor = this.GetFighterUnderCursor();
if (fighterUnderCursor != null && fighterUnderCursor.IsAlive() && !fighterUnderCursor.IsPerformingAction())
{
fighterUnderCursor.GotHit(null, fighterUnderCursor.stats.health * 2f, CombatFighter.ImpactType.Heavy, false, false, true, CombatFighter.DamageType.Physical, null);
}
}
if (GameKey.GetDebugButtonDown(this.playerInput, 30))
{
CombatFighter combatFighter = this.GetFighterUnderCursor();
if (combatFighter != null && combatFighter.IsAlive() && !combatFighter.IsPerformingAction())
{
float single = Mathf.Min(combatFighter.stats.maxHealth * 0.15f, combatFighter.stats.health * 0.5f);
single = Mathf.Max(single, 1f);
combatFighter.GotHit(null, single, CombatFighter.ImpactType.Heavy, false, false, true, CombatFighter.DamageType.Physical, null);
}
}
if (GameKey.GetDebugButtonDown(this.playerInput, 31))
{
CombatFighter fighterUnderCursor1 = this.GetFighterUnderCursor();
if (fighterUnderCursor1 != null && fighterUnderCursor1.IsAlive() && !fighterUnderCursor1.IsPerformingAction())
{
float single1 = Mathf.Min(fighterUnderCursor1.stats.maxHealth * 0.05f, fighterUnderCursor1.stats.health * 0.5f);
single1 = Mathf.Max(single1, 1f);
fighterUnderCursor1.GotHit(null, single1, CombatFighter.ImpactType.Light, false, false, true, CombatFighter.DamageType.Physical, null);
}
}
if (GameKey.GetDebugButtonDown(this.playerInput, 33))
{
this.ToggleGodMode();
}
}
How do I know I have Debug Keys active? I don't. For now. But I can do a further dive into the function that runs when you press the keys I mentioned above. That function: GetDebugButtonDown. Goin in, we see this:
Code: Select all
public static bool GetDebugButtonDown(Player playerInput, int actionId)
{
if (!ReInput.isReady || playerInput == null || !Singleton<GlobalVars>.Instance.debugKeys)
{
return false;
}
return (!playerInput.GetButtonDown(actionId) ? false : playerInput.GetButton(4));
}
The reason I'm indicating stuff with arrows and marking regions in red rectangles is so you pay attention to the details so you'd know how to get to these spots when you enable Mono in Cheat Engine. From the function above we see that GetDebugButtonDown checks for several things before actually allowing normal execution. One of the things it checks for is Singleton<GlobalVars>.Instance.debugKeys. Which is what we want to intercept and patch.
Alright. So far so good, let's get Cheat Engine open. Target the game, then run Mono > Activate mono features from main CE window:
If you remember all those arrows in the previous images and red rectangles:
So we want to check BC:GameKey:GetDebugButtonDown function. Open Memory Viewer, then Ctrl+G and type in or paste the name of the function I just mentioned. Hit OK and you're there:
Now if we cross-reference the above with our C++ code:
Code: Select all
public static bool GetDebugButtonDown(Player playerInput, int actionId)
{
if (!ReInput.isReady || playerInput == null || !Singleton<GlobalVars>.Instance.debugKeys)
{
return false;
}
return (!playerInput.GetButtonDown(actionId) ? false : playerInput.GetButton(4));
}
The ASM equivalent of each of the components in that function.
So my enablement script looks like this:
Code: Select all
{$STRICT}
{$lua}
if syntaxcheck then return end
function string.fromhex( s )
return ( s:gsub( '..', function ( cc )
return string.char( tonumber( cc, 16 ) )
end ) )
end
function aobScanSmall( aob, s, e )
local i = byteTableToString( readBytes( s, e, true ) ):find( string.fromhex( aob ), 1, true )
if i == nil then i = 1 end
return ( s + i - 1 )
end
local _script = [[[ENABLE]
label( hk_GetDebugButtonDown_o )
registersymbol( hk_GetDebugButtonDown_o )
label( pInstance )
registersymbol( pInstance )
GetDebugButtonDownHook:
mov [pInstance],rax
db C6 80
readmem( hk_GetDebugButtonDown+3, 4 )
db 01
hk_GetDebugButtonDown_o:
readmem( hk_GetDebugButtonDown, 7 )
jmp hk_GetDebugButtonDown+7
align 10 CC
pInstance:
dq 0
align 10 CC
hk_GetDebugButtonDown:
jmp GetDebugButtonDownHook
nop 2
]]..[[[DISABLE]
hk_GetDebugButtonDown:
readmem( hk_GetDebugButtonDown_o, 7 )
[pInstance]+1B6:
db 0
]]
[ENABLE]
LaunchMonoDataCollector()
local addr = getAddressSafe( "BC::GameKey::GetDebugButtonDown" )
if addr == nil then stopExec( "GetDebugButtonDown not found!" ) end
addr = aobScanSmall( "0FB680", addr, addr + 0x100 )
unregisterSymbol( "hk_GetDebugButtonDown" )
registerSymbol( "hk_GetDebugButtonDown", addr, true )
local mem = allocateMemory( 0x1000, addr )
unregisterSymbol( "GetDebugButtonDownHook" )
registerSymbol( "GetDebugButtonDownHook", mem, true )
result, disableinfo = autoAssemble( _script )
[DISABLE]
autoAssemble( _script, disableinfo )
local mem = getAddressSafe( "GetDebugButtonDownHook" )
deAlloc( mem )
autoAssemble([[
unregistersymbol( pInstance )
unregistersymbol( hk_GetDebugButtonDown_o )
unregistersymbol( GetDebugButtonDownHook )
unregistersymbol( hk_GetDebugButtonDown )
]])
What the script does is to enable Mono automatically when you activate it. Then it gets to the address of BC:GameKey:GetDebugButtonDown (you can use :: as well, not just from its symbol. If not found, it will error out and let you know something's off (usually.. Mono didn't get enabled or something happened in-between). If the address is found, then I make it scan for the first (and only) occurrence of "0FB680". Which is this line:
Code: Select all
BC:GameKey:GetDebugButtonDown+4b - 0FB6 80 B6010000 - movzx eax,byte ptr [rax+000001B6]
And the reason I do a scan like that is I am not 100% sure Unity will assemble (or JIT -- just-in-time compile) the ASM code in the same way every single time. As such, just to be sure, I'm scanning for a small array of bytes to find the spot accurately. The scan is done between function's prologue and prologue + 0x100 bytes. It's a big enough range. Once found, I unregister a symbol I want to apply before applying it (just to make sure it's not already registered).
Then comes allocation of memory where the ASM _script will be assembled. I called it GetDebugButtonDownHook.
Lastly, the autoAssemble will assemble the code in _script and hook 'BC:GameKey:GetDebugButtonDown+4b' we found earlier. All in all, it looks like this:
Once you enable the script, you will see this in-game:
So now we know Debug Keys are enabled. However, since the developers removed the debug key associations from the encoded ReInput table in Rewired_Core.dll, those F1-F3 key presses won't work to do what I mentioned in the beginning of this topic. However, if you press Tilde (~`) key, you will see this:
So that's what I did in my table to enable the console. Additionally, in the table below, you will also find a script that kills the Arena timer, so you can play those waves for as long as you want
Part two, the goodies, in the next post.
BR,
Sun
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