diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000..9566f7f --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,38 @@ +// For format details, see https://aka.ms/devcontainer.json. For config options, see the +// README at: https://github.com/devcontainers/templates/tree/main/src/docker-existing-dockerfile +{ + "name": "Existing Dockerfile", + "build": { + // Sets the run context to one level up instead of the .devcontainer folder. + "context": "..", + // Update the 'dockerFile' property if you aren't using the standard 'Dockerfile' filename. + "dockerfile": "../Dockerfile" + }, + "runArgs": ["--privileged", "--add-host=host.docker.internal:host-gateway"], + "customizations": { + "vscode": { + "extensions": [ + "ms-vscode.cpptools", + "ms-vscode.cpptools-extension-pack", + "ms-azuretools.vscode-docker", + "ms-python.python", + "ms-vscode.hexeditor" + ] + } + }, + + // Features to add to the dev container. More info: https://containers.dev/features. + // "features": {}, + + // Use 'forwardPorts' to make a list of ports inside the container available locally. + // "forwardPorts": [3333], + + // Uncomment the next line to run commands after the container is created. + "postCreateCommand": "python3 ./build.py -c -b" + + // Configure tool-specific properties. + // "customizations": {}, + + // Uncomment to connect as an existing user other than the container default. More info: https://aka.ms/dev-containers-non-root. + // "remoteUser": "devcontainer" +} diff --git a/.gitignore b/.gitignore index 259148f..80fda77 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,10 @@ +*.nds +build/ +*_readelf.txt +.ninja_deps +.ninja_log +build.ninja + # Prerequisites *.d diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json new file mode 100644 index 0000000..e8a7ff2 --- /dev/null +++ b/.vscode/c_cpp_properties.json @@ -0,0 +1,36 @@ +{ + "configurations": [ + { + "name": "NDS (Linux)", + "includePath": [ + "${workspaceFolder}", + "${workspaceFolder}/libs/**", + "/opt/wonderful/toolchain/gcc-arm-none-eabi/include/**", + "/opt/wonderful/thirdparty/blocksds/core/libs/dswifi/include/**", + "/opt/wonderful/thirdparty/blocksds/core/libs/libnds/include/**", + "/opt/wonderful/thirdparty/blocksds/core/libs/maxmod/include" + ], + "defines": [ + "_DEBUG", + "_UNICODE", + "ARM7", + "ARM9" + ], + "browse": { + "path": [ + "${workspaceFolder}", + "${workspaceFolder}/libs/**", + "/opt/wonderful/toolchain/gcc-arm-none-eabi/include/**", + "/opt/wonderful/thirdparty/blocksds/core/libs/libnds/include/**" + ], + "limitSymbolsToIncludedHeaders": true, + "databaseFilename": "" + }, + "cStandard": "c11", + "cppStandard": "c++20", + "compilerPath": "/opt/wonderful/toolchain/gcc-arm-none-eabi/bin/arm-none-eabi-g++", + "intelliSenseMode": "gcc-arm" + } + ], + "version": 4 +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..9e27cf1 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,6 @@ +{ + "files.associations": { + "memory": "cpp", + "unistd.h": "c" + } +} \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..124ec5a --- /dev/null +++ b/Dockerfile @@ -0,0 +1,14 @@ +FROM skylyrac/blocksds:slim-latest + +RUN apt update && \ + apt install python3-full python3-pip ninja-build wget libxxf86vm1 libxfixes3 libxi6 libxkbcommon-x11-0 libsm6 libgl1 libopengl-dev unzip -y + +RUN git clone https://github.com/AntonioND/architectds.git && \ + cd ./architectds && \ + pip3 install wheel setuptools --break-system-packages && \ + python3 setup.py bdist_wheel && \ + pip3 install dist/architectds-*-py3-none-any.whl --break-system-packages && \ + cd ../ && \ + rm -rf architectds + +ENTRYPOINT [ "/bin/bash" ] \ No newline at end of file diff --git a/build.py b/build.py new file mode 100644 index 0000000..d7fd34e --- /dev/null +++ b/build.py @@ -0,0 +1,32 @@ +from architectds import * + +arm9 = Arm9Binary( + sourcedirs=['src/arm9'], + libs=['nds9'], + cxxflags='-Werror -Wno-psabi -fpermissive -std=gnu++20', + libdirs=['${BLOCKSDS}/libs/libnds'] +) +arm9.generate_elf() + +example_lib = Arm9DynamicLibrary( + name='example', + main_binary=arm9, + sourcedirs=['src/lib_example'], + cxxflags='-Werror -Wno-psabi -fpermissive -std=gnu++20', + libdirs=['${BLOCKSDS}/libs/libnds'] +) +example_lib.generate_dsl() + +nitrofs = NitroFS() +nitrofs.add_arm9_dsl(example_lib) +nitrofs.generate_image() + +nds = NdsRom( + binaries=[arm9, example_lib, nitrofs], + nds_path='dyn_lib_bug_min_repro.nds', + game_title='Dynamic Library', + game_subtitle='Bug Repro' +) +nds.generate_nds() + +nds.run_command_line_arguments() \ No newline at end of file diff --git a/src/arm9/loader.cpp b/src/arm9/loader.cpp new file mode 100644 index 0000000..ba1a207 --- /dev/null +++ b/src/arm9/loader.cpp @@ -0,0 +1,84 @@ +#include "loader.hpp" + +typedef bool (fnIsFlagSet)(Save *, u16); +typedef void (fnSetFlag)(Save *, u16); +typedef void (fnClearFlag)(Save *, u16); +typedef u8 (fnGetGlobal)(Save *, u16); +typedef void (fnSetGlobal)(Save *, u16, u8); + +typedef int (fnGetMemUsed)(); +typedef int (fnGetMemFree)(); + +fnIsFlagSet *isFlagSetFn; +fnSetFlag *setFlagFn; +fnClearFlag *clearFlagFn; +fnGetGlobal *getGlobalFn; +fnSetGlobal *setGlobalFn; + +fnGetMemUsed *getMemUsedFn; +fnGetMemFree *getMemFreeFn; + +namespace Loader +{ + void *exampleLib; + + void loadExampleLib() + { + exampleLib = dlopen("nitro:/dsl/example.dsl", RTLD_NOW | RTLD_LOCAL); + + isFlagSetFn = (fnIsFlagSet *)dlsym(exampleLib, "isFlagSet"); + setFlagFn = (fnSetFlag *)dlsym(exampleLib, "setFlag"); + clearFlagFn = (fnClearFlag *)dlsym(exampleLib, "clearFlag"); + getGlobalFn = (fnGetGlobal *)dlsym(exampleLib, "getGlobal"); + setGlobalFn = (fnSetGlobal *)dlsym(exampleLib, "setGlobal"); + + getMemUsedFn = (fnGetMemUsed *)dlsym(exampleLib, "getMemUsed"); + getMemFreeFn = (fnGetMemFree *)dlsym(exampleLib, "getMemFree"); + } + + void unloadExmapleLib() + { + dlclose(exampleLib); + } +} + +namespace SaveExtensions +{ + bool isFlagSet(Save *save, u16 flag) + { + return isFlagSetFn(save, flag); + } + + void setFlag(Save *save, u16 flag) + { + setFlagFn(save, flag); + } + + void clearFlag(Save *save, u16 flag) + { + clearFlagFn(save, flag); + } + + u8 getGlobal(Save *save, u16 global) + { + return getGlobalFn(save, global); + } + + void setGlobal(Save *save, u16 global, u8 value) + { + setGlobalFn(save, global, value); + } +} + +namespace Debug +{ + int getMemUsed() + { + return getMemUsedFn(); + } + + int getMemFree() + { + return getMemFreeFn(); + } +} \ No newline at end of file diff --git a/src/arm9/loader.hpp b/src/arm9/loader.hpp new file mode 100644 index 0000000..a6f69e7 --- /dev/null +++ b/src/arm9/loader.hpp @@ -0,0 +1,26 @@ +#pragma once + +#include + +#include "save.hpp" + +namespace Loader +{ + void loadExampleLib(); + void unloadExmapleLib(); +} + +namespace SaveExtensions +{ + bool isFlagSet(Save *save, u16 flag); + void setFlag(Save *save, u16 flag); + void clearFlag(Save *save, u16 flag); + u8 getGlobal(Save *save, u16 global); + void setGlobal(Save *save, u16 global, u8 value); +} + +namespace Debug +{ + int getMemUsed(); + int getMemFree(); +} \ No newline at end of file diff --git a/src/arm9/main.cpp b/src/arm9/main.cpp new file mode 100644 index 0000000..364cbbc --- /dev/null +++ b/src/arm9/main.cpp @@ -0,0 +1,73 @@ +#include "main.hpp" + +void wait_forever(void) +{ + while (1) + swiWaitForVBlank(); +} + +int main() +{ + defaultExceptionHandler(); + + PrintConsole topScreen; + PrintConsole bottomScreen; + + videoSetMode(MODE_0_2D); + videoSetModeSub(MODE_0_2D); + + vramSetBankA(VRAM_A_MAIN_BG); + vramSetBankC(VRAM_C_SUB_BG); + + consoleInit(&topScreen, 3, BgType_Text4bpp, BgSize_T_256x256, 31, 0, true, true); + consoleInit(&bottomScreen, 3, BgType_Text4bpp, BgSize_T_256x256, 31, 0, false, true); + + consoleSelect(&topScreen); + + bool init_ok = nitroFSInit(NULL); + if (!init_ok) + { + perror("nitroFSInit()"); + wait_forever(); + } + // Initialize FAT for save files + if (!fatInitDefault()) + { + perror("fatInitDefault()"); + wait_forever(); + } + swiWaitForVBlank(); + + printf("Creating save file...\n"); + save = new Save(); + + printf("Loading example library..."); + Loader::loadExampleLib(); + + SaveExtensions::setFlag(save, 5); + if (SaveExtensions::isFlagSet(save, 5)) + { + printf("Flag 5 was set."); + } + SaveExtensions::setGlobal(save, 5, 20); + printf("Global 5 was set to %d", SaveExtensions::getGlobal(save, 5)); + + printf("Mem used: %d\nMem free: %d", Debug::getMemUsed(), Debug::getMemFree()); + + printf("Unloading example lib..."); + Loader::unloadExmapleLib(); + + printf("Press START to exit to loader\n"); + + while (1) + { + swiWaitForVBlank(); + + scanKeys(); + + if (keysHeld() & KEY_START) + break; + } + + return 0; +} \ No newline at end of file diff --git a/src/arm9/main.hpp b/src/arm9/main.hpp new file mode 100644 index 0000000..22e5895 --- /dev/null +++ b/src/arm9/main.hpp @@ -0,0 +1,24 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "loader.hpp" +#include "save.hpp" + +Save *save; + +int main(); +void update(); +void render(); \ No newline at end of file diff --git a/src/arm9/save.cpp b/src/arm9/save.cpp new file mode 100644 index 0000000..2dd7ba1 --- /dev/null +++ b/src/arm9/save.cpp @@ -0,0 +1,106 @@ +#include "save.hpp" + +// Make things unnecessarily complicated so we can use C++ stuff lol +void debugLog(string str) +{ + printf(format("{}\n", str).c_str()); +} + +string SaveManager::getSavePath() +{ + string defaultDrive = string(fatGetDefaultDrive()); + if (defaultDrive == "") + { + defaultDrive = "fat:/"; + } + string path = format("{}into-the-dream-spring.sav", defaultDrive); + return path; +} + +void SaveManager::initializeSave(string path) +{ + debugLog("Initializing save file..."); + FILE *newSaveFile = fopen(path.c_str(), "wb"); + u8 *cleanData = new u8[NUM_SAVE_SLOTS * SAVE_SLOT_SIZE]; + fwrite(cleanData, sizeof(u8), NUM_SAVE_SLOTS * SAVE_SLOT_SIZE, newSaveFile); + fclose(newSaveFile); + delete cleanData; +} + +void SaveManager::checkSave(string path) +{ + if (access(path.c_str(), F_OK) != 0) + { + debugLog("Save file does not exist; initializing..."); + initializeSave(path); + return; + } + FILE *saveFile = fopen(path.c_str(), "rb"); + fseek(saveFile, 0, SEEK_END); + long length = ftell(saveFile); + if (length != NUM_SAVE_SLOTS * SAVE_SLOT_SIZE) + { + debugLog(format("Save file was wrong size {:d} bytes; re-initializing...", length)); + fclose(saveFile); + initializeSave(path); + } +} + +void SaveManager::save(int slot, Save *save) +{ + string path = getSavePath(); + debugLog(format("Attempting to save to {}...", path)); + checkSave(path); + + FILE *saveFile = fopen(path.c_str(), "rb+"); + fseek(saveFile, slot * SAVE_SLOT_SIZE, SEEK_SET); + + int flagWrite = fwrite(save->getSaveSlot()->flags, sizeof(u8), 512, saveFile); + debugLog(format("Wrote {:d} bytes of flags", flagWrite)); + int globalWrite = fwrite(save->getSaveSlot()->globals, sizeof(u8), 512, saveFile); + debugLog(format("Wrote {:d} bytes of globals", globalWrite)); + + fclose(saveFile); + debugLog("Saved!"); +} + +Save *SaveManager::load(int slot) +{ + string path = getSavePath(); + debugLog(format("Attempting to load from {}...", path)); + checkSave(path); + + FILE *saveFile = fopen(path.c_str(), "rb"); + fseek(saveFile, slot * SAVE_SLOT_SIZE, SEEK_SET); + + Save *save = new Save(); + + int flagRead = fread(save->getSaveSlot()->flags, sizeof(u8), 512, saveFile); + debugLog(format("Read {:d} bytes of flags", flagRead)); + int globalRead = fwrite(save->getSaveSlot()->globals, sizeof(u8), 512, saveFile); + debugLog(format("Read {:d} bytes of globals", globalRead)); + + fclose(saveFile); + + return save; +} + +Save::Save() +{ + this->saveSlot = new SaveSlot(); +} + +Save::Save(SaveSlot *save) +{ + this->saveSlot = save; +} + +Save::~Save() +{ + delete saveSlot; +} + +SaveSlot *Save::getSaveSlot() +{ + return this->saveSlot; +} \ No newline at end of file diff --git a/src/arm9/save.hpp b/src/arm9/save.hpp new file mode 100644 index 0000000..07eb343 --- /dev/null +++ b/src/arm9/save.hpp @@ -0,0 +1,53 @@ +#pragma once + +#include +#include + +#include +#include +#include + +using namespace std; + +#define NUM_SAVE_SLOTS 30 +#define SAVE_SLOT_SIZE 1024 + +struct SaveSlot +{ + u8 *flags; + u8 *globals; + + SaveSlot() + { + flags = new u8[512]; + globals = new u8[512]; + } + ~SaveSlot() + { + delete flags; + delete globals; + } +}; + +class Save +{ + private: + SaveSlot *saveSlot; + + public: + Save(); + Save(SaveSlot *save); + ~Save(); + SaveSlot *getSaveSlot(); +}; + +class SaveManager +{ +private: + static string getSavePath(); + static void initializeSave(string path); + static void checkSave(string path); +public: + static void save(int slot, Save *save); + static Save *load(int slot); +}; \ No newline at end of file diff --git a/src/lib_example/debug.c b/src/lib_example/debug.c new file mode 100644 index 0000000..b35a72a --- /dev/null +++ b/src/lib_example/debug.c @@ -0,0 +1,37 @@ +#include +#include +#include + +#define SYM_PUBLIC __attribute__((visibility ("default"))) + +extern u8 *fake_heap_end; // current heap start +extern u8 *fake_heap_start; // current heap end + +u8* getHeapStart() +{ + return fake_heap_start; +} + +u8* getHeapEnd() +{ + return (u8*)sbrk(0); +} + +u8* getHeapLimit() +{ + return fake_heap_end; +} + +// returns the amount of used memory in bytes +SYM_PUBLIC int getMemUsed() +{ + struct mallinfo mi = mallinfo(); + return mi.uordblks; +} + +// returns the amount of free memory in bytes +SYM_PUBLIC int getMemFree() +{ + struct mallinfo mi = mallinfo(); + return mi.fordblks + (getHeapLimit() - getHeapEnd()); +} \ No newline at end of file diff --git a/src/lib_example/save_extensions.cpp b/src/lib_example/save_extensions.cpp new file mode 100644 index 0000000..f5fb4fa --- /dev/null +++ b/src/lib_example/save_extensions.cpp @@ -0,0 +1,40 @@ +#include "../arm9/save.hpp" + +#define SYM_PUBLIC __attribute__((visibility ("default"))) + +void setOrClearFlag(Save *save, u16 flag, bool set) +{ + flag--; + int byte = flag >> 3; + int bit = flag & 7; + + if (set) + { + save->getSaveSlot()->flags[byte] = save->getSaveSlot()->flags[byte] | (1 << bit); + } + else + { + save->getSaveSlot()->flags[byte] = save->getSaveSlot()->flags[byte] & ~(1 << bit); + } +} +SYM_PUBLIC bool isFlagSet(Save *save, u16 flag) +{ + flag--; + return (save->getSaveSlot()->flags[flag >> 3] >> (flag & 7)) & 1; +} +SYM_PUBLIC void setFlag(Save *save, u16 flag) +{ + setOrClearFlag(save, flag, true); +} +SYM_PUBLIC void clearFlag(Save *save, u16 flag) +{ + setOrClearFlag(save, flag, false); +} +SYM_PUBLIC u8 getGlobal(Save *save, u16 global) +{ + return save->getSaveSlot()->globals[--global]; +} +SYM_PUBLIC void setGlobal(Save *save, u16 global, u8 value) +{ + save->getSaveSlot()->globals[--global] = value; +} \ No newline at end of file