From 1d63058fef0fea2ae9fd854b866c1c59a9d6abe4 Mon Sep 17 00:00:00 2001 From: clue cat Date: Fri, 15 Mar 2024 17:41:07 -0400 Subject: [PATCH] demos: output demo files as asm and binary sources --- Makefile | 8 +- data/demos/.gitkeep | 0 inc/structs.inc | 9 ++ src/demos/.gitkeep | 0 src/main.s | 7 ++ tools/demo.lua | 209 ++++++++++++++++++++++++++++++-------------- 6 files changed, 168 insertions(+), 65 deletions(-) create mode 100644 data/demos/.gitkeep create mode 100644 src/demos/.gitkeep diff --git a/Makefile b/Makefile index 08e683ab..eb5eb45a 100644 --- a/Makefile +++ b/Makefile @@ -3,6 +3,8 @@ BASE := base OUT := m4rs OBJ_DIR := ./obj +DEMO_DIR := ./src/demos +DEMO_FILES := $(wildcard $(DEMO_DIR)/demo-*.s) FLIPS := ./tools/flips.exe AS := ./tools/armips-a8d71f0.exe @@ -27,9 +29,13 @@ dist: $(IN).gba $(OUT).gba stat: $(IN).gba $(AS) src/main.s -stat +$(DEMO_DIR)/demos-combined.s: $(DEMO_FILES) + cat $(DEMO_FILES) >> $@ + clean: $(RM) $(OBJ_DIR)/* $(RM) $(OUT).gba + echo -n "" > $(DEMO_DIR)/demos-combined.s -.PHONY: all check clean +.PHONY: all check clean $(DEMO_DIR)/demos-combined.s .INTERMEDIATE: $(OBJS) diff --git a/data/demos/.gitkeep b/data/demos/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/inc/structs.inc b/inc/structs.inc index de9cf83c..dccfdefd 100644 --- a/inc/structs.inc +++ b/inc/structs.inc @@ -324,6 +324,15 @@ VariableConnection_DestinationDoor equ 03h VariableConnection_Size equ 04h .sym on + +DemoInputData equ 083E3EACh +.sym off +DemoInputData_InputsPointer equ 00h +DemoInputData_NumInputs equ 04h +DemoInputData_DurationsPointer equ 08h +DemoInputData_NumDurations equ 0Ch +.sym on + DemoMemory equ 083E3F6Ch .sym off ; modified from vanilla to account for nonlinear upgrades diff --git a/src/demos/.gitkeep b/src/demos/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/src/main.s b/src/main.s index 33f0a1f5..c4b56fea 100644 --- a/src/main.s +++ b/src/main.s @@ -32,6 +32,10 @@ .ifndef MISSILES_WITHOUT_MAINS .definelabel MISSILES_WITHOUT_MAINS, 0 .endif +.ifndef CUSTOM_DEMOS +.definelabel CUSTOM_DEMOS, 0 +.endif + .include "inc/constants.inc" .include "inc/enums.inc" @@ -110,6 +114,9 @@ CreditsScrollSpeed equ 087FF232h .include "src/nonlinear/bosses.s" .include "src/nonlinear/data-rooms.s" .include "src/nonlinear/demos.s" +.if CUSTOM_DEMOS +.include "src/demos/demos-combined.s" +.endif .include "src/nonlinear/room-states.s" .include "src/nonlinear/main-missiles.s" .include "src/nonlinear/major-completion.s" diff --git a/tools/demo.lua b/tools/demo.lua index 4a1f95df..f3995a32 100644 --- a/tools/demo.lua +++ b/tools/demo.lua @@ -1,5 +1,14 @@ console.clear() memory.usememorydomain("System Bus") +CWD = io.popen("cd"):read() +MARS_DIR = CWD:gsub("tools$", "") + +if string.match(CWD, "(MARS[-]Fusion[\\|/]tools)$") == nil then + print([[ +This script is expected to run inside of the MARS-Fusion/tools directory. +Demos will not be saved if the script is not loaded from the correct location. + ]]) +end local addr = { ["CurrArea"] = 0x0300002C, @@ -27,6 +36,19 @@ local addr = { } +---Turns an table of u16le into table of u8 +---@param u16le_table table +---@return table +function table:u16le_to_u8(u16le_table) + local u8_table = {} + for n=1,(#u16le_table) do + u8_table[n*2], u8_table[(n*2) - 1] = u16le_table[n] >> 8, (u16le_table[n]) & 0xFF + end + return u8_table +end + + +---Loads relevant memory for saving to a demo local function LoadDemoMemory() CurrArea = memory.read_u8(addr["CurrArea"]) PrevDoor = memory.read_u8(addr["PrevDoor"]) @@ -48,6 +70,7 @@ local function LoadDemoMemory() end +---Initializes the script function Init() console.clear() InputQueue = {} @@ -55,6 +78,37 @@ function Init() PrevInput = 0 PrevFrames = 0 SubGameMode = memory.read_u16_le(addr["SubGameMode"]) + DemoNumber = 0 + DemoFolderExists, _, _ = os.rename(MARS_DIR.."data\\demos", MARS_DIR.."data\\demos") -- Hack to check for existing directory + AsmFolderExists, _, _ = os.rename(MARS_DIR.."src\\demos", MARS_DIR.."src\\demos") -- Hack to check for existing directory + + -- check for existing asm files, if found increment demo number and try again + while DemoNumber < 0xC do + -- break early if no demo folder + if DemoFolderExists == nil then + print("Could not find the demos data folder.") + break + elseif AsmFolderExists == nil then + print("Could not find the demos asm folder.") + break + end + + + local filename = io.open(string.format(MARS_DIR.."src\\demos\\demo-%X.s", DemoNumber), "r+") + if type(filename) == "userdata" then -- bizhawk calls files "userdata" for some reason + DemoNumber = DemoNumber + 1 + ---@diagnostic disable-next-line: undefined-field + filename:close() + else + print(string.format([[ +Demo files will be saved at ... +* Inputs & Durations: %sdata\demos\ +* ASM: %ssrc\demos\]], MARS_DIR,MARS_DIR) + ) + break + end + end + end @@ -68,21 +122,47 @@ end function OnExit(DemoFinished) gui.clearGraphics() if not DemoFinished then + print("This recording was not saved.") + return + + -- If demo data storage has been expanded/repointed this can be modified + elseif DemoNumber == 0xD then + print("The maximum number of recordings has been reached. This recording was not saved.") + return + + -- Demo or Asm folder was not found + elseif DemoFolderExists == nil or AsmFolderExists == nil then return end - local DemoInputDataStr = [[ -.org DemoInputData - .dw DemoInputs - .dh %d + local demoasmfile = assert(io.open(string.format(MARS_DIR.."src\\demos\\demo-%X.s", DemoNumber), "wb")) + local demoinputfile = assert(io.open(string.format(MARS_DIR.."data\\demos\\demo-%X-inputs.bin", DemoNumber), "wb")) + local demodurationfile = assert(io.open(string.format(MARS_DIR.."data\\demos\\demo-%X-durations.bin", DemoNumber), "wb")) + + local DemoAsmStr = [[ +.autoregion +.align 2 +@Demo]]..DemoNumber..[[Inputs: +.incbin "data/demos/demo-]]..DemoNumber..[[-inputs.bin" +.endautoregion + +.autoregion +.align 2 +@Demo]]..DemoNumber..[[Durations: +.incbin "data/demos/demo-]]..DemoNumber..[[-durations.bin" +.endautoregion + +.org DemoInputData + ]]..DemoNumber..[[ * 10h +.area 10h + .dw @Demo]]..DemoNumber..[[Inputs + .dh %04Xh .skip 2 - .dw DemoFrames - .dh %d + .dw @Demo]]..DemoNumber..[[Durations + .dh %04Xh .skip 2 -]] +.endarea - local DemoMemoryStr = [[ -.org DemoMemory +.org DemoMemory + ]]..DemoNumber..[[ * 1Ch .db %02Xh, %02Xh .db %02Xh, %02Xh .db %02Xh, %02Xh, %02Xh @@ -94,43 +174,41 @@ function OnExit(DemoFinished) .dh %04Xh, %04Xh, %04Xh ]] - print( - string.format( - DemoMemoryStr, - CurrArea, PrevDoor, - SecurityLevel, MapDownloads, - BeamUpgrades, ExplosiveUpgrades, SuitUpgrades, - StoryFlags, - MaxEnergy, CurrEnergy, - MaxMissiles, CurrMissiles, - MaxPowerBombs, CurrPowerBombs, - SamusDirection, SamusXPos, SamusYPos - ) - ) - - local inputStr = ".org DemoInputs\n\t.dh\t" - local frameStr = ".org DemoFrames\n\t.dh\t" table.insert(InputQueue, PrevInput) table.insert(FrameQueue, PrevFrames) - for i = 1, #InputQueue - 1 do - inputStr = inputStr .. string.format("%04Xh, ", InputQueue[i]) - frameStr = frameStr .. string.format("%d, ", FrameQueue[i]) - end + local demoinputs = table:u16le_to_u8(InputQueue) + local demodurations = table:u16le_to_u8(FrameQueue) - inputStr = inputStr .. string.format("%04Xh", InputQueue[#InputQueue]) - frameStr = frameStr .. string.format("%d", FrameQueue[#FrameQueue]) - print(inputStr.."\n") - -- print(#InputQueue) - print(frameStr.."\n") - -- print(#FrameQueue) - print( - string.format( - DemoInputDataStr, - #InputQueue * 2, - #FrameQueue * 2 - ) - ) + + print("Writing Input file ...") + demoinputfile:write(string.char(table.unpack(demoinputs))) + demoinputfile:flush() + demoinputfile:close() + print("Done!") + + print("Writing Duration file ...") + demodurationfile:write(string.char(table.unpack(demodurations))) + demodurationfile:flush() + demodurationfile:close() + print("Done!") + + print("Writing asm file ...") + demoasmfile:write(string.format( + DemoAsmStr, + #demoinputs, #demodurations, + CurrArea, PrevDoor, + SecurityLevel, MapDownloads, + BeamUpgrades, ExplosiveUpgrades, SuitUpgrades, + StoryFlags, + MaxEnergy, CurrEnergy, + MaxMissiles, CurrMissiles, + MaxPowerBombs, CurrPowerBombs, + SamusDirection, SamusXPos, SamusYPos + )) + demoasmfile:flush() + demoasmfile:close() + print("Done!") end @@ -172,29 +250,32 @@ gui.clearGraphics() Init() -LoadDemoMemory() -print("The Demo recording has started. Pause the game to stop the demo recording.") -gui.pixelText( - 168, 5, - " Recording\nin progress", - "white", - "red" -) - ---[[ - This behavior mimics transitioning from title screen to demo playback. - Demo Playback resets RNG to 0 before playing each demo. Here we reset RNG - to 0 so that if a new room loads, RNG should be consistent during playback - of the new recording. -]] -while true do - SubGameMode = memory.read_u16_le(addr["SubGameMode"]) - if SubGameMode ~= 1 then - emu.frameadvance() - ResetRNG() - else - break + +if DemoFolderExists ~= nil and AsmFolderExists ~= nil then -- no errors + LoadDemoMemory() + print("The Demo recording has started. Pause the game to stop the demo recording.") + gui.pixelText( + 168, 5, + " Recording\nin progress", + "white", + "red" + ) + --[[ + This behavior mimics transitioning from title screen to demo playback. + Demo Playback resets RNG to 0 before playing each demo. Here we reset RNG + to 0 so that if a new room loads, RNG should be consistent during playback + of the new recording. + ]] + while true do + SubGameMode = memory.read_u16_le(addr["SubGameMode"]) + if SubGameMode ~= 1 then + emu.frameadvance() + ResetRNG() + else + break + end end + end DemoFinished = false