diff --git a/Makefile b/Makefile index 39874e9280..c61be8c156 100644 --- a/Makefile +++ b/Makefile @@ -397,7 +397,8 @@ CPP_FILES := $(foreach dir,$(SRC_DIRS),$(wildcard $(dir)/*.cpp)) LIBZ_C_FILES := $(foreach dir,$(LIBZ_SRC_DIRS),$(wildcard $(dir)/*.c)) GODDARD_C_FILES := $(foreach dir,$(GODDARD_SRC_DIRS),$(wildcard $(dir)/*.c)) S_FILES := $(foreach dir,$(SRC_DIRS),$(wildcard $(dir)/*.s)) -GENERATED_C_FILES := $(BUILD_DIR)/assets/mario_anim_data.c $(BUILD_DIR)/assets/demo_data.c +GENERATED_C_FILES := $(BUILD_DIR)/assets/mario_anim_data.c +GENERATED_S_FILES := $(BUILD_DIR)/assets/demo_data.s # Ignore all .inc.c files C_FILES := $(filter-out %.inc.c,$(C_FILES)) @@ -421,6 +422,7 @@ O_FILES := $(foreach file,$(C_FILES),$(BUILD_DIR)/$(file:.c=.o)) \ $(foreach file,$(CPP_FILES),$(BUILD_DIR)/$(file:.cpp=.o)) \ $(foreach file,$(S_FILES),$(BUILD_DIR)/$(file:.s=.o)) \ $(foreach file,$(GENERATED_C_FILES),$(file:.c=.o)) \ + $(foreach file,$(GENERATED_S_FILES),$(file:.s=.o)) \ lib/PR/hvqm/hvqm2sp1.o lib/PR/hvqm/hvqm2sp2.o LIBZ_O_FILES := $(foreach file,$(LIBZ_C_FILES),$(BUILD_DIR)/$(file:.c=.o)) @@ -800,9 +802,9 @@ $(BUILD_DIR)/assets/mario_anim_data.c: $(wildcard assets/anims/*.inc.c) $(V)$(PYTHON) $(TOOLS_DIR)/mario_anims_converter.py > $@ # Generate demo input data -$(BUILD_DIR)/assets/demo_data.c: assets/demo_data.json $(wildcard assets/demos/*.bin) +$(BUILD_DIR)/assets/demo_data.s: $(wildcard assets/demos/*.s) @$(PRINT) "$(GREEN)Generating demo data $(NO_COL)\n" - $(V)$(PYTHON) $(TOOLS_DIR)/demo_data_converter.py assets/demo_data.json $(DEF_INC_CFLAGS) > $@ + $(V)$(PYTHON) $(TOOLS_DIR)/demo_data_converter.py assets/demos/ > $@ # Level headers $(BUILD_DIR)/include/level_headers.h: levels/level_headers.h.in @@ -838,6 +840,9 @@ $(BUILD_DIR)/%.o: $(BUILD_DIR)/%.c $(BUILD_DIR)/%.o: %.s $(call print,Assembling:,$<,$@) $(V)$(CROSS)gcc -c $(ASMFLAGS) $(foreach i,$(INCLUDE_DIRS),-Wa,-I$(i)) -x assembler-with-cpp -MMD -MF $(BUILD_DIR)/$*.d -o $@ $< +$(BUILD_DIR)/%.o: $(BUILD_DIR)/%.s + $(call print,Assembling:,$<,$@) + $(V)$(CROSS)gcc -c $(ASMFLAGS) -U_LANGUAGE_C $(foreach i,$(INCLUDE_DIRS),-Wa,-I$(i)) -x assembler-with-cpp -MMD -MF $(BUILD_DIR)/$*.d -o $@ $< # Assemble RSP assembly code $(BUILD_DIR)/rsp/%.bin $(BUILD_DIR)/rsp/%_data.bin: rsp/%.s diff --git a/assets.json b/assets.json index 9ceab3345b..f941d7c715 100644 --- a/assets.json +++ b/assets.json @@ -525,14 +525,6 @@ "actors/yoshi_egg/yoshi_egg_5_unused.rgba16.png": [32,32,2048,{"jp":[1215456,32696],"us":[1222624,32696],"eu":[1094592,32696],"sh":[1071104,32696]}], "actors/yoshi_egg/yoshi_egg_6_unused.rgba16.png": [32,32,2048,{"jp":[1215456,34744],"us":[1222624,34744],"eu":[1094592,34744],"sh":[1071104,34744]}], "actors/yoshi_egg/yoshi_egg_7_unused.rgba16.png": [32,32,2048,{"jp":[1215456,36792],"us":[1222624,36792],"eu":[1094592,36792],"sh":[1071104,36792]}], -"assets/demos/bbh.bin": [988,{"jp":[5733368],"us":[5741664],"eu":[5620584],"sh":[5589632]}], -"assets/demos/bitdw.bin": [1412,{"us":[5747100],"sh":[5595068]}], -"assets/demos/ccm.bin": [1320,{"jp":[5734356],"us":[5742652],"eu":[5621572],"sh":[5590620]}], -"assets/demos/hmc.bin": [980,{"jp":[5735676],"us":[5743972],"eu":[5622892],"sh":[5591940]}], -"assets/demos/jrb.bin": [620,{"jp":[5736656],"us":[5744952],"eu":[5623872],"sh":[5592920]}], -"assets/demos/pss.bin": [748,{"jp":[5737948],"us":[5746244],"eu":[5625164],"sh":[5594212]}], -"assets/demos/unused.bin": [108,{"jp":[5738696],"us":[5746992],"eu":[5625912],"sh":[5594960]}], -"assets/demos/wf.bin": [672,{"jp":[5737276],"us":[5745572],"eu":[5624492],"sh":[5593540]}], "levels/bbh/0.rgba16.png": [32,64,4096,{"jp":[3604960,0],"us":[3611712,0],"eu":[3485312,0],"sh":[3459680,0]}], "levels/bbh/1.rgba16.png": [32,32,2048,{"jp":[3604960,4096],"us":[3611712,4096],"eu":[3485312,4096],"sh":[3459680,4096]}], "levels/bbh/2.rgba16.png": [32,32,2048,{"jp":[3604960,6144],"us":[3611712,6144],"eu":[3485312,6144],"sh":[3459680,6144]}], diff --git a/assets/demo_data.json b/assets/demo_data.json deleted file mode 100644 index 3652b49071..0000000000 --- a/assets/demo_data.json +++ /dev/null @@ -1,49 +0,0 @@ -/* - * This file defines the demo data. It's parsed by tools/demo_data_converter.py. - * - * The "table" array declares the order of the demos and will be generated - * as pairs of (offset, size). - * Each item has a "demofile" property, which must reference a demofile - * in the "demofiles" array. - * "ifdef" is an optional array property which can be used to specify - * requirement of SM64 version. - * "extraSize" is an optional property which will be added the size of the - * demofile. - * - * The "demofiles" array declares the inclusion order of the demofiles. - * A file with the ".bin" extension with the "name" property as basename - * should exist in the assets/demos/ directory. - * "ifdef" is an optional array property which can be used to specify - * requirement of SM64 version. - */ - -{ - "table": [ - {"demofile":"bitdw", "ifdef":["VERSION_US", "VERSION_SH"]}, - - /* Whomp's Fortress has the wrong size. - The original entries probably manually input the sizes. */ - {"demofile":"wf", "extraSize":368}, - - {"demofile":"ccm"}, - {"demofile":"bbh"}, - {"demofile":"jrb"}, - {"demofile":"hmc"}, - {"demofile":"pss"} - ], - "demofiles": [ - {"name":"bbh"}, - {"name":"ccm"}, - {"name":"hmc"}, - {"name":"jrb"}, - {"name":"wf"}, - {"name":"pss"}, - - /* Might be an unused demo, but it doesn't define a header, - so it can't be normally called. Speculation: "blooper" take for CCM. - Mario runs into the sign and aligns himself as if it were a mistake. */ - {"name":"unused"}, - - {"name":"bitdw", "ifdef":["VERSION_US", "VERSION_SH"]} - ] -} diff --git a/assets/demos/README.md b/assets/demos/README.md new file mode 100644 index 0000000000..969ba6273e --- /dev/null +++ b/assets/demos/README.md @@ -0,0 +1,17 @@ +## Demos +To record a demo, a few steps have to be taken. +- Enable `DEMO_RECORDING_MODE` in `include/config/config_goddard.h`. +- Set a `START_LEVEL` or `TEST_LEVEL` in `config_game.h` or `config_debug.h`, respectively. +- Set `ISVPRINT=1` in the `Makefile` (or `UNF=1` if using a USB-enabled flashcart on console) +- Rebuild the repo, and launch an emulator with debug console support such as Parallel Launcher. + +The demo recording mode will boot into the level you set, from which you can start moving around and interacting with the level and camera. Press the Start button to end the demo + +To test the demo after it's done: + +- Enable `KEEP_MARIO_HEAD` and comment out `DISABLE_DEMO` and `DEMO_RECORDING_MODE` in `config_goddard.h`. +- If you set a `TEST_LEVEL`, comment that out too. +- Set `ISVPRINT=0` in the Makefile if you don't need debug printing anymore. +- Build the game again and wait on the title screen. + +To see demos faster on the title screen, edit `PRESS_START_DEMO_TIMER` in `src/game/demo_system.h`. The default is 800 frames (close to 27 seconds). \ No newline at end of file diff --git a/include/config/config_game.h b/include/config/config_game.h index 5df03c88e3..290f0adfab 100644 --- a/include/config/config_game.h +++ b/include/config/config_game.h @@ -100,3 +100,21 @@ * The levelscript needs to have a MARIO_POS command for this to work. */ #define START_LEVEL LEVEL_CASTLE_GROUNDS + +/** + * Allows demos to be played back, if they exist in assets/demos/ + */ +// #define ENABLE_DEMO_SYSTEM + +/** + * Boots directly to `TEST_LEVEL` (see config_debug.h) and prints inputs to a debug console. + * Press Start to end the recording. + * Copy the console output to a new file in `assets/demos/`. The name of the file should be printed at the top of the output. + * For emulator users (confirmed working in Parallel Launcher and Ares), `ISVPRINT` in the Makefile must be set to 1. + * For N64 testing with a USB-enabled flashcart, `UNF` in the Makefile must be set to 1. + * This define suppresses a few debug prints to keep the console output limited to just the file to save. + * + * If `TEST_LEVEL` is not set, this define will boot into `START_LEVEL`. + */ +// #define DEMO_RECORDING_MODE + diff --git a/include/config/config_goddard.h b/include/config/config_goddard.h index 42b0ef9b42..31afb4e95f 100644 --- a/include/config/config_goddard.h +++ b/include/config/config_goddard.h @@ -13,8 +13,3 @@ * Enables the Goddard easter egg from Shindou (has no effect if KEEP_MARIO_HEAD is disabled). */ #define GODDARD_EASTER_EGG - -/** - * Disables the demo that plays when idle on the start screen (has no effect if KEEP_MARIO_HEAD is disabled). - */ -#define DISABLE_DEMO diff --git a/include/config/config_safeguards.h b/include/config/config_safeguards.h index 8462ec10c2..b77087bf9e 100644 --- a/include/config/config_safeguards.h +++ b/include/config/config_safeguards.h @@ -139,7 +139,6 @@ #define FLYING_CAMERA_MODE CAMERA_MODE_BEHIND_MARIO #endif // !FLYING_CAMERA_MODE - /***************** * config_game.h */ @@ -166,11 +165,16 @@ #ifndef KEEP_MARIO_HEAD #undef GODDARD_EASTER_EGG - - #undef DISABLE_DEMO - #define DISABLE_DEMO + #undef ENABLE_DEMO_SYSTEM // Demos only play back on the title screen #endif // !KEEP_MARIO_HEAD +#ifdef DEMO_RECORDING_MODE + #ifndef TEST_LEVEL +// assume user is testing the start level + #define TEST_LEVEL START_LEVEL + #endif +#endif + /***************** * config_menu.h diff --git a/include/demo_macros.inc b/include/demo_macros.inc new file mode 100644 index 0000000000..905fe2575b --- /dev/null +++ b/include/demo_macros.inc @@ -0,0 +1,43 @@ +#pragma once +#include + +/* Demo Macros */ + +.macro stick x, y + .byte \x, \y +.endm + +.macro end_demo + .half 0 + .byte 0, 0 + .half 0 + .half 0 +.endm + +.macro for holdcount_frames + .half \holdcount_frames +.endm + +/* purely for legibility */ +#define frames + +.macro press buttonMask + .half \buttonMask + .half 0 +.endm + +/* Pretty names since pressing every button on a frame might overrun a buffer*/ +#define A A_BUTTON +#define B B_BUTTON +#define Z Z_TRIG +#define Start START_BUTTON +#define L L_TRIG +#define R R_TRIG + +#define C_Up U_CBUTTONS +#define C_Down D_CBUTTONS +#define C_Left L_CBUTTONS +#define C_Right R_CBUTTONS + +/* Macro for no button */ +#define _ 0 diff --git a/levels/entry.c b/levels/entry.c index 411a47f77e..98f9417fe7 100644 --- a/levels/entry.c +++ b/levels/entry.c @@ -14,7 +14,7 @@ const LevelScript level_script_entry[] = { INIT_LEVEL(), SLEEP(/*frames*/ 2), BLACKOUT(/*active*/ FALSE), -#ifdef TEST_LEVEL +#if defined(TEST_LEVEL) || defined(DEMO_RECORDING_MODE) SET_REG(/*value*/ TEST_LEVEL), EXECUTE(/*seg*/ SEGMENT_GLOBAL_LEVEL_SCRIPT, /*script*/ _scriptsSegmentRomStart, /*scriptEnd*/ _scriptsSegmentRomEnd, /*entry*/ level_main_scripts_entry), #else diff --git a/levels/scripts.c b/levels/scripts.c index c2cf6d8a4f..97a42e4a85 100644 --- a/levels/scripts.c +++ b/levels/scripts.c @@ -3,6 +3,9 @@ #include "game/level_update.h" #include "level_commands.h" #include "game/area.h" +#ifdef DEMO_RECORDING_MODE +#include "game/demo_system.h" +#endif // DEMO_RECORDING_MODE #include "make_const_nonconst.h" @@ -122,6 +125,10 @@ const LevelScript level_main_scripts_entry[] = { EXECUTE(/*seg*/ SEGMENT_MENU_INTRO, _menuSegmentRomStart, _menuSegmentRomEnd, level_main_menu_entry_act_select), JUMP_LINK(script_exec_level_table), SLEEP(/*frames*/ 1), +#ifdef DEMO_RECORDING_MODE + CALL(/*arg*/ 0, /*func*/ print_demo_footer), + SET_REG(WARP_SPECIAL_ENDING), +#endif // DEMO_RECORDING_MODE LOOP_UNTIL(/*op*/ OP_LT, /*arg*/ WARP_SPECIAL_NONE), JUMP_IF( /*op*/ OP_EQ, /*arg*/ WARP_SPECIAL_ENDING, goto_ending), JUMP_IF( /*op*/ OP_EQ, /*arg*/ WARP_SPECIAL_MARIO_HEAD_REGULAR, goto_mario_head_regular), diff --git a/sm64.ld b/sm64.ld index 3e7f1e3045..d5d38b5370 100755 --- a/sm64.ld +++ b/sm64.ld @@ -516,7 +516,9 @@ SECTIONS { KEEP(BUILD_DIR/assets/mario_anim_data.o(.data*)); KEEP(BUILD_DIR/assets/mario_anim_data.o(.rodata*)); +#ifdef ENABLE_DEMO_SYSTEM KEEP(BUILD_DIR/assets/demo_data.o(.data*)); +#endif /* ENABLE_DEMO_SYSTEM */ KEEP(BUILD_DIR/sound/sound_data.o(.data*)); } END_SEG(assets) diff --git a/src/audio/internal.h b/src/audio/internal.h index a70d556607..e350578e9a 100644 --- a/src/audio/internal.h +++ b/src/audio/internal.h @@ -68,7 +68,7 @@ enum Codecs { // Since u8 and u16 fit losslessly in both, behavior is the same. #define FLOAT_CAST(x) (f32) (s32) (x) -#if defined(ISVPRINT) || defined(UNF) +#if (!defined(DEMO_RECORDING_MODE)) && (defined(ISVPRINT) || defined(UNF)) #define stubbed_printf osSyncPrintf #else diff --git a/src/audio/load.c b/src/audio/load.c index c663d93c10..822d64cd48 100644 --- a/src/audio/load.c +++ b/src/audio/load.c @@ -886,6 +886,7 @@ void audio_init() { gAudioLoadLock = AUDIO_LOCK_NOT_LOADING; // Should probably contain the sizes of the data banks, but those aren't // easily accessible from here. +#ifndef DEMO_RECORDING_MODE osSyncPrintf("---------- Init Completed. ------------\n"); osSyncPrintf(" Syndrv :[%6d]\n", gSoundDataRaw - gSoundDataADSR); // gSoundDataADSR #ifndef VERSION_SH @@ -895,5 +896,6 @@ void audio_init() { #endif osSyncPrintf(" audiodata :[%6d]\n", gMusicData - gSoundDataRaw); // gSoundDataRaw osSyncPrintf("---------------------------------------\n"); +#endif // DEMO_RECORDING_MODE } #endif diff --git a/src/boot/main.c b/src/boot/main.c index 2fe93aaff3..000fe37f0c 100644 --- a/src/boot/main.c +++ b/src/boot/main.c @@ -358,7 +358,7 @@ void thread3_main(UNUSED void *arg) { debug_initialize(); #endif -#ifdef DEBUG +#if !defined(DEMO_RECORDING_MODE) && defined(DEBUG) osSyncPrintf("Super Mario 64\n"); #if 0 // if your PC username isn't your real name feel free to uncomment osSyncPrintf("Built by: %s\n", __username__); diff --git a/src/boot/memory.c b/src/boot/memory.c index 0066f8ba53..fac42601b9 100644 --- a/src/boot/memory.c +++ b/src/boot/memory.c @@ -75,7 +75,7 @@ uintptr_t set_segment_base_addr(s32 segment, void *addr) { return sSegmentTable[segment]; } -UNUSED void *get_segment_base_addr(s32 segment) { +void *get_segment_base_addr(s32 segment) { return (void *) (sSegmentTable[segment] | 0x80000000); } @@ -397,7 +397,10 @@ void *load_segment_decompress(s32 segment, u8 *srcStart, u8 *srcEnd) { dest = main_pool_alloc(*size, MEMORY_POOL_LEFT); #endif if (dest != NULL) { +#ifndef DEMO_RECORDING_MODE osSyncPrintf("start decompress\n"); +#endif // DEMO_RECORDING_MODE + #ifdef GZIP expand_gzip(compressed, dest, compSize, (u32)size); #elif RNC1 @@ -409,7 +412,10 @@ void *load_segment_decompress(s32 segment, u8 *srcStart, u8 *srcEnd) { #elif MIO0 decompress(compressed, dest); #endif + +#ifndef DEMO_RECORDING_MODE osSyncPrintf("end decompress\n"); +#endif // DEMO_RECORDING_MODE set_segment_base_addr(segment, dest); main_pool_free(compressed); } diff --git a/src/engine/math_util.c b/src/engine/math_util.c index cab6256591..40e6550155 100644 --- a/src/engine/math_util.c +++ b/src/engine/math_util.c @@ -27,6 +27,10 @@ Vec3s gVec3sOne = { 1, 1, 1 }; static u16 gRandomSeed16; +void set_random_seed(u16 seed) { + gRandomSeed16 = seed; +} + // Generate a pseudorandom integer from 0 to 65535 from the random seed, and update the seed. u16 random_u16(void) { if (gRandomSeed16 == 22026) { diff --git a/src/engine/math_util.h b/src/engine/math_util.h index 5554791afe..244c2d7da6 100644 --- a/src/engine/math_util.h +++ b/src/engine/math_util.h @@ -623,6 +623,7 @@ ALWAYS_INLINE s32 roundf(f32 in) { ((u32 *)(mtx))[15] = FLOAT_ONE; \ } +void set_random_seed(u16 seed); u16 random_u16(void); f32 random_float(void); s32 random_sign(void); diff --git a/src/game/demo_system.c b/src/game/demo_system.c new file mode 100644 index 0000000000..51b21846cf --- /dev/null +++ b/src/game/demo_system.c @@ -0,0 +1,279 @@ +#include +#include "types.h" +#include "segments.h" + +#include "config/config_debug.h" +#include "engine/math_util.h" +#include "demo_system.h" +#include "game_init.h" +#include "level_update.h" +#include "memory.h" +#include "save_file.h" + +#ifdef ENABLE_DEMO_SYSTEM +void *demoInputsMalloc = NULL; +u32 gCurrentDemoSize = 0; +u32 gCurrentDemoIdx = 0; +struct DemoFile gDemos[LEVEL_COUNT] ALIGNED8; +static u16 sDemoCountdown = 0; +u16 gDemoLevel = 0; +u16 gFinalDemoLevel = 0; +u8 gDemoActive = FALSE; + +void dma_new_demo_data() { + void *demoBank = get_segment_base_addr(SEGMENT_DEMO_INPUTS); + + u8 *romStart = gDemos[gDemoLevel].romStart + (sizeof(struct DemoInput) * gCurrentDemoIdx); + u8 *romEnd; + if (gCurrentDemoIdx + DEMO_BANK_INPUT_CAPACITY > gCurrentDemoSize) { + romEnd = gDemos[gDemoLevel].romEnd; + } + else { + romEnd = romStart + DEMO_INPUTS_POOL_SIZE; + } + + dma_read(demoBank, romStart, romEnd); +} + +/** + * If a demo sequence exists, this will run the demo input list until it is complete. + */ +void run_demo_inputs(void) { + if (gDemoActive == FALSE) return; + // Eliminate the unused bits. + gPlayer1Controller->controllerData->button &= VALID_BUTTONS; + + // Check if a demo inputs list exists and if so, + // run the active demo input list. + if (gCurrDemoInput != NULL) { + // The timer variable being 0 at the current input means the demo is over. + // Set the button to the END_DEMO mask to end the demo. + if (gCurrDemoInput->timer == 0) { + gPlayer1Controller->controllerData->stick_x = 0; + gPlayer1Controller->controllerData->stick_y = 0; + gPlayer1Controller->controllerData->button = END_DEMO; + gDemoActive = FALSE; + } else { + // Backup the start button if it is pressed, since we don't want the + // demo input to override the mask where start may have been pressed. + u16 startPushed = (gPlayer1Controller->controllerData->button & START_BUTTON); + + // Perform the demo inputs by assigning the current button mask and the stick inputs. + gPlayer1Controller->controllerData->stick_x = gCurrDemoInput->stickX; + gPlayer1Controller->controllerData->stick_y = gCurrDemoInput->stickY; + + // FEATURE + // In Vanilla SM64, only a limited amount of buttons are recorded, + // so that it fits in 8 bytes instead of 16. This saves ROM space. + // We do not care about this, since we are not constrained by ROM size + // Additionally, demos are simply not included in the final build if not used. + gPlayer1Controller->controllerData->button = gCurrDemoInput->buttonMask; + + // If start was pushed, put it into the demo sequence being input to end the demo. + gPlayer1Controller->controllerData->button |= startPushed; + + // Run the current demo input's timer down. if it hits 0, advance the demo input list. + if (--gCurrDemoInput->timer == 0) { + struct DemoInput *demoBank = get_segment_base_addr(SEGMENT_DEMO_INPUTS); + u8 needs_dma = (gCurrDemoInput == &demoBank[DEMO_BANK_INPUT_CAPACITY - 1]); + gCurrDemoInput++; + gCurrentDemoIdx++; + if (needs_dma) { + dma_new_demo_data(); + gCurrDemoInput = demoBank; + } + } + } + } +} +#endif // ENABLE_DEMO_SYSTEM + +/** + * If level is a valid value, tell the level script up the chain to jump there. + * If level is LEVEL_NONE, do nothing unless a demo is ready to play. + */ +s32 run_level_id_or_demo(s32 level) { + gCurrDemoInput = NULL; + + if (level == LEVEL_NONE) { +#ifdef ENABLE_DEMO_SYSTEM + if (!gPlayer1Controller->buttonDown && !gPlayer1Controller->stickMag) { + if ((++sDemoCountdown) >= PRESS_START_DEMO_TIMER) { + u32 demoCount = 0; + + // DMA in the Level Demo List + // Should always DMA in (LEVEL_COUNT * 8) bytes + dma_read((u8 *) &gDemos, demoFile, demoFileEnd); + + // Find a non-null demo in the list + // (If a demo played already, increment first before checking) + do { + if (gDemoLevel >= LEVEL_MAX) { + gDemoLevel = 0; + } + gDemoLevel++; + demoCount++; + // No demos installed in assets/demos/; continue playing the title screen + if (demoCount > (LEVEL_MAX * 2)) { + sDemoCountdown = 0; + return level; + } + } while (gDemos[gDemoLevel].romStart == NULL); + + // After gFinalDemoLevel's demo is done playing, the intro splash should play + // The vanilla functionality is to always assume PSS is the final demo. + for (int i = 0; i < LEVEL_COUNT; i++) { + if (gDemos[i].romStart != NULL) { + gFinalDemoLevel = i + 1; + } + } + + gCurrentDemoSize = (u32) gDemos[gDemoLevel].romEnd - (u32) gDemos[gDemoLevel].romStart; + gCurrentDemoIdx = 0; + dma_new_demo_data(); + struct DemoInput *demoBank = get_segment_base_addr(SEGMENT_DEMO_INPUTS); + + // Point the current input to the demo segment + gCurrDemoInput = demoBank; + level = gDemoLevel + 1; + gCurrSaveFileNum = 1; + gCurrActNum = 1; + sDemoCountdown = 0; + } + } else { // activity was detected, so reset the demo countdown. + sDemoCountdown = 0; + } +#endif // ENABLE_DEMO_SYSTEM + } + return level; +} + +#ifdef DEMO_RECORDING_MODE + +static u32 sDemoInputCount = 0; +static u8 sDemoRecordingActive = FALSE; + +void print_demo_input(struct DemoInput *d) { + char buttonStr[100]; + char *buttonPtr = buttonStr; + u16 button = d->buttonMask & ~(START_BUTTON); + + if (button == 0) { + sprintf(buttonStr, "_"); + } else { + + if (button & A_BUTTON) { + buttonPtr += sprintf(buttonPtr, "A | "); + } + if (button & B_BUTTON) { + buttonPtr += sprintf(buttonPtr, "B | "); + } + if (button & L_TRIG) { + buttonPtr += sprintf(buttonPtr, "L | "); + } + if (button & R_TRIG) { + buttonPtr += sprintf(buttonPtr, "R | "); + } + if (button & Z_TRIG) { + buttonPtr += sprintf(buttonPtr, "Z | "); + } + + if (button & U_CBUTTONS) { + buttonPtr += sprintf(buttonPtr, "C_Up | "); + } + if (button & D_CBUTTONS) { + buttonPtr += sprintf(buttonPtr, "C_Down | "); + } + if (button & L_CBUTTONS) { + buttonPtr += sprintf(buttonPtr, "C_Left | "); + } + if (button & R_CBUTTONS) { + buttonPtr += sprintf(buttonPtr, "C_Right | "); + } + + if (button & U_JPAD) { + buttonPtr += sprintf(buttonPtr, "U_JPAD | "); + } + if (button & D_JPAD) { + buttonPtr += sprintf(buttonPtr, "D_JPAD | "); + } + if (button & L_JPAD) { + buttonPtr += sprintf(buttonPtr, "L_JPAD | "); + } + if (button & R_JPAD) { + buttonPtr += sprintf(buttonPtr, "R_JPAD | "); + } + + u32 len = strlen(buttonStr); + buttonStr[len - 3] = 0; // Remove the trailing ' | ' + } + + char text[100]; + + sprintf(text, "for %3d frames; stick %4d, %4d; press %s\n", + d->timer, + d->stickX, + d->stickY, + buttonStr + ); + osSyncPrintf(text); +} + +// TODO: When libcart is merged, replace all these print functions +// with file i/o that automatically saves the file to the SD Card. +void print_demo_header() { + char header[500]; + sprintf(header, "#include \"demo_macros.inc\"\n \n"); + osSyncPrintf(header); + sDemoRecordingActive = TRUE; +} + +s32 print_demo_footer(UNUSED s32 arg) { + char footer[300]; + +#define STUB_LEVEL(_0, _1, _2, _3, _4, _5, _6, _7, _8) "stub_level", +#define DEFINE_LEVEL(_0, _1, _2, filename, _4, _5, _6, _7, _8, _9, _10) #filename, + // Level to String conversion for telling the player where to save the file + static char sLevel2Str[LEVEL_COUNT][20] = { + #include "levels/level_defines.h" + }; +#undef STUB_LEVEL +#undef DEFINE_LEVEL + + sprintf(footer, R"( +end_demo +/* Copy the above output to 'assets/demos/%s.s' */ +)", sLevel2Str[TEST_LEVEL - 1]); + osSyncPrintf(footer); + sDemoRecordingActive = FALSE; + return 0; +} + +void record_demo() { + // record the player's button mask and current rawStickX and rawStickY. + u16 buttonMask = gPlayer1Controller->buttonDown; + s8 stickX = gPlayer1Controller->rawStickX; + s8 stickY = gPlayer1Controller->rawStickY; + + // Rrecord the distinct input and timer so long as they are unique. + // If the timer hits 0xFFFF, reset the timer for the next demo input. + if (gRecordedDemoInput.timer == 0xFFFF || buttonMask != gRecordedDemoInput.buttonMask + || stickX != gRecordedDemoInput.stickX || stickY != gRecordedDemoInput.stickY) { + if (sDemoInputCount == 0) { + // Wait 4 frames in the demo so that the RNG lines up while recording and during playback. + gRecordedDemoInput.timer += 4; + } + if (sDemoRecordingActive) { + print_demo_input(&gRecordedDemoInput); + } + gRecordedDemoInput.timer = 0; + gRecordedDemoInput.buttonMask = buttonMask; + gRecordedDemoInput.stickX = stickX; + gRecordedDemoInput.stickY = stickY; + sDemoInputCount++; + } + gRecordedDemoInput.timer++; +} + +#endif // DEMO_RECORDING_MODE + diff --git a/src/game/demo_system.h b/src/game/demo_system.h new file mode 100644 index 0000000000..ba04eae2cc --- /dev/null +++ b/src/game/demo_system.h @@ -0,0 +1,33 @@ +#pragma once + +#include "types.h" +#include "level_update.h" +#include "level_table.h" + +#define DEMO_BANK_INPUT_CAPACITY (DEMO_INPUTS_POOL_SIZE / sizeof(struct DemoInput)) +#define PRESS_START_DEMO_TIMER 800 + +struct DemoFile { + void *romStart; + void *romEnd; +}; + +u8 player_action_reads_stick(struct MarioState *m); +void apply_demo_inputs_to_player(struct MarioState *m); + +s32 run_level_id_or_demo(s32 level); +s32 print_demo_footer(UNUSED s32 arg); + +void print_demo_header(); +void record_demo(); +void run_demo_inputs(void); + +extern void *demoInputsMalloc; +extern u32 gCurrentDemoSize; +extern u32 gCurrentDemoIdx; +extern struct DemoFile gDemos[LEVEL_COUNT]; +extern u8 demoFile[], demoFileEnd[]; +extern u16 gDemoLevel; +extern u16 gFinalDemoLevel; +extern u8 gDemoActive; + diff --git a/src/game/game_init.c b/src/game/game_init.c index bec0cfd3ec..601325a711 100644 --- a/src/game/game_init.c +++ b/src/game/game_init.c @@ -1,4 +1,5 @@ #include +#include #include "sm64.h" #include "gfx_dimensions.h" @@ -9,9 +10,11 @@ #include "buffers/zbuffer.h" #include "engine/level_script.h" #include "engine/math_util.h" +#include "demo_system.h" #include "game_init.h" #include "main.h" #include "memory.h" +#include "level_update.h" #include "save_file.h" #include "seq_ids.h" #include "sound_init.h" @@ -70,9 +73,7 @@ uintptr_t gPhysicalZBuffer; // Mario Anims and Demo allocation void *gMarioAnimsMemAlloc; -void *gDemoInputsMemAlloc; struct DmaHandlerList gMarioAnimsBuf; -struct DmaHandlerList gDemoInputsBuf; // General timer that runs as the game starts u32 gGlobalTimer = 0; @@ -94,7 +95,6 @@ struct Controller* const gPlayer4Controller = &gControllers[3]; // Title Screen Demo Handler struct DemoInput *gCurrDemoInput = NULL; -u16 gDemoInputListID = 0; struct DemoInput gRecordedDemoInput = { 0 }; // Display @@ -474,87 +474,6 @@ void display_and_vsync(void) { gGlobalTimer++; } -#if !defined(DISABLE_DEMO) && defined(KEEP_MARIO_HEAD) -// this function records distinct inputs over a 255-frame interval to RAM locations and was likely -// used to record the demo sequences seen in the final game. This function is unused. -UNUSED static void record_demo(void) { - // record the player's button mask and current rawStickX and rawStickY. - u8 buttonMask = - ((gPlayer1Controller->buttonDown & (A_BUTTON | B_BUTTON | Z_TRIG | START_BUTTON)) >> 8) - | (gPlayer1Controller->buttonDown & (U_CBUTTONS | D_CBUTTONS | L_CBUTTONS | R_CBUTTONS)); - s8 rawStickX = gPlayer1Controller->rawStickX; - s8 rawStickY = gPlayer1Controller->rawStickY; - - // If the stick is in deadzone, set its value to 0 to - // nullify the effects. We do not record deadzone inputs. - if (rawStickX > -8 && rawStickX < 8) { - rawStickX = 0; - } - - if (rawStickY > -8 && rawStickY < 8) { - rawStickY = 0; - } - - // Rrecord the distinct input and timer so long as they are unique. - // If the timer hits 0xFF, reset the timer for the next demo input. - if (gRecordedDemoInput.timer == 0xFF || buttonMask != gRecordedDemoInput.buttonMask - || rawStickX != gRecordedDemoInput.rawStickX || rawStickY != gRecordedDemoInput.rawStickY) { - gRecordedDemoInput.timer = 0; - gRecordedDemoInput.buttonMask = buttonMask; - gRecordedDemoInput.rawStickX = rawStickX; - gRecordedDemoInput.rawStickY = rawStickY; - } - gRecordedDemoInput.timer++; -} - -/** - * If a demo sequence exists, this will run the demo input list until it is complete. - */ -void run_demo_inputs(void) { - // Eliminate the unused bits. - gPlayer1Controller->controllerData->button &= VALID_BUTTONS; - - // Check if a demo inputs list exists and if so, - // run the active demo input list. - if (gCurrDemoInput != NULL) { - // The timer variable being 0 at the current input means the demo is over. - // Set the button to the END_DEMO mask to end the demo. - if (gCurrDemoInput->timer == 0) { - gPlayer1Controller->controllerData->stick_x = 0; - gPlayer1Controller->controllerData->stick_y = 0; - gPlayer1Controller->controllerData->button = END_DEMO; - } else { - // Backup the start button if it is pressed, since we don't want the - // demo input to override the mask where start may have been pressed. - u16 startPushed = (gPlayer1Controller->controllerData->button & START_BUTTON); - - // Perform the demo inputs by assigning the current button mask and the stick inputs. - gPlayer1Controller->controllerData->stick_x = gCurrDemoInput->rawStickX; - gPlayer1Controller->controllerData->stick_y = gCurrDemoInput->rawStickY; - - // To assign the demo input, the button information is stored in - // an 8-bit mask rather than a 16-bit mask. this is because only - // A, B, Z, Start, and the C-Buttons are used in a demo, as bits - // in that order. In order to assign the mask, we need to take the - // upper 4 bits (A, B, Z, and Start) and shift then left by 8 to - // match the correct input mask. We then add this to the masked - // lower 4 bits to get the correct button mask. - gPlayer1Controller->controllerData->button = - ((gCurrDemoInput->buttonMask & 0xF0) << 8) + ((gCurrDemoInput->buttonMask & 0xF)); - - // If start was pushed, put it into the demo sequence being input to end the demo. - gPlayer1Controller->controllerData->button |= startPushed; - - // Run the current demo input's timer down. if it hits 0, advance the demo input list. - if (--gCurrDemoInput->timer == 0) { - gCurrDemoInput++; - } - } - } -} - -#endif - /** * Take the updated controller struct and calculate the new x, y, and distance floats. */ @@ -607,8 +526,15 @@ void read_controller_inputs(s32 threadID) { release_rumble_pak_control(); #endif } -#if !defined(DISABLE_DEMO) && defined(KEEP_MARIO_HEAD) - run_demo_inputs(); +#ifdef DEMO_RECORDING_MODE + if (gMarioState != NULL) { + record_demo(); + } +#endif // DEMO_RECORDING_MODE +#if defined(ENABLE_DEMO_SYSTEM) && defined(KEEP_MARIO_HEAD) + if (gCurrDemoInput != NULL) { + run_demo_inputs(); + } #endif for (s32 cont = 0; cont < MAX_NUM_PLAYERS; cont++) { @@ -746,12 +672,17 @@ void setup_game_memory(void) { setup_dma_table_list(&gMarioAnimsBuf, gMarioAnims, gMarioAnimsMemAlloc); #ifdef PUPPYPRINT_DEBUG set_segment_memory_printout(SEGMENT_MARIO_ANIMS, MARIO_ANIMS_POOL_SIZE); +#ifdef ENABLE_DEMO_SYSTEM set_segment_memory_printout(SEGMENT_DEMO_INPUTS, DEMO_INPUTS_POOL_SIZE); -#endif - // Setup Demo Inputs List - gDemoInputsMemAlloc = main_pool_alloc(DEMO_INPUTS_POOL_SIZE, MEMORY_POOL_LEFT); - set_segment_base_addr(SEGMENT_DEMO_INPUTS, (void *) gDemoInputsMemAlloc); - setup_dma_table_list(&gDemoInputsBuf, gDemoInputs, gDemoInputsMemAlloc); +#endif // ENABLE_DEMO_SYSTEM +#endif // PUPPYPRINT_DEBUG + +#ifdef ENABLE_DEMO_SYSTEM + // Setup Demo Inputs Memory, otherwise save 0x800 bytes + demoInputsMalloc = main_pool_alloc(DEMO_INPUTS_POOL_SIZE, MEMORY_POOL_LEFT); + set_segment_base_addr(SEGMENT_DEMO_INPUTS, (void *) demoInputsMalloc); +#endif // ENABLE_DEMO_SYSTEM + // Setup Level Script Entry load_segment(SEGMENT_LEVEL_ENTRY, _entrySegmentRomStart, _entrySegmentRomEnd, MEMORY_POOL_LEFT, NULL, NULL); // Setup Segment 2 (Fonts, Text, etc) diff --git a/src/game/game_init.h b/src/game/game_init.h index 1258d73740..e5836f76a3 100644 --- a/src/game/game_init.h +++ b/src/game/game_init.h @@ -9,6 +9,7 @@ #include "types.h" #include "memory.h" #include "config.h" +#include "level_table.h" #define MARIO_ANIMS_POOL_SIZE 0x4000 #define DEMO_INPUTS_POOL_SIZE 0x800 @@ -19,10 +20,11 @@ struct GfxPool { }; struct DemoInput { - u8 timer; // time until next input. if this value is 0, it means the demo is over - s8 rawStickX; - s8 rawStickY; - u8 buttonMask; + u16 timer; // time until next input. if this value is 0, it means the demo is over + u8 stickX; + u8 stickY; + u16 buttonMask; + u16 pad; }; enum ZBmodes { @@ -41,7 +43,6 @@ extern struct VblankHandler gGameVblankHandler; extern uintptr_t gPhysicalFramebuffers[3]; extern uintptr_t gPhysicalZBuffer; extern void *gMarioAnimsMemAlloc; -extern void *gDemoInputsMemAlloc; extern struct SPTask *gGfxSPTask; extern Gfx *gDisplayListHead; extern u8 *gGfxPoolEnd; @@ -66,16 +67,13 @@ extern struct Controller* const gPlayer2Controller; extern struct Controller* const gPlayer3Controller; extern struct Controller* const gPlayer4Controller; extern struct DemoInput *gCurrDemoInput; -extern u16 gDemoInputListID; extern struct DemoInput gRecordedDemoInput; // this area is the demo input + the header. when the demo is loaded in, there is a header the size // of a single word next to the input list. this word is the current ID count. extern struct DmaHandlerList gMarioAnimsBuf; -extern struct DmaHandlerList gDemoInputsBuf; extern u8 gMarioAnims[]; -extern u8 gDemoInputs[]; extern u16 sRenderingFramebuffer; extern u32 gGlobalTimer; diff --git a/src/game/level_update.c b/src/game/level_update.c index b2e758ff73..d6baaafb62 100644 --- a/src/game/level_update.c +++ b/src/game/level_update.c @@ -6,6 +6,7 @@ #include "audio/external.h" #include "audio/synthesis.h" #include "level_update.h" +#include "demo_system.h" #include "game_init.h" #include "level_update.h" #include "main.h" @@ -1004,17 +1005,22 @@ void basic_update(void) { } s32 play_mode_normal(void) { -#ifndef DISABLE_DEMO +#ifdef ENABLE_DEMO_SYSTEM if (gCurrDemoInput != NULL) { print_intro_text(); if (gPlayer1Controller->buttonPressed & END_DEMO) { - level_trigger_warp(gMarioState, gCurrLevelNum == LEVEL_PSS ? WARP_OP_DEMO_END : WARP_OP_DEMO_NEXT); + level_trigger_warp(gMarioState, gCurrLevelNum == gFinalDemoLevel ? WARP_OP_DEMO_END : WARP_OP_DEMO_NEXT); } else if (!gWarpTransition.isActive && sDelayedWarpOp == WARP_OP_NONE && (gPlayer1Controller->buttonPressed & START_BUTTON)) { level_trigger_warp(gMarioState, WARP_OP_DEMO_NEXT); } } #endif +#ifdef DEMO_RECORDING_MODE + if (gPlayer1Controller->buttonPressed & START_BUTTON) { + warp_special(WARP_SPECIAL_ENDING); + } +#endif // DEMO_RECORDING_MODE warp_area(); check_instant_warp(); @@ -1270,6 +1276,9 @@ s32 init_level(void) { if (gPlayerSpawnInfos[0].areaIndex >= 0) { load_mario_area(); init_mario(); +#ifdef ENABLE_DEMO_SYSTEM + gDemoActive = TRUE; +#endif // ENABLE_DEMO_SYSTEM } if (gCurrentArea != NULL) { @@ -1323,6 +1332,16 @@ s32 init_level(void) { } append_puppyprint_log("Level loaded in %d" PP_CYCLE_STRING ".", (s32)(PP_CYCLE_CONV(osGetTime() - first))); + +#ifdef DEMO_RECORDING_MODE + print_demo_header(); + set_random_seed(0); +#else // DEMO_RECORDING_MODE + if (gCurrDemoInput != NULL) { + set_random_seed(0); + } +#endif // DEMO_RECORDING_MODE + return TRUE; } @@ -1400,6 +1419,11 @@ s32 lvl_set_current_level(UNUSED s16 initOrUpdate, s32 levelNum) { return FALSE; } +#ifdef DEMO_RECORDING_MODE + return FALSE; +#endif // DEMO_RECORDING_MODE + + if (gCurrLevelNum != LEVEL_BOWSER_1 && gCurrLevelNum != LEVEL_BOWSER_2 && gCurrLevelNum != LEVEL_BOWSER_3) { gMarioState->numCoins = 0; gHudDisplay.coins = 0; diff --git a/src/game/memory.h b/src/game/memory.h index e7ad8336ef..6dbf677a9a 100644 --- a/src/game/memory.h +++ b/src/game/memory.h @@ -42,6 +42,8 @@ struct DmaHandlerList { extern struct MemoryPool *gEffectsMemoryPool; +void dma_read(u8 *dest, u8 *srcStart, u8 *srcEnd); + uintptr_t set_segment_base_addr(s32 segment, void *addr); void *get_segment_base_addr(s32 segment); void *segmented_to_virtual(const void *addr); diff --git a/src/menu/title_screen.c b/src/menu/title_screen.c index b8ad053aaa..53da3919f0 100644 --- a/src/menu/title_screen.c +++ b/src/menu/title_screen.c @@ -3,6 +3,7 @@ #include "audio/external.h" #include "engine/math_util.h" #include "game/area.h" +#include "game/demo_system.h" #include "game/game_init.h" #include "game/level_update.h" #include "game/main.h" @@ -33,56 +34,10 @@ static char sLevelSelectStageNames[64][16] = { #undef DEFINE_LEVEL #ifdef KEEP_MARIO_HEAD -#ifndef DISABLE_DEMO -static u16 sDemoCountdown = 0; -#endif static s16 sPlayMarioGreeting = TRUE; static s16 sPlayMarioGameOver = TRUE; - -#ifndef DISABLE_DEMO -#define PRESS_START_DEMO_TIMER 800 - -/** - * Run the demo timer on the PRESS START screen after a number of frames. - * This function returns the level ID from the first byte of a demo file. - * It also returns the level ID from intro_regular (file select or level select menu) - */ -s32 run_level_id_or_demo(s32 level) { - gCurrDemoInput = NULL; - - if (level == LEVEL_NONE) { - if (!gPlayer1Controller->buttonDown && !gPlayer1Controller->stickMag) { - // start the demo. 800 frames has passed while - // player is idle on PRESS START screen. - if ((++sDemoCountdown) == PRESS_START_DEMO_TIMER) { - - // start the Mario demo animation for the demo list. - load_patchable_table(&gDemoInputsBuf, gDemoInputListID); - - // if the next demo sequence ID is the count limit, reset it back to - // the first sequence. - if (++gDemoInputListID == gDemoInputsBuf.dmaTable->count) { - gDemoInputListID = 0; - } - - // add 1 (+4) to the pointer to skip the first 4 bytes - // Use the first 4 bytes to store level ID, - // then use the rest of the values for inputs - gCurrDemoInput = ((struct DemoInput *) gDemoInputsBuf.bufTarget) + 1; - level = (s8)((struct DemoInput *) gDemoInputsBuf.bufTarget)->timer; - gCurrSaveFileNum = 1; - gCurrActNum = 1; - } - } else { // activity was detected, so reset the demo countdown. - sDemoCountdown = 0; - } - } - return level; -} -#endif #endif - u8 gLevelSelectHoldKeyIndex = 0; u8 gLevelSelectHoldKeyTimer = 0; @@ -190,24 +145,24 @@ s32 intro_regular(void) { if (gPlayer1Controller->buttonDown & L_TRIG) { gDebugLevelSelect = TRUE; } -#endif +#endif // DEBUG_LEVEL_SELECT if (gPlayer1Controller->buttonPressed & START_BUTTON) { play_sound(SOUND_MENU_STAR_SOUND, gGlobalSoundSource); #if ENABLE_RUMBLE queue_rumble_data(60, 70); queue_rumble_decay(1); -#endif +#endif // ENABLE_RUMBLE // calls level ID 100 (or 101 adding level select bool value) // defined in level_intro_mario_head_regular JUMP_IF commands // 100 is File Select - 101 is Level Select level = (LEVEL_FILE_SELECT + gDebugLevelSelect); sPlayMarioGreeting = TRUE; } -#if !defined(DISABLE_DEMO) && defined(KEEP_MARIO_HEAD) +#if defined(ENABLE_DEMO_SYSTEM) && defined(KEEP_MARIO_HEAD) return run_level_id_or_demo(level); #else return level; -#endif +#endif // ENABLE_DEMO_SYSTEM && KEEP_MARIO_HEAD } /** @@ -228,19 +183,19 @@ s32 intro_game_over(void) { #if ENABLE_RUMBLE queue_rumble_data(60, 70); queue_rumble_decay(1); -#endif +#endif // ENABLE_RUMBLE // same criteria as intro_regular level = LEVEL_FILE_SELECT + gDebugLevelSelect; sPlayMarioGameOver = TRUE; } -#if !defined(DISABLE_DEMO) && defined(KEEP_MARIO_HEAD) +#if defined(ENABLE_DEMO_SYSTEM) && defined(KEEP_MARIO_HEAD) return run_level_id_or_demo(level); #else return level; -#endif +#endif // ENABLE_DEMO_SYSTEM && KEEP_MARIO_HEAD } -#endif +#endif // KEEP_MARIO_HEAD /** * Plays the casual "It's a me mario" when the game stars. @@ -260,16 +215,16 @@ s32 lvl_intro_update(s16 arg, UNUSED s32 unusedArg) { #ifdef KEEP_MARIO_HEAD case LVL_INTRO_REGULAR: return intro_regular(); case LVL_INTRO_GAME_OVER: return intro_game_over(); -#else +#else // KEEP_MARIO_HEAD case LVL_INTRO_REGULAR: #ifdef DEBUG_LEVEL_SELECT if (gPlayer1Controller->buttonDown & L_TRIG) { gDebugLevelSelect = TRUE; } -#endif +#endif // DEBUG_LEVEL_SELECT // fallthrough case LVL_INTRO_GAME_OVER: return (LEVEL_FILE_SELECT + gDebugLevelSelect); -#endif +#endif // KEEP_MARIO_HEAD case LVL_INTRO_LEVEL_SELECT: return intro_level_select(); default: return LEVEL_NONE; } diff --git a/tools/demo_data_converter.py b/tools/demo_data_converter.py index 86ea94c5ea..0692f01624 100755 --- a/tools/demo_data_converter.py +++ b/tools/demo_data_converter.py @@ -1,78 +1,61 @@ #!/usr/bin/env python3 import sys -import re -import json +import os +import glob + +def usage(): + print(f"Usage: {sys.argv[0]} path/to/demo/folder/") def main(): - need_help = False - defines = [] - skip_next = 0 - prog_args = [] - for i, a in enumerate(sys.argv[1:], 1): - if skip_next > 0: - skip_next -= 1 - continue - if a == "--help" or a == "-h": - need_help = True - if a == "-D": - defines.append(sys.argv[i + 1]) - skip_next = 1 - elif a.startswith("-D"): - defines.append(a[2:]) - else: - prog_args.append(a) + if len(sys.argv) != 2: + usage() + sys.exit(1) - defines = [d.split("=")[0] for d in defines] + demo_folder = sys.argv[1] + demo_files = glob.glob(f"{demo_folder}/*.s") + available_levels = [os.path.basename(i).split(".")[0] for i in demo_files] - if len(prog_args) < 1 or need_help: - print("Usage: {} [-D ] > ".format(sys.argv[0])) - sys.exit(0 if need_help else 1) - - with open(prog_args[0], "r") as file: - descr = json.loads(re.sub(r"/\*[\w\W]*?\*/", "", file.read())) - - table = [] - for item in descr["table"]: - if not "ifdef" in item or any(d in defines for d in item["ifdef"]): - table.append(item) - - demofiles = [] - for item in descr["demofiles"]: - if not "ifdef" in item or any(d in defines for d in item["ifdef"]): - demofiles.append(item) - - structdef = ["u32 numEntries;", - "const void *addrPlaceholder;", - "struct OffsetSizePair entries[" + str(len(table)) + "];"] - structobj = [str(len(table)) + ",", - "NULL,"] + # Get available levels + level_list = [] + stub_counter = 0 + with open("levels/level_defines.h") as levelfile: + for line in levelfile: + if line.startswith("DEFINE_LEVEL("): + level_list.append(line.split(",")[3].strip()) + elif line.startswith("STUB_LEVEL("): + level_list.append(f"stub_{stub_counter}") + stub_counter += 1 + + # Check that demo files actually correspond to a level + for level, filename in zip(available_levels, demo_files): + if level not in level_list: + print(f"Unknown Demo at {filename} - '{level}' is not a level name", file=sys.stderr) + sys.exit(1) + + print('#include ') + print('#include "macros.inc"') + print('#include "demo_macros.inc"') + print() + + print(".section .data") + print("glabel demoFile") + + for level in level_list: + if level in available_levels: + print(f".word demo_{level}_start, demo_{level}_end") + else: + print(f".word 0, 0") - structobj.append("{") - for item in table: - offset_to_data = "offsetof(struct DemoInputsObj, " + item["demofile"] + ")" - size = "sizeof(gDemoInputs." + item["demofile"] + ")" - if "extraSize" in item: - size += " + " + str(item["extraSize"]) - structobj.append("{" + offset_to_data + ", " + size + "},") - structobj.append("},") + print("glabel demoFileEnd") + print() - for item in demofiles: - with open("assets/demos/" + item["name"] + ".bin", "rb") as file: - demobytes = file.read() - structdef.append("u8 " + item["name"] + "[" + str(len(demobytes)) + "];") - structobj.append("{" + ",".join(hex(x) for x in demobytes) + "},") + # start actual data counting + for file, name in zip(demo_files, available_levels): + print(f"glabel demo_{name}_start") + print(f'#include "{file}"') + print(f"glabel demo_{name}_end") - print("#include \"game/memory.h\"") - print("#include ") - print("") - print("struct DemoInputsObj {") - for s in structdef: - print(s) - print("} gDemoInputs = {") - for s in structobj: - print(s) - print("};") if __name__ == "__main__": main()