smbot/smb.lua

291 lines
7.9 KiB
Lua
Raw Normal View History

2018-05-12 13:38:51 -07:00
-- disassembly used for reference:
-- https://gist.githubusercontent.com/1wErt3r/4048722/raw/59e88c0028a58c6d7b9156749230ccac647bc7d4/SMBDIS.ASM
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,
}
local util = require("util")
local R = memory.readbyteunsigned
local W = memory.writebyte
local function S(addr) return util.signbyte(R(addr)) end
-- TODO: reinterface to one "input" array visible to main.lua.
local sprite_input = {}
local tile_input = {}
local extra_input = {}
local overlay = false
local band = bit.band
local floor = math.floor
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)
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
sprite_input[#sprite_input+1] = 0
else
sprite_input[#sprite_input+1] = x
sprite_input[#sprite_input+1] = y
sprite_input[#sprite_input+1] = t
end
if t == 0 then return end
if overlay then
gui.box(x-4, y-4, x+4, y+4)
--gui.text(x-2, y-3, tostring(i), '#FFFFFF', '#00000000')
gui.text(x-13, y-3-9, ("%+04i"):format(t), '#FFFFFF', '#0000003F')
--gui.text(x-5, y-3+9, ("%02X"):format(x), '#FFFFFF', '#0000003F')
end
end
local function mark_tile(x, y, t)
tile_input[#tile_input+1] = t
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)
gui.text(x-13, y-3+9, ("%04X"):format(rot), '#FFFFFF', '#0000003F')
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
for y = 0, 12 do
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,
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,
}