-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathasm.lua
134 lines (117 loc) · 3.42 KB
/
asm.lua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
-- Lua helpers to interact with assembly.
local P = {}
asm = P
local assert = assert
local coroutine = coroutine
local emu = emu
local error = error
local io = io
local memory = memory
local string = string
local tonumber = tonumber
local unpack = unpack
local print = print
setfenv(1, P)
-- Returns a table mapping labels to addresses.
function loadlabels (filename)
local labels = {}
for line in io.lines(filename) do
local addr, name = string.match(line, "^al ([0-9A-F]+) %.(.*)$")
if addr ~= nil then
labels[name] = tonumber(addr, 16)
end
end
return labels
end
local function resume (co, ...)
local ok, err = coroutine.resume(co, unpack(arg))
if not ok then
error(err)
end
end
-- Waits until addr is written. memory.registerwrite does not actually provide
-- the write in 'value', so it is not returned here.
function waitwrite (addr)
assert(addr ~= nil, "addr must not be nil")
local co = coroutine.running()
memory.registerwrite(addr, function ()
memory.registerwrite(addr, nil)
resume(co)
end)
coroutine.yield()
end
-- Waits until addr is executed
function waitexecute (addr)
assert(addr ~= nil, "addr must not be nil")
local co = coroutine.running()
memory.registerexecute(addr, function ()
memory.registerexecute(addr, nil)
resume(co)
end)
coroutine.yield()
end
-- Waits until before the frame is emulated
function waitbefore ()
local co = coroutine.running()
emu.registerbefore(function ()
emu.registerbefore(nil)
resume(co)
end)
coroutine.yield()
end
-- Waits until after the frame is emulated
function waitafter ()
local co = coroutine.running()
emu.registerafter(function ()
emu.registerafter(nil)
resume(co)
end)
coroutine.yield()
end
-- Push byte onto stack
function push (byte)
local s = memory.getregister("s")
memory.writebyte(0x100 + s, byte)
memory.setregister("s", s-1)
end
-- Pop byte from stack
function pop ()
local s = memory.getregister("s") + 1
memory.setregister("s", s)
return memory.readbyte(0x100 + s, byte)
end
-- Jumps to the named address and waits for it to return. The PC is restored to
-- the same value after the subroutine. The currently-executing instruction
-- when calling this function should be a nop or compare. It can be an
-- instruction that writes to a register, as long as the value for the register
-- is not important. It must not be an instruction that writes to memory,
-- adjusts the stack, or changes the PC.
--
-- FCEUX's callbacks run in the middle of instruction processing and don't
-- provide single-clock stepping. The current instruction's execution must
-- complete before running the chosen subroutine, but it will run in a
-- corrupted manner.
function jsr (addr)
assert(addr ~= nil, "addr must not be nil")
local curaddr = memory.getregister("pc")
-- Wait until FCEUX has finished the current instruction.
-- NOP sled because FCEUX hasn't incremented the PC for the current
-- instruction yet, and we don't know how long the instruction is.
local lastnopaddr = 0x100 + memory.getregister("s")
push(0xEA) -- NOP
push(0xEA) -- NOP
push(0xEA) -- NOP
push(0xEA) -- NOP
-- This may corrupt the current instruction, especially for multi-byte
-- insturctions. The later bytes will be relative to this new pc.
memory.setregister("pc", lastnopaddr-2)
waitexecute(lastnopaddr)
pop()
pop()
pop()
pop()
push(((curaddr-1)/256) % 256)
push(((curaddr-1) ) % 256)
memory.setregister("pc", addr-1) -- -1 for the NOP's size
waitexecute(curaddr)
end