diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 04e23855..eba2b919 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -419,3 +419,43 @@ jobs: token: ${{ secrets.GITHUB_TOKEN }} tag_name: ${{ steps.extract_tag.outputs.tag_name }} files: projects/*.zip + + build-switch: + runs-on: ubuntu-latest + container: devkitpro/devkita64:latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4.1.7 + + - name: Install Switch toolchain deps + run: | + apt-get update && apt-get install -y python3-pil jq + git config --global --add safe.directory "$GITHUB_WORKSPACE" + + - name: Build Switch + working-directory: projects + run: make PLATFORM=SWITCH + + - name: Package build + working-directory: projects + run: | + TAG=$(curl "https://api.github.com/repos/djdiskmachine/lgpt-resources/tags" | jq -r '.[0].name') + curl -L -o lgpt-resources.zip https://github.com/djdiskmachine/lgpt-resources/archive/refs/tags/${TAG}.zip + unzip lgpt-resources.zip + mv lgpt-resources-${TAG}/*/ ./resources/packaging + rm -rf lgpt-resources* + ./resources/packaging/lgpt_package.sh + + - name: Extract Git tag name + id: extract_tag + run: echo "tag_name=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT + env: + GITHUB_REF: ${{ github.ref }} + + - name: Upload build release + uses: softprops/action-gh-release@v2.4.2 + with: + token: ${{ secrets.GITHUB_TOKEN }} + tag_name: ${{ steps.extract_tag.outputs.tag_name }} + files: ./projects/*.zip diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index b887ff40..2b007560 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -408,3 +408,37 @@ jobs: name: LGPT-${{ github.job }}-${{ github.sha }}.zip path: projects/*.zip if-no-files-found: error + + switch: + runs-on: ubuntu-latest + container: devkitpro/devkita64:latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4.1.7 + + - name: Install Switch toolchain deps + run: | + apt-get update && apt-get install -y python3-pil jq + git config --global --add safe.directory "$GITHUB_WORKSPACE" + + - name: Build Switch + working-directory: projects + run: make PLATFORM=SWITCH + + - name: Package build + working-directory: projects + run: | + TAG=$(curl "https://api.github.com/repos/djdiskmachine/lgpt-resources/tags" | jq -r '.[0].name') + curl -L -o lgpt-resources.zip https://github.com/djdiskmachine/lgpt-resources/archive/refs/tags/${TAG}.zip + unzip lgpt-resources.zip + mv lgpt-resources-${TAG}/*/ ./resources/packaging + rm -rf lgpt-resources* + ./resources/packaging/lgpt_package.sh + + - name: Upload artifact + uses: actions/upload-artifact@v4 + with: + name: LGPT-${{ github.job }}-${{ github.sha }}.zip + path: projects/*.zip + if-no-files-found: error diff --git a/.gitignore b/.gitignore index 5d849f6f..466ec9de 100644 --- a/.gitignore +++ b/.gitignore @@ -26,4 +26,7 @@ projects/lgpt-bittboy.elf projects/lgpt-garlic.elf projects/lgpt-garlicplus.elf projects/lgpt-rg35xxplus.elf +projects/lgpt-switch.nro +projects/lgpt-switch.nacp projects/LittleGPTracker.app +projects/LGPT-*.zip diff --git a/CHANGELOG b/CHANGELOG index 7145fd49..409c6f15 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,26 @@ +1.6.0-bacon18 Migrate W32 to SDL2, removing need for building in vs2008 + Nintendo Switch support + * Homebrew .nro built with devkitPro/libnx, launched from hbmenu + * Installs to /switch/lgpt on the SD card, see INSTALL_HOW_TO.txt + * Built on PRs and releases by the CI + + Fixes: + Fix songs playing far too fast on Switch + * The audio thread's semaphore came from UnixSysSemaphore, which uses + sem_open - an ENOSYS stub on devkitA64 that returns NULL, so every + wait returned immediately and the thread rendered unthrottled + Fix crash when loading a project while another one is playing + * The load ran while onEvent() held the MixerService lock and + destroyed that very mutex, so the later unlock hit freed memory + Fix dependency file generation on macOS + * The sed in rules_base used the GNU-only \+, which BSD sed takes + literally, leaving the generated .d files malformed + Fix README, CHANGELOG and LICENSE being unextractable from release zips + * They were stored under a literal ../../../ path, which unzip refuses + 1.6.0-bacon17 Fix sample list refreshing bug diff --git a/docs/wiki/What-is-LittlePiggyTracker.md b/docs/wiki/What-is-LittlePiggyTracker.md index 1a3b4fe4..ad9b13f3 100644 --- a/docs/wiki/What-is-LittlePiggyTracker.md +++ b/docs/wiki/What-is-LittlePiggyTracker.md @@ -27,6 +27,7 @@ this, set Software rendering **or** change rendering resolution after LGPT has s - **lgptsav.dat** - this file doesn't exist for new projects but will be created by lgpt when you first save - **lgpt.fxe** - gp32 executable - **lgpt.gpe** - gp2x executable +- **lgpt.nro** - Nintendo Switch executable - **SampleLib** - Place your samples here Getting lgpt to run depends on your platform: @@ -34,6 +35,7 @@ Getting lgpt to run depends on your platform: - **gp32:** place LGPT.fxe, lgptNew, and lgpt10k in \GPMM - **gp2x/caanoo:** place LGPT.gpe, lgptNew, and lgpt10k in the same folder (anywhere on your SD card) - **win/mac/\*nix :** everything should be in the right spot, just run the executable in \install\bin\ (for fullscreen run “lgpt.exe -fullscreen”) +- **Nintendo Switch:** merge the `switch` folder from the zip into the root of your SD card, so you end up with lgpt.nro, samplelib and the projects together in /switch/lgpt, then launch it from the homebrew menu. Custom firmware is required, and the folder has to keep the `lgpt` name because the app looks for its projects and samples there. ## Importing Samples diff --git a/projects/Makefile b/projects/Makefile index 9fc7e43d..a7676709 100644 --- a/projects/Makefile +++ b/projects/Makefile @@ -198,6 +198,14 @@ PSPDIRS := \ ../sources/Adapters/SDL/GUI \ ../sources/Adapters/SDL/Timer +SWITCHDIRS := \ + ../sources/Adapters/SWITCH/Main \ + ../sources/Adapters/SWITCH/System \ + ../sources/Adapters/Unix/FileSystem \ + ../sources/Adapters/SDL2/Process \ + ../sources/System/Process \ + $(DUMMYMIDIDIRS) $(SDL2DIRS) $(SDL2AUDIODIRS) + GP32DIRS := .\ ../sources/Adapters/GP32FileSystem \ ../sources/Adapters/GP32Midi \ @@ -439,6 +447,20 @@ PSPFILES := \ SDLTimer.o \ PSPSystem.o +SWITCHFILES := \ + UnixFileSystem.o \ + SwitchMain.o \ + SwitchSystem.o \ + SwitchNxlink.o \ + GUIFactory.o \ + SDLGUIWindowImp.o \ + SDLEventManager.o \ + Process.o \ + SDLProcess.o \ + SDLTimer.o \ + $(SDLAUDIOFILES) \ + $(DUMMYMIDIFILES) + #--------------------------------------------------------------------------------- # Windows #--------------------------------------------------------------------------------- @@ -520,7 +542,7 @@ $(BUILD): clean: @echo clean ... - @rm -fr $(OUTPUT).$(EXTENSION) $(BUILD) *.elf *.gxb *.fxe *.gdb + @rm -fr $(OUTPUT).$(EXTENSION) $(BUILD) *.elf *.gxb *.fxe *.gdb *.nro *.nacp *.map #--------------------------------------------------------------------------------- diff --git a/projects/Makefile.SWITCH b/projects/Makefile.SWITCH new file mode 100644 index 00000000..6c70dafc --- /dev/null +++ b/projects/Makefile.SWITCH @@ -0,0 +1,77 @@ +-include $(PWD)/rules_base + +#--------------------------------------------------------------------------------- +# Nintendo Switch (homebrew, devkitPro/libnx) +#--------------------------------------------------------------------------------- + +ifeq ($(strip $(DEVKITPRO)),) +$(error "Please set DEVKITPRO in your environment. export DEVKITPRO=/devkitpro") +endif + +# config +DEFINES := \ + -DPLATFORM_$(PLATFORM) \ + -D_64BIT \ + -DBUFFERED \ + -DCPP_MEMORY \ + -DHAVE_STDINT_H \ + -D_NDEBUG \ + -DSDL2 \ + -DSDLAUDIO \ + -DDUMMYMIDI \ + -D__SWITCH__ + +DEVKITARM := $(DEVKITPRO)/devkitA64 +TOOLPATH := $(DEVKITARM)/bin +TOOLSBIN := $(DEVKITPRO)/tools/bin +PREFIX := aarch64-none-elf- + +# rules_base sets CC/CXX from $(PREFIX) at include time (line 1, above), +# before PREFIX is known here - so bind the real cross-compiler explicitly. +CC := $(PREFIX)gcc +CXX := $(PREFIX)g++ +AS := $(PREFIX)as +AR := $(PREFIX)ar +OBJCOPY := $(PREFIX)objcopy + +PORTLIBS := $(DEVKITPRO)/portlibs/switch +LIBNX := $(DEVKITPRO)/libnx + +# must match the flags libSDL2.a itself was built with (see +# $(PORTLIBS)/lib/pkgconfig/sdl2.pc) or the TLS model / ABI mismatches +ARCH := -march=armv8-a+crc+crypto -mtune=cortex-a57 -mtp=soft -fPIE -ftls-model=local-exec + +SDL_CFLAGS := -I$(PORTLIBS)/include -I$(PORTLIBS)/include/SDL2 -isystem $(LIBNX)/include -D_REENTRANT +# switch-sdl2 uses EGL internally for SDL_GetWindowSurface even though we +# never touch GL ourselves - these are required to satisfy its link deps +SDL_LIBS := -lSDL2 -lEGL -lstdc++ -lglapi -ldrm_nouveau -lnx -lpthread + +OPT_FLAGS := -O2 $(ARCH) -ffunction-sections -fdata-sections +INCLUDES := $(SDL_CFLAGS) -I$(PWD)/../sources +CFLAGS := $(DEFINES) $(INCLUDES) $(OPT_FLAGS) -Wall +CXXFLAGS := $(CFLAGS) -std=gnu++03 +LIBS := -L$(PORTLIBS)/lib -L$(LIBNX)/lib $(SDL_LIBS) + +# (constexpr/alignas) needs C++11; only SwitchNxlink.cpp touches it +SwitchNxlink.o: CXXFLAGS := $(CFLAGS) -std=gnu++11 + +LDFLAGS := -specs=$(LIBNX)/switch.specs $(ARCH) -Wl,-Map,$(notdir $*.map) + +OUTPUT := ../lgpt-switch +EXTENSION := nro + +APP_TITLE := LittleGPTracker +APP_AUTHOR := n0s2 +APP_VERSION := $(shell awk -F'"' '/PROJECT_NUMBER/{n=$$2} /PROJECT_RELEASE/{r=$$2} /BUILD_COUNT/{b=$$2} END{print n"."r"."b}' $(PWD)/../sources/Application/Model/Project.h) +APP_ICON := $(PWD)/resources/SWITCH/LittleGPTracker.jpg + +#--------------------------------------------------------------------------------- +# link the elf, then wrap it into a homebrew .nro (with icon/nacp metadata) +#--------------------------------------------------------------------------------- + +%.elf: $(OFILES) + $(CXX) $(LDFLAGS) -o $@ $(OFILES) $(LIBS) + +%.nro: %.elf + $(TOOLSBIN)/nacptool --create "$(APP_TITLE)" "$(APP_AUTHOR)" "$(APP_VERSION)" $(basename $@).nacp + $(TOOLSBIN)/elf2nro $< $@ --icon=$(APP_ICON) --nacp=$(basename $@).nacp diff --git a/projects/resources/SWITCH/INSTALL_HOW_TO.txt b/projects/resources/SWITCH/INSTALL_HOW_TO.txt new file mode 100644 index 00000000..aa756e17 --- /dev/null +++ b/projects/resources/SWITCH/INSTALL_HOW_TO.txt @@ -0,0 +1,26 @@ +Installation: +1. You need a Nintendo Switch running custom firmware (Atmosphere) with the + homebrew menu available. Nothing here installs to the HOME menu. +2. Copy the `switch` folder from this zip onto the root of your SD card and + merge it with the one already there. You should end up with: + /switch/lgpt/lgpt.nro + /switch/lgpt/mapping.xml + /switch/lgpt/config.xml + /switch/lgpt/samplelib/ + The folder MUST be named `lgpt`: the app looks for its projects and samples + in `sdmc:/switch/lgpt` and will not find them anywhere else. +3. Put the SD card back in the console. + + +Usage: +Open the homebrew menu (album, or the title override of your choice) and pick +LittleGPTracker. Your projects are saved under /switch/lgpt/. + + +Notes: +- Audio buffer size and sample rate are already set for this console in the + build. If you add AUDIOBUFFERSIZE, AUDIOPREBUFFERCOUNT, AUDIOSAMPLERATE or + SCREENMULT to config.xml you will override those defaults, which usually + makes things worse (too small a buffer crackles). +- Launching through `nxlink -s lgpt.nro` streams the log output to your PC, + which is useful if you want to report a problem. diff --git a/projects/resources/SWITCH/LittleGPTracker.jpg b/projects/resources/SWITCH/LittleGPTracker.jpg new file mode 100644 index 00000000..be6c6a78 Binary files /dev/null and b/projects/resources/SWITCH/LittleGPTracker.jpg differ diff --git a/projects/resources/SWITCH/config.xml b/projects/resources/SWITCH/config.xml new file mode 100644 index 00000000..2d473f1a --- /dev/null +++ b/projects/resources/SWITCH/config.xml @@ -0,0 +1,11 @@ + + + + + + + diff --git a/projects/resources/SWITCH/mapping.xml b/projects/resources/SWITCH/mapping.xml new file mode 100644 index 00000000..9522eb86 --- /dev/null +++ b/projects/resources/SWITCH/mapping.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/projects/resources/packaging/lgpt_package.sh b/projects/resources/packaging/lgpt_package.sh index 968f7731..102d02cf 100755 --- a/projects/resources/packaging/lgpt_package.sh +++ b/projects/resources/packaging/lgpt_package.sh @@ -27,15 +27,30 @@ collect_resources() { #1PLATFORM #2lgpt.*-exe elif [ "$1" == "MACOS" ]; then # .app is a folder zip -9 $PACKAGE -j $CONTENTS zip -9yr $PACKAGE LittleGPTracker.app/ + elif [ "$1" == "SWITCH" ]; then # everything lives in one SD card folder + # hbmenu lists /switch//.nro as a single entry, and the app + # hardcodes sdmc:/switch/lgpt for both bin: and root:, so the binary and + # its data - samplelib and the projects included - all go in that one + # folder. The user merges "switch" into their SD card root. + mkdir -p switch/lgpt ; cp $CONTENTS switch/lgpt + mv switch/lgpt/$2 switch/lgpt/lgpt.nro + cp -r ./resources/packaging/samplelib switch/lgpt + # -type d so the lgpt_* project folders come across as folders, and so it + # doesn't also pick up lgpt_package.sh sitting next to them + find ./resources/packaging -maxdepth 1 -type d -name "lgpt_*" -exec cp -r {} switch/lgpt \; + zip -9 -r $PACKAGE switch/ && rm -r switch/ else # all the others go in the bin mkdir bin ; cp $CONTENTS bin zip -9 $PACKAGE bin/* && rm -r bin/ fi - cd ./resources/packaging - CONTENTS="../../../README.md ../../../CHANGELOG ../../../LICENSE" - CONTENTS+=" $(find . -name "samplelib" -type d)" - CONTENTS+=" $(find . -name "lgpt_*" -type d)" - zip -9 -r ../../$PACKAGE $CONTENTS + cd ./resources/packaging + # -j or these get stored as literal ../../../README.md, which unzip refuses + zip -9 -jq ../../$PACKAGE ../../../README.md ../../../CHANGELOG ../../../LICENSE + if [ "$1" != "SWITCH" ]; then # already staged inside switch/lgpt above + CONTENTS="$(find . -name "samplelib" -type d)" + CONTENTS+=" $(find . -name "lgpt_*" -type d)" + zip -9 -r ../../$PACKAGE $CONTENTS + fi CONTENTS="../../../docs/wiki/What-is-LittlePiggyTracker.md" CONTENTS+=" ../../../docs/wiki/config_xml.md" CONTENTS+=" ../../../docs/wiki/tips_and_tricks.md" @@ -54,6 +69,7 @@ collect_resources CHIP lgpt.chip-exe collect_resources BITTBOY lgpt-bittboy.elf collect_resources GARLICPLUS lgpt-garlicplus.elf collect_resources RG35XXPLUS lgpt-rg35xxplus.elf +collect_resources SWITCH lgpt-switch.nro collect_resources MACOS LittleGPTracker.app # collect_resources RS97 lgpt.dge # collect_resources STEAM lgpt.steam-exe diff --git a/projects/rules_base b/projects/rules_base index 9a22957f..b5fcf457 100644 --- a/projects/rules_base +++ b/projects/rules_base @@ -17,7 +17,7 @@ export OBJCOPY := $(PREFIX)objcopy define adjustdepends cp $(DEPSDIR)/$*.d $(DEPSDIR)/$*.P; -sed -e 's/#.*//' -e 's/^[^:]*: \+//' -e 's/^ *//' -e 's/ *\\$$//' -e '/^$$/ d' -e 's/$$/ :/' < $(DEPSDIR)/$*.P >> $(DEPSDIR)/$*.d; +sed -e 's/#.*//' -e 's/^[^:]*:[[:space:]]*//' -e 's/^ *//' -e 's/ *\\$$//' -e '/^$$/ d' -e 's/$$/ :/' < $(DEPSDIR)/$*.P >> $(DEPSDIR)/$*.d; rm $(DEPSDIR)/$*.P endef diff --git a/sources/Adapters/SDL2/GUI/SDLGUIWindowImp.cpp b/sources/Adapters/SDL2/GUI/SDLGUIWindowImp.cpp index 7f4eb05a..84b7b3f4 100644 --- a/sources/Adapters/SDL2/GUI/SDLGUIWindowImp.cpp +++ b/sources/Adapters/SDL2/GUI/SDLGUIWindowImp.cpp @@ -48,18 +48,22 @@ SDLGUIWindowImp::SDLGUIWindowImp(GUICreateWindowParams &p) int screenWidth = 320; int screenHeight = 240; windowed_ = false; - #else +#elif defined(PLATFORM_SWITCH) + int screenWidth = 1280; + int screenHeight = 720; + windowed_ = false; +#else int screenWidth = displayMode.w; int screenHeight = displayMode.h; - #endif - - #if defined(RS97) +#endif + +#if defined(RS97) /* Pick the best bitdepth for the RS97 as it will select 32 as its default, even though that's slow */ bitDepth_ = 16; - #else +#else bitDepth_ = SDL_BITSPERPIXEL(displayMode.format); - #endif - +#endif + const char * driverName = SDL_GetVideoDriver(0); Trace::Log("DISPLAY","Using driver %s. Screen (%d,%d) Bpp:%d",driverName,screenWidth,screenHeight,bitDepth_); @@ -89,7 +93,9 @@ SDLGUIWindowImp::SDLGUIWindowImp(GUICreateWindowParams &p) } else { - if (framebuffer_) + // If drawing on a framebuffer or in a fullscreen app, calc the UI scale + // from size + if (framebuffer_ || windowed_ == false) { mult_ = multFromSize; } diff --git a/sources/Adapters/SDL2/Process/SDLProcess.cpp b/sources/Adapters/SDL2/Process/SDLProcess.cpp index 90719c30..b188df3e 100644 --- a/sources/Adapters/SDL2/Process/SDLProcess.cpp +++ b/sources/Adapters/SDL2/Process/SDLProcess.cpp @@ -9,8 +9,8 @@ int _SDLStartThread(void *argp) { } bool SDLProcessFactory::BeginThread(SysThread& thread) { - SDL_CreateThread(_SDLStartThread,&thread); - return true ; + SDL_CreateThread(_SDLStartThread, "SysThread", &thread); + return true ; } SysSemaphore *SDLProcessFactory::CreateNewSemaphore(int initialcount, int maxcount) { @@ -18,11 +18,14 @@ SysSemaphore *SDLProcessFactory::CreateNewSemaphore(int initialcount, int maxcou } ; SDLSysSemaphore::SDLSysSemaphore(int initialcount,int maxcount) { - handle_=SDL_CreateSemaphore(0) ; + handle_ = SDL_CreateSemaphore(initialcount); } ; SDLSysSemaphore::~SDLSysSemaphore() { - handle_=0 ; + if (handle_) { + SDL_DestroySemaphore(handle_) ; + } + handle_=0 ; } ; SysSemaphoreResult SDLSysSemaphore::Wait() { diff --git a/sources/Adapters/SWITCH/Main/SwitchMain.cpp b/sources/Adapters/SWITCH/Main/SwitchMain.cpp new file mode 100644 index 00000000..328c14ef --- /dev/null +++ b/sources/Adapters/SWITCH/Main/SwitchMain.cpp @@ -0,0 +1,20 @@ +#include "Adapters/SDL2/GUI/SDLGUIWindowImp.h" +#include "Adapters/SWITCH/System/SwitchSystem.h" +#include "Application/Application.h" +#include +/* + * entrypoint for the Nintendo Switch homebrew target + */ +int main(int argc,char *argv[]) { + SwitchSystem::Boot(argc,argv); + + SDLCreateWindowParams params; + params.title="littlegptracker"; + params.cacheFonts_=true; + + Application::GetInstance()->Init(params); + + return SwitchSystem::MainLoop(); +} + +void _assert() {}; diff --git a/sources/Adapters/SWITCH/System/SwitchNxlink.cpp b/sources/Adapters/SWITCH/System/SwitchNxlink.cpp new file mode 100644 index 00000000..40df10c0 --- /dev/null +++ b/sources/Adapters/SWITCH/System/SwitchNxlink.cpp @@ -0,0 +1,7 @@ +#include "SwitchNxlink.h" +#include + +void SwitchNxlinkInit() { + socketInitializeDefault(); + nxlinkStdio(); +} diff --git a/sources/Adapters/SWITCH/System/SwitchNxlink.h b/sources/Adapters/SWITCH/System/SwitchNxlink.h new file mode 100644 index 00000000..ebdd2b01 --- /dev/null +++ b/sources/Adapters/SWITCH/System/SwitchNxlink.h @@ -0,0 +1,8 @@ +#ifndef _SWITCH_NXLINK_H_ +#define _SWITCH_NXLINK_H_ + +// isolated from on purpose: libnx typedefs (AudioDriver, Result, ...) +// collide with this project's own class names of the same name +void SwitchNxlinkInit(); + +#endif diff --git a/sources/Adapters/SWITCH/System/SwitchSystem.cpp b/sources/Adapters/SWITCH/System/SwitchSystem.cpp new file mode 100644 index 00000000..eafddf2a --- /dev/null +++ b/sources/Adapters/SWITCH/System/SwitchSystem.cpp @@ -0,0 +1,157 @@ +#include "SwitchSystem.h" +#include "Adapters/Dummy/Midi/DummyMidi.h" +#include "Adapters/SDL2/Audio/SDLAudio.h" +#include "Adapters/SDL2/GUI/GUIFactory.h" +#include "Adapters/SDL2/GUI/SDLEventManager.h" +#include "Adapters/SDL2/GUI/SDLGUIWindowImp.h" +#include "Adapters/SDL2/Process/SDLProcess.h" +#include "Adapters/SDL2/Timer/SDLTimer.h" +#include "Adapters/Unix/FileSystem/UnixFileSystem.h" +#include "Application/Commands/NodeList.h" +#include "Application/Controllers/ControlRoom.h" +#include "Application/Model/Config.h" +#include "SwitchNxlink.h" +#include "System/Console/Logger.h" +#include +#include + +// homebrew apps live under a fixed sdmc: path, there is no /proc/self/exe +// equivalent to derive it from under libnx +#define SWITCH_APP_ROOT "sdmc:/switch/lgpt" + +EventManager *SwitchSystem::eventManager_ = NULL; +static int secbase = 0; + +/* + * starts the main loop + */ +int SwitchSystem::MainLoop() { + eventManager_->InstallMappings(); + return eventManager_->MainLoop(); +}; + +/* + * initializes the application + */ +void SwitchSystem::Boot(int argc, char **argv) { + + // Redirect stdout/stderr to nxlink so `nxlink -s lgpt-switch.nro` streams + // Trace::Log output live; a no-op if not launched through nxlink. + SwitchNxlinkInit(); + + // Install System + System::Install(new SwitchSystem()); + + // Install FileSystem + FileSystem::Install(new UnixFileSystem()); + + // Install aliases + Path::SetAlias("bin", SWITCH_APP_ROOT); + Path::SetAlias("root", SWITCH_APP_ROOT); + + // always use stdout, visible via nxlink if the app was launched that way + Trace::GetInstance()->SetLogger(*(new StdOutLogger())); + + // Process arguments + Config::GetInstance()->ProcessArguments(argc, argv); + + // Install GUI Factory + I_GUIWindowFactory::Install(new GUIFactory()); + + // Install Timers + TimerService::GetInstance()->Install(new SDLTimerService()); + + Trace::Log("System", "Installing SDL audio"); + AudioSettings hint; + hint.bufferSize_ = 1024; + hint.preBufferCount_ = 2; + // switch-sdl2 mixes at a hardcoded 48kHz (its arConfig is a static const + // AudioRendererConfig). Voices are initialised at whatever rate we ask + // for and libnx resamples per voice, so 44100 works too, we ask for + // 48000 to match the mix rate and skip that conversion entirely + hint.sampleRate_ = 48000; + Audio::Install(new SDLAudio(hint)); + + Trace::Log("System", "Installing DUMMY MIDI"); + MidiService::Install(new DummyMidi()); + + // Install Threads. + SysProcessFactory::Install(new SDLProcessFactory()); + + if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER) < 0) { + return; + } + SDL_ShowCursor(SDL_DISABLE); + + atexit(SDL_Quit); + + eventManager_ = I_GUIWindowFactory::GetInstance()->GetEventManager(); + eventManager_->Init(); +}; + +void SwitchSystem::Shutdown() {}; + +/* + * get current time for status display + */ +unsigned long SwitchSystem::GetClock() { + struct timeval tp; + + gettimeofday(&tp, NULL); + if (!secbase) { + secbase = tp.tv_sec; + return long(tp.tv_usec / 1000.0); + } + return long((tp.tv_sec - secbase) * 1000 + tp.tv_usec / 1000.0); +} + +/* + * wraps sleep, guess we never sleep! + */ +void SwitchSystem::Sleep(int millisec) { +} + +/* + * wraps malloc + */ +void *SwitchSystem::Malloc(unsigned size) { + return malloc(size); +} + +/* + * wraps free + */ +void SwitchSystem::Free(void *ptr) { free(ptr); } + +/* + * wraps memset + */ +void SwitchSystem::Memset(void *addr, char val, int size) { + memset(addr, val, size); +}; + +/* + * wraps memcpy + */ +void *SwitchSystem::Memcpy(void *s1, const void *s2, int n) { + return memcpy(s1, s2, n); +}; + +/* + * logprint + */ +void SwitchSystem::AddUserLog(const char *msg) { + fprintf(stderr, "LOG: %s\n", msg); +}; + +/* + * print after quit + */ +void SwitchSystem::PostQuitMessage() { + SDLEventManager::GetInstance()->PostQuitMessage(); +}; + +/* + * get memory usage, guess it's infinite + */ +unsigned int SwitchSystem::GetMemoryUsage() { return 0; }; diff --git a/sources/Adapters/SWITCH/System/SwitchSystem.h b/sources/Adapters/SWITCH/System/SwitchSystem.h new file mode 100644 index 00000000..34adb981 --- /dev/null +++ b/sources/Adapters/SWITCH/System/SwitchSystem.h @@ -0,0 +1,29 @@ +#ifndef _SWITCH_SYSTEM_H_ +#define _SWITCH_SYSTEM_H_ + +#include +#include "System/System/System.h" +#include "UIFramework/SimpleBaseClasses/EventManager.h" + +class SwitchSystem: public System { +public: + static void Boot(int argc,char **argv); + static void Shutdown(); + static int MainLoop(); + +public: // System implementation + virtual unsigned long GetClock(); + virtual void Sleep(int millisec); + virtual void *Malloc(unsigned size); + virtual void Free(void *); + virtual void Memset(void *addr,char val,int size); + virtual void *Memcpy(void *s1, const void *s2, int n); + virtual void AddUserLog(const char *); + virtual int GetBatteryLevel() { return -1 ; }; + virtual void PostQuitMessage(); + virtual unsigned int GetMemoryUsage(); + +private: + static EventManager *eventManager_; +}; +#endif diff --git a/sources/Application/AppWindow.cpp b/sources/Application/AppWindow.cpp index e7d5ec95..3de5febc 100644 --- a/sources/Application/AppWindow.cpp +++ b/sources/Application/AppWindow.cpp @@ -45,7 +45,12 @@ static void ProjectSelectCallback(View &v, ModalView &dialog) { if (dialog.GetReturnCode() > 0) { Path selected = spd.GetSelection(); instance->SaveLastProject(selected); - instance->LoadProject(selected.GetPath().c_str()); + // Defer the actual load instead of calling LoadProject() here: this + // callback runs while onEvent() holds the MixerService lock, and a + // synchronous load destroys/recreates that same mutex mid-lock + // (MixerService::Close()/Init()), corrupting it. Process it on the + // next onUpdate() instead, after the lock is released. + instance->LoadProjectDeferred(selected.GetPath().c_str()); } else { System::GetInstance()->PostQuitMessage(); } @@ -88,6 +93,7 @@ AppWindow::AppWindow(I_GUIWindowImp &imp) : GUIWindow(imp) { _closeProject = 0; _loadAfterSaveAsProject = 0; _loadAfterResume = 0; + _loadAfterProjectSelect = 0; _lastA = 0; _lastB = 0; _mask = 0; @@ -129,7 +135,8 @@ AppWindow::AppWindow(I_GUIWindowImp &imp) : GUIWindow(imp) { SelectProjectDialog *spd = new SelectProjectDialog(*_currentView); Path lastProjectPath = GetLastProjectPath(); - if (shouldAutoLoad && lastProjectPath.Exists()) { + if (shouldAutoLoad && !lastProjectPath.GetPath().empty() && + lastProjectPath.Exists()) { Trace::Log("AppWindow", "Auto-loading last project: %s", lastProjectPath.GetPath().c_str()); _newProjectToLoad = lastProjectPath.GetPath().c_str(); @@ -414,6 +421,11 @@ void AppWindow::LoadProject(const Path &p) { Redraw(); } +void AppWindow::LoadProjectDeferred(const Path &p) { + _newProjectToLoad = p.GetPath(); + _loadAfterProjectSelect = true; +} + void AppWindow::CloseProject() { _closeProject = false; @@ -537,6 +549,12 @@ void AppWindow::onUpdate() { LoadProject(_newProjectToLoad.c_str()); return; } + if (_loadAfterProjectSelect) { + _loadAfterProjectSelect = false; + _isDirty = true; + LoadProject(_newProjectToLoad.c_str()); + return; + } // Call AnimationUpdate periodically (~10-25Hz depending on frame rate) static unsigned int animTick = 0; diff --git a/sources/Application/AppWindow.h b/sources/Application/AppWindow.h index a312bda6..bc1b9fd8 100644 --- a/sources/Application/AppWindow.h +++ b/sources/Application/AppWindow.h @@ -28,6 +28,7 @@ class AppWindow : public GUIWindow, I_Observer, Status { public: static AppWindow *Create(GUICreateWindowParams &); void LoadProject(const Path &path); + void LoadProjectDeferred(const Path &path); void SaveLastProject(const Path &p); void CloseProject(); @@ -79,6 +80,7 @@ class AppWindow : public GUIWindow, I_Observer, Status { bool _closeProject; bool _loadAfterSaveAsProject; bool _loadAfterResume; + bool _loadAfterProjectSelect; bool _shouldQuit; unsigned short _mask; unsigned long _lastA; diff --git a/sources/Application/Model/Project.h b/sources/Application/Model/Project.h index 7d1cd145..857108a7 100644 --- a/sources/Application/Model/Project.h +++ b/sources/Application/Model/Project.h @@ -21,7 +21,7 @@ #define PROJECT_NUMBER "1" #define PROJECT_RELEASE "6" -#define BUILD_COUNT "0-bacon17" +#define BUILD_COUNT "0-bacon18" #define MAX_TAP 3