Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
Empty file added data/demos/.gitkeep
Empty file.
9 changes: 9 additions & 0 deletions inc/structs.inc
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Empty file added src/demos/.gitkeep
Empty file.
7 changes: 7 additions & 0 deletions src/main.s
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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"
Expand Down
209 changes: 145 additions & 64 deletions tools/demo.lua
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -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"])
Expand All @@ -48,13 +70,45 @@ local function LoadDemoMemory()
end


---Initializes the script
function Init()
console.clear()
InputQueue = {}
FrameQueue = {}
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


Expand All @@ -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
Expand All @@ -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


Expand Down Expand Up @@ -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
Expand Down