From 5262dcf5c66972463279086664adc63bf5ab36f1 Mon Sep 17 00:00:00 2001 From: emartinez Date: Mon, 17 Aug 2026 17:35:38 +0200 Subject: [PATCH 01/13] feat: implemented build for Nintendo Switch, compatible with nxlink --- projects/Makefile | 24 ++- projects/Makefile.SWITCH | 77 +++++++++ projects/resources/SWITCH/LittleGPTracker.jpg | Bin 0 -> 6204 bytes projects/resources/SWITCH/mapping.xml | 14 ++ sources/Adapters/SWITCH/Main/SwitchMain.cpp | 20 +++ .../Adapters/SWITCH/System/SwitchNxlink.cpp | 7 + sources/Adapters/SWITCH/System/SwitchNxlink.h | 8 + .../Adapters/SWITCH/System/SwitchSystem.cpp | 160 ++++++++++++++++++ sources/Adapters/SWITCH/System/SwitchSystem.h | 29 ++++ 9 files changed, 338 insertions(+), 1 deletion(-) create mode 100644 projects/Makefile.SWITCH create mode 100644 projects/resources/SWITCH/LittleGPTracker.jpg create mode 100644 projects/resources/SWITCH/mapping.xml create mode 100644 sources/Adapters/SWITCH/Main/SwitchMain.cpp create mode 100644 sources/Adapters/SWITCH/System/SwitchNxlink.cpp create mode 100644 sources/Adapters/SWITCH/System/SwitchNxlink.h create mode 100644 sources/Adapters/SWITCH/System/SwitchSystem.cpp create mode 100644 sources/Adapters/SWITCH/System/SwitchSystem.h diff --git a/projects/Makefile b/projects/Makefile index 9fc7e43d..a3236cd2 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/Unix/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 \ + UnixProcess.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..e237060e --- /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 := 1.0 +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/LittleGPTracker.jpg b/projects/resources/SWITCH/LittleGPTracker.jpg new file mode 100644 index 0000000000000000000000000000000000000000..be6c6a783bdbd5ad0710fe03f95013c540d9cf77 GIT binary patch literal 6204 zcmb_g4OkOrwvI)lQg5kMv|X)fwJq2zP1Rb80(n}iZsD#JwFUt*{*4ZLW6J#7u`v1x%LaDFUsOb-g2gQrgq znm!|V#*C>EGV}2nAu~f}%y|5X$7hBH4sbQ=iO^Yr4}qIJ{Oe=EK|#TxAu~dzru?5y z{%>is9}n0RG;J~MvDwptW>53qpwWSy!Bge|V;>&V9t)ZtJR<~{@dTJ4e+rl%6a=(y=^{bCID`*;6yr9`M7LD{(6hzYg{|2>nEFykWRoF2%uGO+G6fis?h-Ij`HU+9q zOY7GyIP8@&RYI(?L2=f1;Ya9p2K-WP@hcpyx3A^n!WMG9WI$!t>eE{BRRvTvV`!Bx z1c_dH(@)#OCzQ$ti^x9Ot88CDN?XUBd`Q-yC>n=TPbaTE@Z8n4bKjfxFOP8FC%VoD zVNdyK+X?0X-{}-pr)nLduJjENWA1J|Zn=6FQpIpNE^BfyTUe&u;lL_!#aC9*4EABK znWf^URpRK`NlCg2UKegHmVI=dgcYb&R48FZv%2OfBF@>n`$%>2IZ>t*KOk@NdKRh# z+dg6oppcnrtGw8XSnPP>Gk!jJj*La$wBq)LZ+s zC@$sSUAC1Tg$gF&t1P5|F^M78!_BpmVHa`;oywcW6{yW^*YZIS*ySn7Yv-I46VXu0^~9xFRhJj_7ygbZCgx(teZa;b-8zYGZSo&;Hzvd?ngscdKFx`A1(9 z>-z|;f|>MS;d2L1CqwklZU}|Dx%A(k&Y<5(r~I_&l})Kyv~+xKGKbCn^qPoUYLLr* z@Sb=w#8PH($+9H|DpSq=SbOtev0IsV6YdkY6Ju>rxG)Wh}tStV#87{%#y~wpyR4HTR(EZ8!&*Bud z33$j*FQyox#9X!H)kici>lY zO}WneH8)tj@$GoJ&xh^N%9T>SWX^G~>at2>8A1zJxkJhKE@~WMHcckcwIOjrW#_Hz z^`0nNO?~H;a(PPICu=e_CUutlH_#isa%ec+>1~I*= z;>5MHZfmNAR8Jp&!l+;}OI73iZiG~cVq)S06RD!eiUQA)*3ihBC!6+8!aPyduJ1aX zu(c+MU-}i9#RUbly3M0j{|PCz+{??PIDVQQCHB>1xZ~kJvs-grm3gEnT)c=fDC~k5 zR#?+yvH#{Tm2L69 zQ=zJ%4~1Eoy9u>em@AhBGW(TocgZbA+dX7+O_C?vPkUH>UY2K0pb)ttQo{i2qE4)_ z&U=D@OAR9c zuF~t!ml!sxJj#Z6`fDAmhDMQLL!t|0!ArfKF7;;!9VxQdmVUq*llf@^TU?bSs!^OX zH9h3`D+HWTDeLso_p?Vx(U;IS;Sc&>*hHqdy~3p3?W8`(2^ZidN3VO|E%^4yZKA5n zD4x~Md%*D-(&!phhHj!Z{W;NrIVV*}G!ZA`Dg3m)LRPhlnOSLvqhywTBBy|gw%jW) zx7PbYVe%f^EuOO|wORk1SR{ZhN!74dKP9mmpG>dV4 z=(nF+7PaFgis+=I6%*EQmQ|f4Xp|Y{q)__1QsOk7*X}Uc!7H!X|t2qWW+$Rhq*0SY-J;b zoqk$uLbNt|={HyD$Y;0Ks2b#SuE&5z$+mFwS4zWD=Pa!;3%nVUYO20 zOYnA&r)n*hk#KvsgA{E0J!d7=s~}iRCTd3c{En(KqMejYX7X1pFEUWZsmGFks6d~!Obj)T8e?(XPYaujFaU~#doMbD7gJB@Oc zuuLTo`Hp*}bvJgNwZ_a?&Vk@6mM-PpengY`Ajh z{=P&a70Z->#|{)KuAAvw(MGPoPy06Rlrp=(oiGC6bUogv-U8?qq_oJP(7?^L!@2!z zLpWY3%364g*x;urjYfysPg~hL+4Wiryi*u z)Q_0;5S%wS6u;Eyh8iiJe3b0*AT=EH0K??H@5s;8#gO{c0UpSN@4w4~zHzCjG=M1I z2FS*<2^p(XeN?z4zyeCMS&^8kbBZ*dbU0bIMM-=~3CU9_CEF_s7+CC|A6>n`H8NyC zFBdRwb}ff(oFo`bW(xJ42VSq*bjnL_kJ}$k#6r%hhgKgx9&#&&42}ozxWV#?KGFAx$@tw`DxJ$Uc(ey2bw9Wng#j zr5NNU_g4V3c;osKKW!)gZ>#Hk5pG?ys_7)??G2$}rgxc-gcD!RUh*)1lR!P8pcLJr*UQ3*Xp49F4T#$Ah1wA4EYH8uo$*37}aPTay4b{ zB4FE-WlA*Qu(o)T4ynUEAslK~fbkrZv*^^raL4}V0YG@|fbf2jn*qWTZN2}`^k#@d z1APtd0_Cw=G8S?*8cL(LdHuBgWc`cnaJ{Gs?d;4h>7#g8*aOARbMLaH-G17pxN^0w z4zp!#3x^%d1DLLsnjgUkEUeO@=2 zRM+;JxJPQR_1u~?G#(<~0V-q68QVhcAHhYEI+Vk8rnSAHV4fS#zceaZA5dWS@%)Ij z;r;A(He!vc92dn$%y9{K3(KRoWeA=-WE2X%pzmsz+2J3wC7I-Tm@koZ5kARwWIOUF z%V>h@HG$4s*-4;WTb8b*AARVz^GqMwx3DS4foX(?4zN~Qh*`+67y##k$)^`eQ-C)t z_wdM-=Giv#DBvrq4*;0>aQ^m3wK4kv3f&2YpLZEe=_{z(95G%5FevjYpO$jSFNx@3 zT6cU;q5-?$6+kO>4arbU*}f{bB4Rv7z;h$QfM#_Seajai03fgHM%V7m^`IM4!Y-24 zSUWY-zHvx;5yGQUT&5tK-~>P@@Ef`ABYDr9aFQ8{aZ+0E1O(8Us&)3aM-8&9i(riZ zi{vk=iAOg^BA3q}b4P3OO(s@gBVxXe4qXU$ zssu6RyT~J;zj9(S8CMW*{^4!d!X#I_VYkbpwxk+_((~a?k$8pf8+QHCR#q*K=n}(> zR4lsRQU6hN#LEB(*Ni3%T86x&5;->51oxHeI2;ZX2g-k9SuJ(lNaW)VInX6OtcIih z7CC0QH+hgwR@+1dFi)Yfk2KR+W!qV|$CHH8axw;JooHb{!+Qo9$wq4uh_NeE&E#Ym z9_(Ue+JSb-l9|BTevm);?=I~Nx@0Jy-St397bW$=^&B6Tt%A1}?jMNa-2(Ul z!Z#XvucMx6H9S<=fhM5h3%U$TOEWhbbFb>UfwVDaoRzXzjd}$6(luEY?Y*7_^tH|6 z6lvv!n*q>|6#h06l*?$SiyvE0tS-V#CNwdN7u|2XzEfN)irQYG<{^KH>~LtT<4G_O z^HFjow)f$0yG&m1+iG5gx_U)B>#`t@;-pQ%V`77nkTdI+vrLVD_G#4x@#59x-h||Jv4b)PY(tuHDJc znP>>G+qupaOxu%x$8KX`F9Kyr`*}QUUkmQ9=qMyI@BE6jaq0Cds4g=e( zEE3@#zmiGrazm<-5fc%sGZ_PTAUMqxE*J$um)$v$%&FO00%!6GU%z%V3o#KApi+1C zrM5(D%gAvJ=OQyQS(X+BiPgGeRRX)F(>TSRqq|zKXRz+DaZjUC#?8n05~Ly$=~y+| zw!F}q4a3s%fhpDkytA6%Nd`tn&@_YFvF$_?*bgy!y@xNi0vyN&2wC#5CtBW>!?PBG zt}~lEA(w$f0#SBrX8j!Nz&nUH&?~Y~PyWknO6?R3lm~63bVRyR%~ny|+6QF##7*&T-TYa8+IQ=1;+6;| z4y4H_H>G{qAZDc?1K1d{>cNJ{Aap5Q%N{-_c>SWEc7v^!Zt*I}8z#4OGFaAX7EUxM zpFIk>x}ZZ^G>}v7ZXl9mVBueH^V2Rj{!(0ri=hk=lV4QP)CM{^ebm?F*BXQwr3|QA zBRxz{am4id8-~!umR`oa1fQSwd?i&O{JlugpunSA;Xs$RMUi%@mcDsZ=xbnoo$yc< zs_%O4KC1W-;|BC%+uv`Wr84fvtGeZk>Q}#mGx$Pfzt+M{rBq$MZIG>e8(^R*6|5~_ zI5>5d)}!>&3u8ko3U}~waHpU4N6{d(4!FO+csL_JlWKHQ!*_%XQ4QA#5w-Hhd^=xV z6VaQN*dGx6L7+QqdAXA%C#xFU+q0)Ic)`vwk}XLzm@R9=hh5h2i8KB4Z+S!(LjaOA z5a=ihf7wPkR4HDj>kV(!nNpry?9RuUY;INlG3pX%9{jX+gBTv~(*i!0@<*D;*qS;J z>ADWS###dn1h(;}Xr?3S1+Uqslq(Um!`t1s%EB_K$r9_FI!bB}wP_HU0#sfMteGZAMG%J9L7~5GuE?59r z%9?-zH?(}rUg<8uS@g*eN7Y)ehrx~+p?@EDC?jymVT8L%cXOZX?dScPmj#hEYd(Fn zrvCAoO0awb#D0N?^X#D1#qXnVsZ?Th=q05@7v8AaW9S3W0K`Trd*aSo(Met!!7kds zN%I)o6ITAF!k0A04a!%6c41UaEA&dDn}zzR{gtKH&rn+}uY%NIbbK1Kp<~?=y*@Do NJiPwQKU(0w^IsNOs38CV literal 0 HcmV?d00001 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/sources/Adapters/SWITCH/Main/SwitchMain.cpp b/sources/Adapters/SWITCH/Main/SwitchMain.cpp new file mode 100644 index 00000000..9bf77511 --- /dev/null +++ b/sources/Adapters/SWITCH/Main/SwitchMain.cpp @@ -0,0 +1,20 @@ +#include +#include "Adapters/SWITCH/System/SwitchSystem.h" +#include "Adapters/SDL2/GUI/SDLGUIWindowImp.h" +#include "Application/Application.h" +/* + * 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..db459f1e --- /dev/null +++ b/sources/Adapters/SWITCH/System/SwitchSystem.cpp @@ -0,0 +1,160 @@ +#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/Timer/SDLTimer.h" +#include "Adapters/Unix/FileSystem/UnixFileSystem.h" +#include "Adapters/Unix/Process/UnixProcess.h" +#include "Application/Commands/NodeList.h" +#include "Application/Controllers/ControlRoom.h" +#include "Application/Model/Config.h" +#include "System/Console/Logger.h" +#include "SwitchNxlink.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_ = 8; + // switch-sdl2's audio renderer runs at a hardcoded 48kHz mix rate + // (AudioRendererOutputRate_48kHz in its SDL_switchaudio.c) regardless + // of what we request; the per-voice rate conversion from a requested + // 44100 up to that fixed 48kHz mix rate wasn't behaving correctly in + // practice (playback audibly sped up, ~48000/44100 = 1.088x). Request + // 48000 directly so the voice rate matches the renderer's native rate + // and no resampling is needed at all. + hint.sampleRate_ = 48000; + Audio::Install(new SDLAudio(hint)); + + Trace::Log("System", "Installing DUMMY MIDI"); + MidiService::Install(new DummyMidi()); + + // Install Threads + SysProcessFactory::Install(new UnixProcessFactory()); + + 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 From e9b22418ad6c6bcf518c0cde11fe5c978a6cb1fe Mon Sep 17 00:00:00 2001 From: emartinez Date: Mon, 17 Aug 2026 18:18:51 +0200 Subject: [PATCH 02/13] fix: added switch screen size and calculate screen mult on fullscreen --- sources/Adapters/SDL2/GUI/SDLGUIWindowImp.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/sources/Adapters/SDL2/GUI/SDLGUIWindowImp.cpp b/sources/Adapters/SDL2/GUI/SDLGUIWindowImp.cpp index 7f4eb05a..b060310e 100644 --- a/sources/Adapters/SDL2/GUI/SDLGUIWindowImp.cpp +++ b/sources/Adapters/SDL2/GUI/SDLGUIWindowImp.cpp @@ -48,6 +48,10 @@ SDLGUIWindowImp::SDLGUIWindowImp(GUICreateWindowParams &p) int screenWidth = 320; int screenHeight = 240; windowed_ = false; + #elif defined(PLATFORM_SWITCH) + int screenWidth = 1280; + int screenHeight = 720; + windowed_ = false; #else int screenWidth = displayMode.w; int screenHeight = displayMode.h; @@ -89,7 +93,7 @@ SDLGUIWindowImp::SDLGUIWindowImp(GUICreateWindowParams &p) } else { - if (framebuffer_) + if (framebuffer_ || windowed_ == false) { mult_ = multFromSize; } From 139436b1982b308ced3ae03e153be68149e699ff Mon Sep 17 00:00:00 2001 From: emartinez Date: Mon, 17 Aug 2026 18:35:24 +0200 Subject: [PATCH 03/13] fix: use SDL2 process instead of Unix for Switch, the audio semaphore was not waiting for consumer and it played songs x10 times faster --- projects/Makefile | 4 ++-- sources/Adapters/SWITCH/System/SwitchSystem.cpp | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/projects/Makefile b/projects/Makefile index a3236cd2..a7676709 100644 --- a/projects/Makefile +++ b/projects/Makefile @@ -202,7 +202,7 @@ SWITCHDIRS := \ ../sources/Adapters/SWITCH/Main \ ../sources/Adapters/SWITCH/System \ ../sources/Adapters/Unix/FileSystem \ - ../sources/Adapters/Unix/Process \ + ../sources/Adapters/SDL2/Process \ ../sources/System/Process \ $(DUMMYMIDIDIRS) $(SDL2DIRS) $(SDL2AUDIODIRS) @@ -456,7 +456,7 @@ SWITCHFILES := \ SDLGUIWindowImp.o \ SDLEventManager.o \ Process.o \ - UnixProcess.o \ + SDLProcess.o \ SDLTimer.o \ $(SDLAUDIOFILES) \ $(DUMMYMIDIFILES) diff --git a/sources/Adapters/SWITCH/System/SwitchSystem.cpp b/sources/Adapters/SWITCH/System/SwitchSystem.cpp index db459f1e..b81ead84 100644 --- a/sources/Adapters/SWITCH/System/SwitchSystem.cpp +++ b/sources/Adapters/SWITCH/System/SwitchSystem.cpp @@ -5,8 +5,8 @@ #include "Adapters/SDL2/GUI/SDLEventManager.h" #include "Adapters/SDL2/GUI/SDLGUIWindowImp.h" #include "Adapters/SDL2/Timer/SDLTimer.h" +#include "Adapters/SDL2/Process/SDLProcess.h" #include "Adapters/Unix/FileSystem/UnixFileSystem.h" -#include "Adapters/Unix/Process/UnixProcess.h" #include "Application/Commands/NodeList.h" #include "Application/Controllers/ControlRoom.h" #include "Application/Model/Config.h" @@ -78,8 +78,8 @@ void SwitchSystem::Boot(int argc, char **argv) { Trace::Log("System", "Installing DUMMY MIDI"); MidiService::Install(new DummyMidi()); - // Install Threads - SysProcessFactory::Install(new UnixProcessFactory()); + // Install Threads. + SysProcessFactory::Install(new SDLProcessFactory()); if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER) < 0) { return; From 44ea24c90257c2538a6c4647cbcf70e2d3cb45e4 Mon Sep 17 00:00:00 2001 From: emartinez Date: Mon, 17 Aug 2026 18:37:24 +0200 Subject: [PATCH 04/13] fix: use proper SDL_CreateThread args for SDL2 and pass initial count to SDLSysSemaphore. Also fixed semaphore leak on destructor --- sources/Adapters/SDL2/Process/SDLProcess.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/sources/Adapters/SDL2/Process/SDLProcess.cpp b/sources/Adapters/SDL2/Process/SDLProcess.cpp index 90719c30..0c9cbcbd 100644 --- a/sources/Adapters/SDL2/Process/SDLProcess.cpp +++ b/sources/Adapters/SDL2/Process/SDLProcess.cpp @@ -9,7 +9,7 @@ int _SDLStartThread(void *argp) { } bool SDLProcessFactory::BeginThread(SysThread& thread) { - SDL_CreateThread(_SDLStartThread,&thread); + SDL_CreateThread(_SDLStartThread,"SysThread",&thread); return true ; } @@ -18,10 +18,13 @@ SysSemaphore *SDLProcessFactory::CreateNewSemaphore(int initialcount, int maxcou } ; SDLSysSemaphore::SDLSysSemaphore(int initialcount,int maxcount) { - handle_=SDL_CreateSemaphore(0) ; + handle_=SDL_CreateSemaphore(initialcount) ; } ; SDLSysSemaphore::~SDLSysSemaphore() { + if (handle_) { + SDL_DestroySemaphore(handle_) ; + } handle_=0 ; } ; From dbc8a6c4e74ff525222426d1db6b4a236699e250 Mon Sep 17 00:00:00 2001 From: emartinez Date: Mon, 17 Aug 2026 18:44:25 +0200 Subject: [PATCH 05/13] fix: defer the project loading so the app doesn't crash when loading a project while playing another one --- sources/Application/AppWindow.cpp | 22 ++++++++++++++++++++-- sources/Application/AppWindow.h | 2 ++ 2 files changed, 22 insertions(+), 2 deletions(-) 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; From 5d066f586aa6b021ef670bdbb1e19c959e23b62d Mon Sep 17 00:00:00 2001 From: emartinez Date: Mon, 17 Aug 2026 18:49:27 +0200 Subject: [PATCH 06/13] fix: use POSIX [[:space:]] in depfile sed, BSD sed on macOS is missing \+ --- projects/rules_base | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From 59b7fb01bab92982c052cbd7e43ab3b84c545f9e Mon Sep 17 00:00:00 2001 From: emartinez Date: Mon, 17 Aug 2026 19:14:36 +0200 Subject: [PATCH 07/13] fix: lower audio engine default prebuffer count to 2, can't hear any artifact so it's safe --- sources/Adapters/SWITCH/System/SwitchSystem.cpp | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/sources/Adapters/SWITCH/System/SwitchSystem.cpp b/sources/Adapters/SWITCH/System/SwitchSystem.cpp index b81ead84..f2900861 100644 --- a/sources/Adapters/SWITCH/System/SwitchSystem.cpp +++ b/sources/Adapters/SWITCH/System/SwitchSystem.cpp @@ -64,14 +64,11 @@ void SwitchSystem::Boot(int argc, char **argv) { Trace::Log("System", "Installing SDL audio"); AudioSettings hint; hint.bufferSize_ = 1024; - hint.preBufferCount_ = 8; - // switch-sdl2's audio renderer runs at a hardcoded 48kHz mix rate - // (AudioRendererOutputRate_48kHz in its SDL_switchaudio.c) regardless - // of what we request; the per-voice rate conversion from a requested - // 44100 up to that fixed 48kHz mix rate wasn't behaving correctly in - // practice (playback audibly sped up, ~48000/44100 = 1.088x). Request - // 48000 directly so the voice rate matches the renderer's native rate - // and no resampling is needed at all. + 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)); From 0150ddb715bac00ec6e18096cef2ef60104c168f Mon Sep 17 00:00:00 2001 From: emartinez Date: Mon, 17 Aug 2026 19:39:42 +0200 Subject: [PATCH 08/13] feat: added Switch for lgpt_package and also for the github actions --- .github/workflows/build.yml | 40 ++++++++++++++++++++ .github/workflows/check.yml | 34 +++++++++++++++++ projects/resources/SWITCH/INSTALL_HOW_TO.txt | 26 +++++++++++++ projects/resources/SWITCH/config.xml | 11 ++++++ projects/resources/packaging/lgpt_package.sh | 26 ++++++++++--- 5 files changed, 132 insertions(+), 5 deletions(-) create mode 100644 projects/resources/SWITCH/INSTALL_HOW_TO.txt create mode 100644 projects/resources/SWITCH/config.xml 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/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/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/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 From 92c96de79c2662c04c7bf15c94e0f3c4cd059ed5 Mon Sep 17 00:00:00 2001 From: emartinez Date: Mon, 17 Aug 2026 19:45:15 +0200 Subject: [PATCH 09/13] chore(gitignore): added switch build artifacts to gitignore --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) 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 From 218e63f6f1cc6b76e4659c34ee6dbd7aa8c4a268 Mon Sep 17 00:00:00 2001 From: emartinez Date: Mon, 17 Aug 2026 19:52:19 +0200 Subject: [PATCH 10/13] chore(makefile): derive APP_VERSION from Project.h --- projects/Makefile.SWITCH | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/projects/Makefile.SWITCH b/projects/Makefile.SWITCH index e237060e..6c70dafc 100644 --- a/projects/Makefile.SWITCH +++ b/projects/Makefile.SWITCH @@ -62,7 +62,7 @@ EXTENSION := nro APP_TITLE := LittleGPTracker APP_AUTHOR := n0s2 -APP_VERSION := 1.0 +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 #--------------------------------------------------------------------------------- From 074aff54f334bb28020b0865142453f8dbad1746 Mon Sep 17 00:00:00 2001 From: emartinez Date: Mon, 17 Aug 2026 20:00:36 +0200 Subject: [PATCH 11/13] chore: bump Project version, update CHANGELOG and the wiki "What is LPGT" page --- CHANGELOG | 20 ++++++++++++++++++++ docs/wiki/What-is-LittlePiggyTracker.md | 2 ++ sources/Application/Model/Project.h | 2 +- 3 files changed, 23 insertions(+), 1 deletion(-) 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/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 From 8784f06f1bf29092c09bfed4608bc38b8655b15a Mon Sep 17 00:00:00 2001 From: emartinez Date: Mon, 17 Aug 2026 20:13:41 +0200 Subject: [PATCH 12/13] chore: added a comment on app UI calculation, it was not scaling properly before --- sources/Adapters/SDL2/GUI/SDLGUIWindowImp.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/sources/Adapters/SDL2/GUI/SDLGUIWindowImp.cpp b/sources/Adapters/SDL2/GUI/SDLGUIWindowImp.cpp index b060310e..5ebbf913 100644 --- a/sources/Adapters/SDL2/GUI/SDLGUIWindowImp.cpp +++ b/sources/Adapters/SDL2/GUI/SDLGUIWindowImp.cpp @@ -93,6 +93,7 @@ SDLGUIWindowImp::SDLGUIWindowImp(GUICreateWindowParams &p) } else { + // If drawing on a framebuffer or in a fullscreen app, calc the UI scale from size if (framebuffer_ || windowed_ == false) { mult_ = multFromSize; From 7f7bd820b2a77bf6b80c9d14a6374f55af6d7d40 Mon Sep 17 00:00:00 2001 From: emartinez Date: Mon, 17 Aug 2026 20:26:37 +0200 Subject: [PATCH 13/13] chore: check if clang-format hook is working properly --- sources/Adapters/SDL2/GUI/SDLGUIWindowImp.cpp | 21 ++++++++++--------- sources/Adapters/SDL2/Process/SDLProcess.cpp | 14 ++++++------- sources/Adapters/SWITCH/Main/SwitchMain.cpp | 4 ++-- .../Adapters/SWITCH/System/SwitchSystem.cpp | 4 ++-- 4 files changed, 22 insertions(+), 21 deletions(-) diff --git a/sources/Adapters/SDL2/GUI/SDLGUIWindowImp.cpp b/sources/Adapters/SDL2/GUI/SDLGUIWindowImp.cpp index 5ebbf913..84b7b3f4 100644 --- a/sources/Adapters/SDL2/GUI/SDLGUIWindowImp.cpp +++ b/sources/Adapters/SDL2/GUI/SDLGUIWindowImp.cpp @@ -48,22 +48,22 @@ SDLGUIWindowImp::SDLGUIWindowImp(GUICreateWindowParams &p) int screenWidth = 320; int screenHeight = 240; windowed_ = false; - #elif defined(PLATFORM_SWITCH) +#elif defined(PLATFORM_SWITCH) int screenWidth = 1280; int screenHeight = 720; windowed_ = false; - #else +#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_); @@ -93,8 +93,9 @@ SDLGUIWindowImp::SDLGUIWindowImp(GUICreateWindowParams &p) } else { - // If drawing on a framebuffer or in a fullscreen app, calc the UI scale from size - if (framebuffer_ || windowed_ == false) + // 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 0c9cbcbd..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,"SysThread",&thread); - return true ; + SDL_CreateThread(_SDLStartThread, "SysThread", &thread); + return true ; } SysSemaphore *SDLProcessFactory::CreateNewSemaphore(int initialcount, int maxcount) { @@ -18,14 +18,14 @@ SysSemaphore *SDLProcessFactory::CreateNewSemaphore(int initialcount, int maxcou } ; SDLSysSemaphore::SDLSysSemaphore(int initialcount,int maxcount) { - handle_=SDL_CreateSemaphore(initialcount) ; + handle_ = SDL_CreateSemaphore(initialcount); } ; SDLSysSemaphore::~SDLSysSemaphore() { - if (handle_) { - SDL_DestroySemaphore(handle_) ; - } - 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 index 9bf77511..328c14ef 100644 --- a/sources/Adapters/SWITCH/Main/SwitchMain.cpp +++ b/sources/Adapters/SWITCH/Main/SwitchMain.cpp @@ -1,7 +1,7 @@ -#include -#include "Adapters/SWITCH/System/SwitchSystem.h" #include "Adapters/SDL2/GUI/SDLGUIWindowImp.h" +#include "Adapters/SWITCH/System/SwitchSystem.h" #include "Application/Application.h" +#include /* * entrypoint for the Nintendo Switch homebrew target */ diff --git a/sources/Adapters/SWITCH/System/SwitchSystem.cpp b/sources/Adapters/SWITCH/System/SwitchSystem.cpp index f2900861..eafddf2a 100644 --- a/sources/Adapters/SWITCH/System/SwitchSystem.cpp +++ b/sources/Adapters/SWITCH/System/SwitchSystem.cpp @@ -4,14 +4,14 @@ #include "Adapters/SDL2/GUI/GUIFactory.h" #include "Adapters/SDL2/GUI/SDLEventManager.h" #include "Adapters/SDL2/GUI/SDLGUIWindowImp.h" -#include "Adapters/SDL2/Timer/SDLTimer.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 "System/Console/Logger.h" #include "SwitchNxlink.h" +#include "System/Console/Logger.h" #include #include