View Full Version : Development: Lua? In MY emulator?

February 6th, 2013, 2:34 AM
It's likelier than you think!

I forked VBA-M a few days ago to add scripting support and it just reached alpha level.
You can download/star/subscribe the project at https://github.com/dkreuter/VBA-M

You'll have to compile it yourself, and I only have a linux machine to work with, so I don't know if it even works with windows.

Anyway, here are some of the uses I imagine:

ROM-Hackers could write their gameplay in a proper programming language instead of pokescript.
ROM-Hackers could add features like multiplayer.
Hacker X wants to know to something about offset Y but can't find where Y is used.
X writes a script that triggers when Y is read and uploads it to a server
Somewhere else, the lua-enabled emulator downloads that script and lets it interact with the game.
When the script triggers it uploads a savegame to the server for X to analyze.

I personally want to use it to get traces as nice html.

Feedback? Bug-reports? Questions? Suggestions?

February 8th, 2013, 1:44 PM
UPDATE: The building process for linux users should be fully automated now.

Here's a test-file you can use with my emulator. Put it in the top folder of the project and name it… "pokemon.lua" maybe. Launch the emulator with 'vbam -l firered.gba' in the top folder, press F11, type 'require "pokemon"'. Resume emulation with Ctrl+D. You should see lots of debug output now. (PROFIT!)

-- Usage:
-- require "pokemon"
-- Ctrl+D

require "bkpt"
bit = require "bit"
stack = require "stack"
stack.over = {}

mute_c3 = false

function pwrite(x)
io.write(string.format("%-80s", x))

function c3_init_enter()
print("\n======== RESET C3 ========\n")
mute_c3 = true

function c3_init_leave()
mute_c3 = false

function c3watch_trigger(self, a, b, c)
if mute_c3 then return end
local space = ""
for i=1,self.id do space = space.." " end
if a == 0 then
pwrite(space..string.format("#%02x ", self.id) .. ref(c))
elseif a == 4 then
pwrite(space..string.format("#%02x ", self.id) .. (c==1 and "ON" or "OFF"))

function dp01watch_trigger(self, a, b, c)
local space = ""
for i=1,self.id do space = space.." " end
pwrite("DP01 ==== "..space..ref(c))

in_script = false
function script_enter() in_script = true end
function script_leave() in_script = false end
function script_env_trigger(self, a, b, c)
-- a = offset
-- b = old value
-- c = new value
if a >= 0x74 then
if a == 0x5C and c ~= 0x0815F9B4 then
print(string.format("New script command table @ %08x", c))
if not in_script then
print(string.format("untracked change 0x%08x+%02x %x->%x", self.start, a, b, c))
function script_desc()
local p = gba.reg(4)
return string.format("scriptenv %08x @ %08x ", p, gba.mem32(p+8))
function script_cmd()
print(script_desc().."cmd: "..ref(gba.reg(1)))
function script_env_init()
in_script = true
pwrite(string.format("scriptenv %08x @ reset", gba.reg(4)))
function script_status_normal()
in_script = true
pwrite(string.format("scriptenv %08x @ %08x LAUNCH", gba.reg(0), gba.reg(1)))
function oamt_spawn_from_uns_2()
pwrite(string.format("OAMt from UNS 2 %08x", gba.reg(0)))
function oamt_spawn_from_uns_1()
pwrite(string.format("OAMt from UNS 1 %08x", gba.reg(0)))

-- -- -- -- -- -- -- --

function watch_array(start, count, size, trigger)
for i=0,count do
local nw = {}
nw.id = i
nw.start = start + size*i
nw.stop = start + size*(i+1)
nw.trigger = trigger
table.insert(watch, nw)
gba.oldapi("bpw", string.format("0x%08x", start), string.format("%d", count*size))

function watch_simple(pos, size, msg)
function pp(self, a, b, c)
pwrite(string.format("%s: %08x+%x: ", msg, self.start, a)..ref(b).." -> "..ref(c))
local nw = {}
nw.start = pos
nw.stop = pos + size
nw.trigger = pp
table.insert(watch, nw)
gba.oldapi("bpw", string.format("0x%08x", pos), string.format("%d", size))

function regmodpoint(addr, reg, value)
function modder()
gba.reg(reg, value)
table.insert(bkpts.inactive, addr)
bkpts.callback[addr] = modder

-- -- -- -- -- -- -- --

function install()
bkpts:add(0x080773BC, c3_init_enter)
bkpts:add(0x0807740C, c3_init_leave)
bkpts:add(0x08069804, script_enter)
bkpts:add(0x0806986E, script_cmd)
bkpts:add(0x0806987C, script_leave)
bkpts:add(0x080697AC, script_env_init)
bkpts:add(0x080697E2, script_leave)
bkpts:add(0x080697E8, script_status_normal)
bkpts:add(0x080697F0, script_leave)
bkpts:add(0x08006F8C, oamt_spawn_from_uns_1)
bkpts:add(0x08006FE0, oamt_spawn_from_uns_2)

watch_array(0x03005090, 16, 0x28, c3watch_trigger)
watch_array(0x03000eb0, 2, 0x78, script_env_trigger)
watch_array(0x03004FE0, 4, 4, dp01watch_trigger)
watch_simple(0x03005020, 4, "map loader")
-- watch_simple(0x030030F0, 8)
-- watch_simple(0x02023D74, 4)
-- watch_simple(0x03004F84, 4)

add_tracepoint(0x08035AC0, " ------------AC0", 1)
-- add_tracepoint(0x0800D8B0, "load dp01 buffer", 1)
-- add_tracepoint(0x08014068, "", 0)
-- add_tracepoint(0x08012408, "c1_battle_dp01_bx_r0", 0)


-- You probably don't have this file.
-- loadnames("firered.map")

-- -- -- -- -- -- -- --

names = {}

function ref(addr)
local addr = bit.band(addr, bit.bnot(1))
return names[addr] or string.format("%08x", addr)

function loadnames(fname)
for line in io.lines(fname) do
k, v = string.match(line, ":(%x+) (.+)")
if k ~= nil then
names[tonumber(k, 16)+0x08000000] = v

-- -- -- -- -- -- -- --

function introskipper()
gba.mem32(0x080EC5D0, 0x0800C301)
gba.mem32(0x0800C4C4, 0x08056645)
gba.mem32(0x080EC5D0, 0x0800C301)
gba.mem32(0x0800C4C4, 0x08056645) -- Player Name: Kamon
gba.mem32(0x08054A68, 0x68204C3C)
gba.mem32(0x08054A6C, 0xE0014900)
gba.mem32(0x08054A70, 0x081C5797)
gba.mem16(0x08054A7E, 0x4E34)
gba.mem16(0x08054B3A, 0x0000)
gba.mem16(0x08054B3C, 0x0000)

-- -- -- -- -- -- -- --


I can't really explain all of it right now. I hope you can figure it out from the lua-files.

Shiny Quagsire
February 8th, 2013, 10:28 PM

I can see so much potential with this. If the Lua can be triggered on a RAM write, you could use a writebytetooffset in a pokescript to trigger a lua script, do some stuff, then return!

As for networking, that might take a bit of work for stuff like a DPPt style underground, but I can see some REALLY good but simple uses for networking: Mystery Gifts. Use an online distribution system (or codes based on an online thingy) to get certain items or event pokemon. Or, a GTS to trade pokemon with other ROM-Hack-players. OR, an online saving system where you can view your progress... With networking, the possibilities are endless!

Although, I must request one feature if it's not already existant: Linking (wireless or wired) from a Lua script. While it's possible manually, it's annoyingly difficult. If we could have link connections from lua, we could have a wifi battle system, or a wifi trading system in all the Pokemon Games!