EWM Update: Let's Get Cookin'!
Building on top of the Lua integration
work that I landed recently
in EWM, my Apple ][+
emulator, here
are two small improvements and a fun use case to show those off.
New Lua Callbacks
First, I have added two callbacks that let you intercept key presses. Not emulated presses, but EWM application key downs and ups. This means that you can now trigger scripts by connecting them to a key combination.
For example, say you want to dump the Zero Page when you hit Command-Z. That is now easy with just a few lines of code:
two:onKeyDown(function(two, mod, sym)
if mod == KMOD_GUI then -- Command on macOS and Meta on Linux
if sym == string.byte('z') then
ewm:hexdump(0x0000, 0xff) -- Dump the zero page
return true
end
end
end)
(The KMOD_GUI constant comes from SDL. I hope to have some abstraction for that at some point.)
Second, it is now possible to inspect and modify memory. The cpu
global now has a memory
property that you can index as an array:
-- Set the memory location at $300
cpu.memory[0x0300] = 1
-- Read the RESET vector
v = bit32.bor(cpu.memory[0xfffc], bit32.lshift(cpu.memory[0xfffd], 8))
(Bitwise operations in Lua are a bit of a pain unfortunately. I will probably introduce a more convenient API to access 16 bit values.)
Let’s build a cheat for Burger Time
Combined, these two new features can be used to build little cheats for games. Let’s try to build one for BurgerTime.
First let’s see if we can find where the game stores the number of chefs and pepper shots and see if we can modify those values.
So with this zero page dumper example in place, we hit Command-Z when the game has started. This is what we see:
0000: 1d 02 ae 1d 0c 00 d0 33 28 77 ae 00 00 b8 00 00
0010: 60 56 d5 00 00 00 00 00 00 00 00 00 00 00 00 00
0020: 00 02 00 b6 01 0a 00 00 01 00 01 01 63 10 0b 57
0030: a2 02 00 00 00 00 00 a3 1b fd 09 09 01 01 01 33
0040: 17 00 00 00 00 0c 07 00 00 09 e6 05 00 00 00 00
0050: 04 00 ea 00 00 00 00 00 00 00 00 00 00 00 00 00
0060: 73 96 00 00 3f 96 05 05 00 00 05 05 00 00 07 00
0070: 00 00 00 00 00 00 83 00 00 00 00 00 00 e2 a1 00
0080: 89 9a ab 9a 26 3d 3f 96 7f 7f 00 00 00 0f 25 1d
0090: 00 00 ff 07 00 1b 0f 03 1b 01 00 1a 0f 00 19 0f
00a0: 00 18 0f 0a 00 c1 00 00 00 00 00 00 00 00 00 00
00b0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00c0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00d0: 00 0c 00 00 02 00 5c c6 00 00 00 00 00 00 00 00
00e0: c0 92 00 92 04 00 00 2e d0 3d 50 2a 50 26 50 22
00f0: d0 35 d0 39 00 17 00 00 00 00 00 00 00 00 eb 1c
What we are looking for is the value 05
for the number of lives or
the number of pepper shots we have left. There are multiple
occurences, so what we do is run into an enemy and see if the value
changes somewhere:
Easily spotted, the value at 0x66
changed from 0x05
to 0x04
.
...
0060: 73 96 00 00 3f 96 04 05 00 00 05 05 00 00 07 00
... ^^
And if we get caught once more, by Mr. Pickle, and then use four
pepper shots, the values at 0x66
and 0x6a
look like this:
...
0060: 73 96 00 00 3f 96 03 05 00 00 01 05 00 00 07 00
... ^^ ^^
I think we can safely say that we have found the following memory locations:
CUR_LIVES = 0x66
MAX_LIVES = 0x67
CUR_SHOTS = 0x6a
MAX_SHOTS = 0x6b
How do we change them? Simple, we can define another key combo that simply resets the memory locations to a bigger number:
two:onKeyDown(function(two, mod, sym)
if mod == KMOD_GUI then
if sym == string.byte('r') then
cpu.memory[CUR_LIVES] = 7
cpu.memory[MAX_LIVES] = 7
cpu.memory[CUR_SHOTS] = 7
cpu.memory[MAX_SHOTS] = 7
return true;
end
end
return false
end)
Now every time you press Command-R, your lives and pepper shots are set to 7.
We can take this one step further and simply make sure that the
CUR_LIVES
and CUR_SHOTS
never changes at all.
There are only a few instructions to store a value in the Zero Page,
so with a little bit of trial and error it is pretty easy to find out
that the values are set with an STA $66,X
and STA $6A,X
instructions.
We can intercept those instructons by setting up a callback with
onBeforeExecuteInstruction
and then in the callback we make sure
that the value is always 0x05
:
STA_zpg_X = 0x95 -- Opcode for STA $NN,X
cpu:onBeforeExecuteInstruction(STA_zpg_X, function(cpu, op, oper)
-- If we are modifying the known locations ...
if oper == CUR_LIVES or oper == CUR_SHOTS then
cpu.a = 5 -- ... always store 0x05
end
end)
Now when the chef gets caught, the number of lives just stays the same. Same with the pepper shots.
Note that we have not actually looked at the Burger Time code at all. We have just inspected memory and made a guess about how it changes those values. No disassembly required!
If you are interested in playing around with my emulator, you can find the project at github.com/st3fan/ewm.
Most of my professional and personal work is Open Source. You can find many projects at github.com/st3fan - feel free to leave a bug report or open a feature request.