1 2
13 14
Post subject: Adventures in Lua (BizHawk, DeSmuME, and others)
Pokota
He/Him
Joined: 2/5/2014
Posts: 778
E: Originally, this thread was BizHawk centric, but has since branched out to other emulators. NOTE: This thread isn't just for me to get help, but for everyone to show off and ask help for their BizHawk Lua scripts. ---- So because BizHawk has its own interpretation implementation of Lua, I figured I could write some Lua Scripts to use when I'm playing around. Then I realized that I've never actually scripted in Lua before. After a few quick lessons and some bugsquashing by the good people in IRC, I've managed to put together my first ever Lua HUD script. Download smb2hud.lua
Language: lua

-- "Super Mario Bros. 2" local cherryCount local foeLifeA local foeLifeB local squatTimer UI_GREEN = 0x00FF00; UI_RED = 0xFF0000; while (true) do cherryCount = memory.readbyte(0x062A); squatTimer = memory.readbyte(0x04CA); foeLifeA = memory.readbyte(0x0468); if (foeLifeA == 255) then foeLifeA = -1; end foeLifeB = memory.readbyte(0x0469); if (foeLifeB == 255) then foeLifeB = -1; end foeLifeA = foeLifeA + 1; foeLifeB = foeLifeB + 1; gui.drawRectangle(40,20,(foeLifeA*5),5); gui.drawRectangle(40,25,(foeLifeB*5),5); gui.drawRectangle(40,10,60,5); gui.drawRectangle(40,10,squatTimer,5); for i = 0, cherryCount, 1 do gui.drawIcon("cherry.ico",0,(i*16)-8,16,16); end emu.frameadvance(); end
Which yields this amended HUD: What I'm planning on doing next is fixing the Cherry Meter to show how many cherries have been collected instead of simply moving the cherry around, then converting the enemy health meters from bars to hearts, then coloring the squat meter.
Adventures in Lua When did I get a vest?
Pokota
He/Him
Joined: 2/5/2014
Posts: 778
So now this is a thing. Download z2hud.lua
Language: lua

-- "Zelda II: The Adventure of Link" local bagCounter local currentMP local currentHP local maxMP local maxHP local hearts local jars local armor local intel local skill local xp local toLevelTemp local toLevel local percentToLevel local keys local selector while true do bagCounter = memory.readbyte(0x05DF); currentMP = memory.readbyte(0x0773); currentHP = memory.readbyte(0x0774); hearts = memory.readbyte(0x0784); jars = memory.readbyte(0x0783); armor = memory.readbyte(0x0779); intel = memory.readbyte(0x0778); skill = memory.readbyte(0x0777); xp = (memory.readbyte(0x0775)*255)+memory.readbyte(0x0776); toLevelTemp = (memory.readbyte(0x0770)*255)+memory.readbyte(0x0771); keys = memory.readbyte(0x0793); selector = memory.readbyte(0x0749); percentToLevel = xp / toLevelTemp; toLevel = percentToLevel*100; maxMP = (jars*32)-1; maxHP = (hearts*32)-1; gui.drawBox(0,0,255,41,"White","Black"); gui.drawText(0,8,armor.."|HP: "..currentHP.."/"..maxHP,"Red",11); gui.drawText(0,18,intel.."|MP: "..currentMP.."/"..maxMP,"White",11); gui.drawText(0,28,skill.."|"..bagCounter.." enemies since last drop","Yellow",11); gui.drawText(95,8," Keys: "..keys,"Aqua",11); gui.drawText(95,18,"Spell: "..selector,"White",11); gui.drawRectangle(235,8,20,(32*percentToLevel),"Green","Green"); gui.drawBox(235,8,255,40,"White"); emu.frameadvance(); end
I need to flip the direction of the experience bar, but other than that this is about where I like it. And now that I know how to handle color (passing a name string instead of any sort of number? Sure, let's go with that), I can make forward progress on the SMB2 one. A little bit of reinventing the wheel here, but it's FUN ^^ EDIT: Thanks to Scepheo, I now know that colors are passed in through four bytes instead of three. 0xFF00FF00 is Green - this declares 255 Alpha, 0 Red, 255 Green, and 0 Blue. Now to use this as intended...
Adventures in Lua When did I get a vest?
Site Admin, Skilled player (1235)
Joined: 4/17/2010
Posts: 11264
Location: RU
You can highlight syntax, and even download code as a file: Download sample.lua
Language: lua

function stuff() x = memory.reawword(0x100) gui.text(10,10,string.format("Xpos: %d",x)) end gui.register(stuff) -- obsoletes the "while true do emu.frameadvance() end" loop
[code=sample.lua] function stuff() x = memory.reawword(0x100) gui.text(10,10,string.format("%d",x)) end gui.register(stuff) -- obsoletes the "while true do emu.frameadvance() end" loop [/code] However when using scripts with < and >, make sure to "Disable HTML in this post". I'm also pretty sure using "local" for globally declared variables is unnecessary. As is declaring them without instantly using, as is ";".
Warning: When making decisions, I try to collect as much data as possible before actually deciding. I try to abstract away and see the principles behind real world events and people's opinions. I try to generalize them and turn into something clear and reusable. I hate depending on unpredictable and having to make lottery guesses. Any problem can be solved by systems thinking and acting.
Editor, Emulator Coder
Joined: 8/7/2008
Posts: 1156
It's good practice to type local everywhere in lua, in fact there's strict.lua to enforce it. Even at the global level it still makes sense, because it isn't actually global but instead 'script-level' and a local declared there wouldn't be accessible to other scripts, and would run faster than a true global. That's what I remember, anyway.
Pokota
He/Him
Joined: 2/5/2014
Posts: 778
feos wrote:
I'm also pretty sure using "local" for globally declared variables is unnecessary. As is declaring them without instantly using, as is ";".
C# habits are hard to break. With that said, I'll update the scripts to account for gui.register today, and I'll modify the posts above for syntax highlighting. Link to video Link to video
Adventures in Lua When did I get a vest?
Site Admin, Skilled player (1235)
Joined: 4/17/2010
Posts: 11264
Location: RU
Brackets around conditions (unless they're complicated) also aren't required.
Warning: When making decisions, I try to collect as much data as possible before actually deciding. I try to abstract away and see the principles behind real world events and people's opinions. I try to generalize them and turn into something clear and reusable. I hate depending on unpredictable and having to make lottery guesses. Any problem can be solved by systems thinking and acting.
Pokota
He/Him
Joined: 2/5/2014
Posts: 778
It broke once and putting the condition in brackets fixed it, so I'm going with what I know. Besides, I'm using Notepad++ as my editor, which lets me spot missing brackets quickly. Most of what I end up screwing up is stray characters like out of place commas and punkts. EDIT: The sample you posted yields this error:
LuaInterface.LuaScriptException: E:\Games\Emulation\Lua\sample.lua:7: attempt to call field 'register' (a nil value)
Adventures in Lua When did I get a vest?
Amaraticando
It/Its
Editor, Player (157)
Joined: 1/10/2012
Posts: 673
Location: Brazil
Pokota wrote:
EDIT: The sample you posted yields this error:
LuaInterface.LuaScriptException: E:\Games\Emulation\Lua\sample.lua:7: attempt to call field 'register' (a nil value)
This error signals that there's no field register in the table gui. This is a function in Snes9x API. Instead of gui.register, try event.onframeend(stuff). More events are listed here http://tasvideos.org/Bizhawk/LuaFunctions.html
Pokota
He/Him
Joined: 2/5/2014
Posts: 778
Taking that information, I was able to clean up the Zelda II script quite quickly. When I try to apply the same idea to the SMB2 script, it breaks citing unable to find cherry.png - which is in the same folder that I'm running the script out of. (Code block below actually does work, it's when I convert from this to on frame end that it breaks) Download smb2hudv2.lua
Language: lua

-- "Super Mario Bros. 2" local cherryCount local foeLifeA local foeLifeB local foeLifeC local foeLifeD local foeLifeE local squatTimer local carpetTimer local starTimer local dreamTimer local bigVegCount local stopTimer local starPercent local carpetPercent local dreamPercent local stopPercent TIMER = 100; while (true) do cherryCount = memory.readbyte(0x062A); bigVegCount = memory.readbyte(0x062C); squatTimer = memory.readbyte(0x04CA); carpetTimer = memory.readbyte(0x00B9); dreamTimer = memory.readbyte(0x04B7); starTimer = memory.readbyte(0x04E0); stopTimer = memory.readbyte(0x04FF); carpetPercent = carpetTimer / 160; dreamPercent = dreamTimer / 96; starPercent = starTimer / 62; stopPercent = stopTimer / 255; foeLifeA = memory.read_s8(0x0465); foeLifeB = memory.read_s8(0x0466); foeLifeC = memory.read_s8(0x0467); foeLifeD = memory.read_s8(0x0468); foeLifeE = memory.read_s8(0x0469); gui.drawRectangle(100,8,squatTimer,4,"Cyan","Cyan"); gui.drawRectangle(100,8,60,4,"RED"); gui.drawRectangle(100,15,TIMER*carpetPercent,5,"Red","Red"); gui.drawRectangle(100,15,TIMER*dreamPercent,5,"CYAN","CYAN"); gui.drawRectangle(100,15,TIMER*starPercent,5,"MAGENTA","MAGENTA"); gui.drawRectangle(100,15,TIMER*stopPercent,5,"Yellow","Yellow"); gui.drawRectangle(100,15,TIMER,5,"White"); for i = 0, cherryCount, 1 do gui.drawImage("cherry.png",(i*8)-8,16); end for j = 0, bigVegCount, 1 do gui.drawImage("veg.png",(j*8)-8,8); end for k = 0, foeLifeA, 1 do gui.drawImage("heart.png",(k*8)-8,144); end for l = 0, foeLifeB, 1 do gui.drawImage("heart.png",(l*8)-8,160); end for m = 0, foeLifeC, 1 do gui.drawImage("heart.png",(m*8)-8,176); end for n = 0, foeLifeD, 1 do gui.drawImage("heart.png",(n*8)-8,184); end for o = 0, foeLifeE, 1 do gui.drawImage("heart.png",(o*8)-8,192); end emu.frameadvance(); end
Lua HUD Package for SMB2, v2
Adventures in Lua When did I get a vest?
Amaraticando
It/Its
Editor, Player (157)
Joined: 1/10/2012
Posts: 673
Location: Brazil
I write a script for lsnes and try to adapt it to run in BizHawk. To facilitate things, I use:
Language: lua

-- Compatibility local u8 = mainmemory.read_u8 local s8 = mainmemory.read_s8 local u16 = mainmemory.read_u16_le local s16 = mainmemory.read_s16_le -- Example local RNG = u8(WRAM.RNG)
So, in the other emulator, I just do the same thing with the proper functions. Another good side of this is that I don't have to memorize a big name like "mainmemory.read_s16_le".
mz
Player (79)
Joined: 10/26/2007
Posts: 693
I wrote this unnecessarily long and messy script for Tails Adventure to create maps, view hitboxes, take screenshots, and other stuff: http://pastebin.com/XZfwkwDe I used it mainly just to take a screenshot for each frame and write the X and Y coordinates of the game camera, so I could make some videos: https://www.youtube.com/watch?v=FOmlQk8X0UQ I also liked how the maps looked after Tails had gone all over them: (Sorry for the long image, I can change it to a link if anyone finds it annoying.)
You're just fucking stupid, everyone hates you, sorry to tell you the truth. no one likes you, you're someone pretentious and TASes only to be on speed game, but don't have any hope, you won't get there.
Player (142)
Joined: 7/16/2009
Posts: 686
Pokota: as you probably know from C#, it's always a good idea to look for repeating structures in your script, and convert them to simpler code. For example, all of the "foeLife" related code can be made a lot simpler and cleaner:
Language: lua

for i = 0, 5 do local hp = memory.read_s8(0x0465 + i) local y = 144 + 16 * i for j = 0, hp do gui.drawImage("heart.png", j * 8 - 8, y) end end
Site Admin, Skilled player (1235)
Joined: 4/17/2010
Posts: 11264
Location: RU
There's also a way to display some huge string of numbers, breaking it into lines where it fits the best. Bonus points for displaying slot numbers over object sprites.
Language: lua

-- SolarJetman objects function DrawTable() local Xcam = memory.readwordsigned(0x4c) local Ycam = memory.readwordsigned(0x4e) for Slot = 0, 0x1f do local ID = memory.readbyte(0x317 + Slot) local X = memory.readwordsigned(0x200 + Slot, 0x21f + Slot) - Xcam local Y = memory.readwordsigned(0x25d + Slot, 0x27c + Slot) - Ycam local CellWidth = 32 local CellHeight = 8 local ScreenWidth = 256 local RowLength = 8 local OffsetX = 1 local OffsetY = 9 local Color = "white" if ID == 0 then Color = "red" end gui.text( Slot * CellWidth % ScreenWidth + OffsetX, -- text X math.floor(Slot / RowLength) * CellHeight + OffsetY, -- text Y string.format("%2d:%02X", Slot, ID), -- output string Color -- you guess ) gui.text(X, Y, Slot) end end
Warning: When making decisions, I try to collect as much data as possible before actually deciding. I try to abstract away and see the principles behind real world events and people's opinions. I try to generalize them and turn into something clear and reusable. I hate depending on unpredictable and having to make lottery guesses. Any problem can be solved by systems thinking and acting.
ventuz
He/Him
Player (123)
Joined: 10/4/2004
Posts: 940
Is there way to get rom crc32 hash? current lua in BizHawk seem to only return SHA-1 hash.
Editor, Emulator Coder
Joined: 8/7/2008
Posts: 1156
write which platform you want it for, since the code to do the hashes differs by platform, and consider it a feature request.
Pokota
He/Him
Joined: 2/5/2014
Posts: 778
I've been experimenting with a partial hud replacement for the Zelda Oracles games - nothing fancy, just a replaced health meter and an added Maple meter. I've just run into the issue that Seasons stores the bytes I need in a different location from Ages, so either I make two flavors or I actually study case/switch flow control. The reason Gasha Tree progress isn't included? Each tree has its own byte.
Adventures in Lua When did I get a vest?
Joined: 3/11/2012
Posts: 149
Location: WI
Pokota wrote:
I've been experimenting with a partial hud replacement for the Zelda Oracles games - nothing fancy, just a replaced health meter and an added Maple meter. I've just run into the issue that Seasons stores the bytes I need in a different location from Ages, so either I make two flavors or I actually study case/switch flow control. The reason Gasha Tree progress isn't included? Each tree has its own byte.
Can't you just check the title at ROM offset 0x134 for that? Oracle of Ages is "ZELDA NAYRU" and Oracle of Seasons is "ZELDA DIN"
Pokota
He/Him
Joined: 2/5/2014
Posts: 778
@Sappharad: The actual offsets for the ram values I need to pull for the Maple Meter and health meter are in different places between the two games. Probably because of the difference in titles, now that I think about it. And it's more "I haven't played with the case/switch flow control yet" than anything else.
Adventures in Lua When did I get a vest?
Player (142)
Joined: 7/16/2009
Posts: 686
Pseudocode below:
Language: Lua

checkCharacter = read_from_rom(0x140); if (checkCharacter == "N") then offset = someValue; else offset = someOtherValue; end ram_value = read_from_ram(offset);
Obviously these aren't the correct function names and you'd have to check against the numeric value for 'D' (ZELDA DIN) vs. 'N' (ZELDA NAYRU), but walking through a switch/case every time instead of just setting the offsets correct at the beginning is a needless performance hit.
Joined: 3/11/2012
Posts: 149
Location: WI
Sorry, I read the posts linearly without paying attention to the poster names. So I thought the answer to the "Why do you need crc32?" question was for telling the two oracle games apart, and provided an answer for how to tell the two games apart. After looking up again, I see those were two different conversations. But hopefully my suggestion is at least somewhat useful.
Post subject: Metroid II - Return of Samus - HUD: Metroid Queen HP
CrazyTerabyte
He/Him
Joined: 5/7/2005
Posts: 74
This small code shows the HP of the last boss in Metroid II game for GameBoy. This code most likely won't work inside Super GameBoy, though. Having this health bar gives useful feedback to the player. For me (my first playthrough of this game), the last fight was frustrating and near-impossible. After seeing the boss health bar, and seeing it going down, the fight became a lot more fun! It also gave me clear feedback on which attacks work, and how to defeat the boss faster.
Language: lua

while true do HP = mainmemory.readbyte(0x03D3); MAX = 150; if HP > 0 and HP <= MAX then -- Color is 0xAARRGGBB. gui.drawRectangle(0,144-16, HP,8, 0, 0xFFFF0000); gui.drawRectangle(0,144-16, MAX,7, 0xFFFFFFFF, 0); end; emu.frameadvance(); end
Pokota
He/Him
Joined: 2/5/2014
Posts: 778
I'll poke around in ram and see if I can't adapt that to work for all metroids and not just the queen. I know Zetas will be a lot less stressful with visual progress being made.
Adventures in Lua When did I get a vest?
CrazyTerabyte
He/Him
Joined: 5/7/2005
Posts: 74
@Pokota: for the first Metroid game (the NES one), there is already a fairly comprehensive RAM map. However, there is none for Metroid II (GB). Maybe you could write down your findings in that wiki? http://datacrystal.romhacking.net/wiki/Metroid:RAM_map http://datacrystal.romhacking.net/wiki/Metroid_II:_Return_of_Samus
CrazyTerabyte
He/Him
Joined: 5/7/2005
Posts: 74
For Disney's Aladdin (U) [!] (SEGA Mega Drive/Genesis), I've written the following script. It allows hiding the HUD, and can also move sprites in front of the other layers (now we can see hidden items).
Language: lua

-- Sprite Table for Disney's Aladdin SPRITE_TABLE = 0xF400 -- Information about sprite table layout: http://md.squee.co/wiki/VDP#Sprites function get_sprite_pattern(index) local data memory.usememorydomain("VRAM") data = memory.read_u16_be(SPRITE_TABLE + 8*index + 2*2) return bit.band(data, 0x7FF) end function set_sprite_x(index, pos) memory.usememorydomain("VRAM") pos = bit.band(pos, 0x1FF) memory.write_u16_be(SPRITE_TABLE + 8*index + 3*2, pos) end function set_sprite_priority(index, priority) local data memory.usememorydomain("VRAM") data = memory.read_u16_be(SPRITE_TABLE + 8*index + 2*2) if priority == 1 then data = bit.bor(data, 0x8000) elseif priority == 0 then data = bit.band(data, 0x7FFF) end memory.write_u16_be(SPRITE_TABLE + 8*index + 2*2, data) end FORM = forms.newform(128, 80, "Aladdin") CHECK_HUD = forms.checkbox(FORM, "Hide HUD", 0, 0) CHECK_TOP = forms.checkbox(FORM, "Sprites on-top", 0, 24) while true do for i = 0, 128, 1 do local pat = get_sprite_pattern(i) if (pat >= 0x680 and pat <= 0x6FF) -- Health, Lives, Apples, Gems or (pat >= 0x7C0 and pat <= 0x7E4) -- Score then if forms.ischecked(CHECK_HUD) then -- 32 is to the left, outside the screen set_sprite_x(i, 32) end else if forms.ischecked(CHECK_TOP) then -- Set all other sprites above the layers set_sprite_priority(i, 1) end end end emu.frameadvance() end
I've also built this RAM Watch map:
Domain 68K RAM
SystemID GEN
7E29	b	h	1	68K RAM	Score - 0x30~0x39
7E2A	b	h	1	68K RAM	Score - 0x30~0x39
7E2B	b	h	1	68K RAM	Score - 0x30~0x39 - thousands
7E2C	b	h	1	68K RAM	Score - 0x30~0x39 - hundreds
7E2D	b	h	1	68K RAM	Score - 0x30~0x39 - tens
7E2E	b	h	1	68K RAM	Score - 0x30~0x39 - units
7E3C	b	h	1	68K RAM	Lives - 0x30~0x39 - units
7E3F	b	h	1	68K RAM	Continues - 0~unlimited
EFE0	b	h	1	68K RAM	Apples - 0x30~0x39 - tens
EFE1	b	h	1	68K RAM	Apples - 0x30~0x39 - units
EFE2	b	h	1	68K RAM	Gems - 0x30~0x39 - tens
EFE3	b	h	1	68K RAM	Gems - 0x30~0x39 - units
EFFA	b	h	1	68K RAM	Health - 00~08
F003	b	h	1	68K RAM	Genies - 00~unlimited
F400	w	h	1	VRAM	Sprite Table
Pokota
He/Him
Joined: 2/5/2014
Posts: 778
Okay, question time. Do the readbyte family of commands accept decimal numbers or just hex numbers?
Adventures in Lua When did I get a vest?
1 2
13 14