2018-05-12 13:38:51 -07:00
|
|
|
-- disassembly used for reference:
|
|
|
|
-- https://gist.githubusercontent.com/1wErt3r/4048722/raw/59e88c0028a58c6d7b9156749230ccac647bc7d4/SMBDIS.ASM
|
|
|
|
|
2018-05-12 14:08:00 -07:00
|
|
|
local band = bit.band
|
|
|
|
local floor = math.floor
|
|
|
|
local emu = emu
|
|
|
|
local gui = gui
|
|
|
|
|
|
|
|
local util = require("util")
|
|
|
|
local R = memory.readbyteunsigned
|
|
|
|
local W = memory.writebyte
|
|
|
|
local function S(addr) return util.signbyte(R(addr)) end
|
|
|
|
|
2018-06-07 13:40:31 -07:00
|
|
|
local valid_tiles = {
|
|
|
|
0x00, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16,
|
|
|
|
0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E,
|
|
|
|
0x1F, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26,
|
|
|
|
0x51, 0x52, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59,
|
|
|
|
0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F, 0x60, 0x61,
|
|
|
|
0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69,
|
|
|
|
0x6B, 0x6C, 0x89, 0xC0, 0xC1, 0xC2, 0xC3, 0xC4,
|
|
|
|
0xC5
|
|
|
|
}
|
|
|
|
|
|
|
|
local tile_lut = {}
|
|
|
|
for i, v in ipairs(valid_tiles) do
|
|
|
|
tile_lut[v] = i - 1
|
|
|
|
end
|
|
|
|
|
2018-06-08 19:34:21 -07:00
|
|
|
local area_lut = {
|
|
|
|
-- first digit: world number.
|
|
|
|
-- second digit: level number.
|
|
|
|
-- note: excludes pipe intros.
|
|
|
|
[11] = 0, [12] = 2, [13] = 3, [14] = 4,
|
|
|
|
[21] = 0, [22] = 2, [23] = 3, [24] = 4,
|
|
|
|
[31] = 0, [32] = 1, [33] = 2, [34] = 3,
|
|
|
|
[41] = 0, [42] = 2, [43] = 3, [44] = 4,
|
|
|
|
[51] = 0, [52] = 1, [53] = 2, [54] = 3,
|
|
|
|
[61] = 0, [62] = 1, [63] = 2, [64] = 3,
|
|
|
|
[71] = 0, [72] = 2, [73] = 3, [74] = 4,
|
|
|
|
[81] = 0, [82] = 1, [83] = 2, [84] = 3,
|
|
|
|
}
|
|
|
|
|
2018-05-12 13:38:51 -07:00
|
|
|
local rotation_offsets = { -- FIXME: not all of these are pixel-perfect.
|
|
|
|
0, -40, -- 0x00
|
|
|
|
6, -38,
|
|
|
|
15, -37,
|
|
|
|
22, -32,
|
|
|
|
28, -28,
|
|
|
|
32, -22,
|
|
|
|
37, -14,
|
|
|
|
39, -6,
|
|
|
|
40, 0, -- 0x08
|
|
|
|
38, 7,
|
|
|
|
37, 15,
|
|
|
|
33, 23,
|
|
|
|
27, 29,
|
|
|
|
22, 33,
|
|
|
|
14, 37,
|
|
|
|
6, 39,
|
|
|
|
0, 41, -- 0x10
|
|
|
|
-7, 40,
|
|
|
|
-16, 38,
|
|
|
|
-22, 34,
|
|
|
|
-28, 28,
|
|
|
|
-34, 23,
|
|
|
|
-38, 16,
|
|
|
|
-40, 8,
|
|
|
|
-40, -0, -- 0x18
|
|
|
|
-40, -6,
|
|
|
|
-38, -14,
|
|
|
|
-34, -22,
|
|
|
|
-28, -28,
|
|
|
|
-22, -32,
|
|
|
|
-16, -36,
|
|
|
|
-8, -38,
|
|
|
|
}
|
|
|
|
|
|
|
|
-- TODO: reinterface to one "input" array visible to main.lua.
|
|
|
|
local sprite_input = {}
|
|
|
|
local tile_input = {}
|
|
|
|
local extra_input = {}
|
|
|
|
|
|
|
|
local overlay = false
|
|
|
|
|
|
|
|
local function get_timer()
|
|
|
|
return R(0x7F8) * 100 + R(0x7F9) * 10 + R(0x7FA)
|
|
|
|
end
|
|
|
|
|
|
|
|
local function get_score()
|
|
|
|
return R(0x7DE) * 10000 +
|
|
|
|
R(0x7DF) * 1000 +
|
|
|
|
R(0x7E0) * 100 +
|
|
|
|
R(0x7E1) * 10 +
|
|
|
|
R(0x7E2)
|
|
|
|
end
|
|
|
|
|
|
|
|
local function set_timer(time)
|
|
|
|
W(0x7F8, floor(time / 100))
|
|
|
|
W(0x7F9, floor((time / 10) % 10))
|
|
|
|
W(0x7FA, floor(time % 10))
|
|
|
|
end
|
|
|
|
|
|
|
|
local function mark_sprite(x, y, t)
|
2018-06-08 06:03:09 -07:00
|
|
|
if t == 0 or x <= -8 or x >= 0xFF or y <= 0 or y >= 0xE8 then
|
2018-06-08 05:52:04 -07:00
|
|
|
-- place unused/unseen sprites
|
2018-06-08 06:03:09 -07:00
|
|
|
x = -8 -- just off the left side of the screen
|
2018-06-08 05:52:04 -07:00
|
|
|
y = 0xC8 -- at ground level.
|
|
|
|
end
|
|
|
|
|
|
|
|
local cx = 2 * (x - 0x80) -- relative to center of screen.
|
|
|
|
local cy = 2 * (y - 0x88) -- relative to standing on 4th block from floor.
|
|
|
|
|
2018-05-12 13:38:51 -07:00
|
|
|
if x < 0 or x >= 256 or y < 0 or y > 224 then
|
|
|
|
sprite_input[#sprite_input+1] = 0
|
|
|
|
sprite_input[#sprite_input+1] = 0
|
2018-06-10 07:41:07 -07:00
|
|
|
--sprite_input[#sprite_input+1] = 0
|
2018-05-12 13:38:51 -07:00
|
|
|
else
|
2018-06-08 05:52:04 -07:00
|
|
|
sprite_input[#sprite_input+1] = cx
|
|
|
|
sprite_input[#sprite_input+1] = cy
|
2018-06-10 07:41:07 -07:00
|
|
|
--sprite_input[#sprite_input+1] = t
|
2018-05-12 13:38:51 -07:00
|
|
|
end
|
2018-06-08 05:52:04 -07:00
|
|
|
|
|
|
|
if overlay and t ~= 0 then
|
2018-05-12 13:38:51 -07:00
|
|
|
gui.box(x-4, y-4, x+4, y+4)
|
|
|
|
gui.text(x-13, y-3-9, ("%+04i"):format(t), '#FFFFFF', '#0000003F')
|
2018-06-08 05:52:04 -07:00
|
|
|
gui.text(x-13, y-3+9, ("%+04i"):format(cx), '#FFFFFF', '#0000003F')
|
2018-05-12 13:38:51 -07:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
local function mark_tile(x, y, t)
|
2018-06-07 13:40:31 -07:00
|
|
|
tile_input[#tile_input+1] = tile_lut[t]
|
2018-05-12 13:38:51 -07:00
|
|
|
if t == 0 then return end
|
|
|
|
if overlay then
|
|
|
|
gui.box(x-8, y-8, x+8, y+8)
|
|
|
|
gui.text(x-5, y-3, ("%02X"):format(t), '#FFFFFF', '#00000000')
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
local function getxy(i, x_addr, y_addr, pageloc_addr, hipos_addr)
|
|
|
|
local spl_l = R(0x71A)
|
|
|
|
local spl_r = R(0x71B)
|
|
|
|
local sx_l = R(0x71C)
|
|
|
|
local sx_r = R(0x71D)
|
|
|
|
|
|
|
|
local x = R(x_addr + i)
|
|
|
|
local y = R(y_addr + i)
|
|
|
|
local sx, sy = x, y
|
|
|
|
if pageloc_addr ~= nil then
|
|
|
|
local page = R(pageloc_addr + i)
|
|
|
|
sx = sx - sx_l - (spl_l - page) * 256
|
|
|
|
else
|
|
|
|
sx = sx - sx_l
|
|
|
|
end
|
|
|
|
if hipos_addr ~= nil then
|
|
|
|
local hipos = S(hipos_addr + i)
|
|
|
|
sy = sy + (hipos - 1) * 256
|
|
|
|
end
|
|
|
|
|
|
|
|
return sx, sy
|
|
|
|
end
|
|
|
|
|
|
|
|
local function paused() return band(R(0x776), 1) end
|
|
|
|
|
|
|
|
local function get_state()
|
|
|
|
if R(0xE) == 0xFF then return 'power' end
|
|
|
|
if R(0x774) > 0 then return 'lagging' end
|
|
|
|
if R(0x7A2) > 0 then return 'waiting_demo' end
|
|
|
|
if R(0x717) > 0 then return 'playing_demo' end
|
|
|
|
-- if R(0x770) == 0xFF then return 'power' end
|
|
|
|
if paused() ~= 0 then return 'paused' end
|
|
|
|
if R(0xE) == 0 then return 'world_screen' end
|
|
|
|
-- if R(0x712) == 1 then return 'deadmusic' end
|
|
|
|
if R(0x7CA) == 0x94 then return 'dead' end
|
|
|
|
if R(0xE) == 4 then return 'win_flagpole' end
|
|
|
|
if R(0xE) == 5 then return 'win_walking' end
|
|
|
|
if R(0xE) == 6 then return 'lose' end
|
|
|
|
-- if R(0x770) == 0 then return 'not_playing' end
|
|
|
|
if R(0x770) == 2 then return 'win_castle' end
|
|
|
|
if R(0x772) == 2 then return 'no_control' end
|
|
|
|
if R(0x772) == 3 then return 'playing' end
|
|
|
|
if R(0x770) == 1 then return 'loading' end
|
|
|
|
if R(0x770) == 3 then return 'lose' end
|
|
|
|
return 'unknown'
|
|
|
|
end
|
|
|
|
|
|
|
|
local function advance()
|
|
|
|
emu.frameadvance()
|
|
|
|
while emu.lagged() do emu.frameadvance() end -- skip lag frames.
|
|
|
|
while R(0x774) > 0 do emu.frameadvance() end -- also lag frames.
|
|
|
|
end
|
|
|
|
|
|
|
|
local function handle_enemies()
|
|
|
|
-- enemies, flagpole
|
|
|
|
for i = 0, 5 do
|
|
|
|
local x, y = getxy(i, 0x87, 0xCF, 0x6E, 0xB6)
|
|
|
|
x, y = x + 8, y + 16
|
|
|
|
local tid = R(0x16 + i)
|
|
|
|
local flags = R(0xF + i)
|
|
|
|
--local offscr = R(0x3D8 + i)
|
|
|
|
local invisible = tid < 0x10 and flags == 0
|
|
|
|
if tid == 0x30 then y = y - 8 end -- flagpole flag
|
|
|
|
if tid == 0x31 then y = y - 8 end -- castle flag
|
|
|
|
if tid == 0x16 then x, y = x - 4, y - 12 end -- fireworks
|
|
|
|
if tid >= 0x24 and tid <= 0x29 then x, y = x + 16, y - 12 end -- moving platforms
|
|
|
|
if tid == 0x2D then x, y = x, y end -- bowser (TODO: determine head or body)
|
|
|
|
if tid == 0x15 then x, y = x, y - 12 end -- bowser fire
|
|
|
|
if tid == 0x32 then x, y = x, y - 8 end -- spring
|
|
|
|
-- tid == 0x35 -- toad
|
|
|
|
if tid == 0x1D or tid == 0x1B then -- rotating fire bars
|
|
|
|
x, y = x - 4, y - 12
|
|
|
|
-- this is a mess... gotta find out its rotation and then project.
|
|
|
|
-- TODO: handle long fire bars too
|
|
|
|
local rot = R(0xA0 + i) --* 0x100 + R(0x58 + i)
|
2018-06-08 19:35:09 -07:00
|
|
|
if overlay then
|
|
|
|
gui.text(x-13, y-3+9, ("%04X"):format(rot), '#FFFFFF', '#0000003F')
|
|
|
|
end
|
2018-05-12 13:38:51 -07:00
|
|
|
local x_off, y_off = rotation_offsets[rot*2+1], rotation_offsets[rot*2+2]
|
|
|
|
x, y = x + x_off, y + y_off
|
|
|
|
end
|
|
|
|
if invisible then
|
|
|
|
mark_sprite(0, 0, 0)
|
|
|
|
else
|
|
|
|
mark_sprite(x, y, tid + 1)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
local function handle_fireballs()
|
|
|
|
for i = 0, 1 do
|
|
|
|
local x, y = getxy(i, 0x8D, 0xD5, 0x74, 0xBC)
|
|
|
|
x, y = x + 4, y + 4
|
|
|
|
local state = R(0x24 + i)
|
|
|
|
local invisible = state == 0
|
|
|
|
if invisible then
|
|
|
|
mark_sprite(0, 0, 0)
|
|
|
|
else
|
|
|
|
mark_sprite(x, y, 257)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
local function handle_blocks()
|
|
|
|
for i = 0, 3 do
|
|
|
|
local x, y = getxy(i, 0x8F, 0xD7, 0x76, 0xBE)
|
|
|
|
x, y = x + 8, y + 8
|
|
|
|
local state = R(0x26 + i)
|
|
|
|
local invisible = state == 0
|
|
|
|
if invisible then
|
|
|
|
mark_sprite(0, 0, 0)
|
|
|
|
else
|
|
|
|
mark_sprite(x, y, 258)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
local function handle_hammers()
|
|
|
|
-- hammers, coins, score bonus text...
|
|
|
|
for i = 0, 8 do
|
|
|
|
local x, y = getxy(i, 0x93, 0xDB, 0x7A, 0xC2)
|
|
|
|
x, y = x + 8, y + 8
|
|
|
|
local state = R(0x2A + i)
|
|
|
|
-- skip coin effect states. not interactable; we don't care!
|
|
|
|
if state ~= 0 and state >= 0x30 then
|
|
|
|
mark_sprite(x, y, state + 1)
|
|
|
|
else
|
|
|
|
mark_sprite(0, 0, 0)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
local function handle_misc()
|
|
|
|
for i = 0, 0 do
|
|
|
|
local x, y = getxy(i, 0x9C, 0xE4, 0x83, 0xCB)
|
|
|
|
x, y = x + 8, y + 8
|
|
|
|
local state = R(0x33 + i)
|
|
|
|
if state ~= 0 then
|
|
|
|
mark_sprite(x, y, state + 1)
|
|
|
|
else
|
|
|
|
mark_sprite(0, 0, 0)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
local function handle_tiles()
|
|
|
|
--local tile_col = R(0x6A0)
|
|
|
|
local tile_scroll = floor(R(0x73F) / 16) + R(0x71A) * 16
|
|
|
|
local tile_scroll_remainder = R(0x73F) % 16
|
|
|
|
extra_input[#extra_input+1] = tile_scroll_remainder
|
2018-06-13 11:18:10 -07:00
|
|
|
-- for y = 0, 12 do
|
|
|
|
-- afaik the bottom row is always a copy of the second to bottom,
|
|
|
|
-- and the top is always air, so drop those from the inputs:
|
|
|
|
for y = 1, 11 do
|
2018-05-12 13:38:51 -07:00
|
|
|
for x = 0, 16 do
|
|
|
|
local col = (x + tile_scroll) % 32
|
|
|
|
local t
|
|
|
|
if col < 16 then
|
|
|
|
t = R(0x500 + y * 16 + (col % 16))
|
|
|
|
else
|
|
|
|
t = R(0x5D0 + y * 16 + (col % 16))
|
|
|
|
end
|
|
|
|
local sx = x * 16 + 8 - tile_scroll_remainder
|
|
|
|
local sy = y * 16 + 40
|
|
|
|
mark_tile(sx, sy, t)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
return {
|
|
|
|
-- TODO: don't expose these; provide interfaces for everything needed.
|
|
|
|
R=R,
|
|
|
|
W=W,
|
|
|
|
S=S,
|
|
|
|
overlay=overlay,
|
|
|
|
|
2018-06-07 13:40:31 -07:00
|
|
|
valid_tiles=valid_tiles,
|
2018-06-08 19:34:21 -07:00
|
|
|
area_lut=area_lut,
|
2018-06-07 13:40:31 -07:00
|
|
|
|
2018-05-12 13:38:51 -07:00
|
|
|
sprite_input=sprite_input,
|
|
|
|
tile_input=tile_input,
|
|
|
|
extra_input=extra_input,
|
|
|
|
|
|
|
|
get_timer=get_timer,
|
|
|
|
get_score=get_score,
|
|
|
|
set_timer=set_timer,
|
|
|
|
mark_sprite=mark_sprite,
|
|
|
|
mark_tile=mark_tile,
|
|
|
|
getxy=getxy,
|
|
|
|
paused=paused,
|
|
|
|
get_state=get_state,
|
|
|
|
advance=advance,
|
|
|
|
handle_enemies=handle_enemies,
|
|
|
|
handle_fireballs=handle_fireballs,
|
|
|
|
handle_blocks=handle_blocks,
|
|
|
|
handle_hammers=handle_hammers,
|
|
|
|
handle_misc=handle_misc,
|
|
|
|
handle_tiles=handle_tiles,
|
|
|
|
}
|