emoemu 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (213) hide show
  1. package/.claude/settings.local.json +77 -0
  2. package/.node-version +1 -0
  3. package/CLAUDE.md +435 -0
  4. package/README.md +404 -0
  5. package/TODO.md +655 -0
  6. package/dist/index.cjs +25108 -0
  7. package/dist/index.d.ts +1 -0
  8. package/dist/index.js +25085 -0
  9. package/docs/building-libretro-cores-arm-mac.md +237 -0
  10. package/docs/config-file-format.md +488 -0
  11. package/docs/cores-trd.md +425 -0
  12. package/docs/headless-hardware-rendering-trd.md +676 -0
  13. package/docs/libretro-cores-trd.md +997 -0
  14. package/docs/mupen64-software-rendering-trd.md +751 -0
  15. package/docs/n64-support-trd.md +306 -0
  16. package/docs/native-rendering-trd.md +540 -0
  17. package/docs/native-ui-rendering-trd.md +1195 -0
  18. package/docs/netplay-trd.md +665 -0
  19. package/docs/retroarch-netplay-docs.md +277 -0
  20. package/docs/save-state-format.md +666 -0
  21. package/eslint.config.js +111 -0
  22. package/icon/icon.png +0 -0
  23. package/icon/icon.pxd +0 -0
  24. package/package.json +63 -0
  25. package/pnpm-workspace.yaml +10 -0
  26. package/src/Emulator/consts.ts +14 -0
  27. package/src/Emulator/index.ts +2496 -0
  28. package/src/Emulator/saveState/index.ts +155 -0
  29. package/src/Emulator/screenshot/index.ts +160 -0
  30. package/src/Emulator/terminalDimensions/index.ts +79 -0
  31. package/src/Emulator/types.ts +83 -0
  32. package/src/cli/commands/consts.ts +10 -0
  33. package/src/cli/commands/index.ts +462 -0
  34. package/src/cli/parseArgs/consts.ts +17 -0
  35. package/src/cli/parseArgs/index.ts +457 -0
  36. package/src/cli/parseArgs/types.ts +61 -0
  37. package/src/cli/runEmulator/index.ts +406 -0
  38. package/src/cli/runEmulator/types.ts +7 -0
  39. package/src/consts.ts +19 -0
  40. package/src/core/button/consts.ts +35 -0
  41. package/src/core/button/index.ts +123 -0
  42. package/src/core/core.ts +300 -0
  43. package/src/core/index.ts +19 -0
  44. package/src/cores/libretro/api/index.ts +334 -0
  45. package/src/cores/libretro/api/types.ts +148 -0
  46. package/src/cores/libretro/callbacks/consts.ts +41 -0
  47. package/src/cores/libretro/callbacks/index.ts +456 -0
  48. package/src/cores/libretro/consts.ts +45 -0
  49. package/src/cores/libretro/coreOptions/consts.ts +36 -0
  50. package/src/cores/libretro/coreOptions/index.ts +222 -0
  51. package/src/cores/libretro/environment/consts.ts +118 -0
  52. package/src/cores/libretro/environment/index.ts +1095 -0
  53. package/src/cores/libretro/index.ts +937 -0
  54. package/src/cores/libretro/loader/index.ts +496 -0
  55. package/src/cores/libretro/pixelFormat/consts.ts +43 -0
  56. package/src/cores/libretro/pixelFormat/index.ts +397 -0
  57. package/src/cores/libretro/types.ts +339 -0
  58. package/src/frontend/AudioManager/index.ts +420 -0
  59. package/src/frontend/SettingsManager/index.ts +250 -0
  60. package/src/frontend/config/index.ts +608 -0
  61. package/src/frontend/config/tests.ts +354 -0
  62. package/src/frontend/config/types.ts +36 -0
  63. package/src/frontend/consts.ts +114 -0
  64. package/src/frontend/coreBuilder/index.ts +644 -0
  65. package/src/frontend/coreBuilder/types.ts +15 -0
  66. package/src/frontend/coreDownloader/index.ts +620 -0
  67. package/src/frontend/coreDownloader/types.ts +17 -0
  68. package/src/frontend/corePreferences/index.ts +69 -0
  69. package/src/frontend/coreRegistry/index.ts +276 -0
  70. package/src/frontend/directoryCache/index.ts +75 -0
  71. package/src/frontend/index.ts +79 -0
  72. package/src/frontend/notifications/consts.ts +14 -0
  73. package/src/frontend/notifications/index.ts +250 -0
  74. package/src/frontend/playlist/consts.ts +168 -0
  75. package/src/frontend/playlist/index.ts +899 -0
  76. package/src/frontend/playlist/labelFormatter/consts.ts +15 -0
  77. package/src/frontend/playlist/labelFormatter/index.ts +155 -0
  78. package/src/frontend/playlist/labelFormatter/tests.ts +153 -0
  79. package/src/frontend/playlist/reader/index.ts +559 -0
  80. package/src/frontend/playlist/sync/index.ts +511 -0
  81. package/src/frontend/playlist/systemLookup/index.ts +233 -0
  82. package/src/frontend/playlist/utils/index.ts +50 -0
  83. package/src/frontend/romScanner/consts.ts +348 -0
  84. package/src/frontend/romScanner/index.ts +1957 -0
  85. package/src/frontend/saveServices/consts.ts +2 -0
  86. package/src/frontend/saveServices/index.ts +313 -0
  87. package/src/frontend/serviceProvider/index.ts +108 -0
  88. package/src/frontend/serviceProvider/types.ts +13 -0
  89. package/src/index.ts +428 -0
  90. package/src/input/Controller/consts.ts +50 -0
  91. package/src/input/Controller/index.ts +81 -0
  92. package/src/input/GamepadManager/consts.ts +22 -0
  93. package/src/input/GamepadManager/index.ts +418 -0
  94. package/src/input/InputManager/consts.ts +86 -0
  95. package/src/input/InputManager/index.ts +593 -0
  96. package/src/input/InputMapper/consts.ts +33 -0
  97. package/src/input/InputMapper/index.ts +436 -0
  98. package/src/input/consts.ts +410 -0
  99. package/src/input/gamepadProfiles/index.ts +1100 -0
  100. package/src/input/index.ts +38 -0
  101. package/src/input/inputUtils/index.ts +31 -0
  102. package/src/netplay/FrameBuffer/consts.ts +2 -0
  103. package/src/netplay/FrameBuffer/index.ts +364 -0
  104. package/src/netplay/FrameBuffer/tests.ts +286 -0
  105. package/src/netplay/InputBuffer/consts.ts +7 -0
  106. package/src/netplay/InputBuffer/index.ts +347 -0
  107. package/src/netplay/InputBuffer/tests.ts +166 -0
  108. package/src/netplay/NetplayClient/index.ts +976 -0
  109. package/src/netplay/NetplayConnection/index.ts +536 -0
  110. package/src/netplay/NetplayDiscovery/consts.ts +41 -0
  111. package/src/netplay/NetplayDiscovery/index.ts +525 -0
  112. package/src/netplay/NetplayServer/index.ts +1407 -0
  113. package/src/netplay/SyncManager/index.ts +984 -0
  114. package/src/netplay/SyncManager/tests.ts +419 -0
  115. package/src/netplay/consts.ts +371 -0
  116. package/src/netplay/crc32/consts.ts +14 -0
  117. package/src/netplay/crc32/index.ts +97 -0
  118. package/src/netplay/crc32/tests.ts +40 -0
  119. package/src/netplay/index.ts +41 -0
  120. package/src/netplay/netplayLogger/consts.ts +30 -0
  121. package/src/netplay/netplayLogger/index.ts +345 -0
  122. package/src/netplay/protocol/consts.ts +86 -0
  123. package/src/netplay/protocol/index.ts +1280 -0
  124. package/src/netplay/protocol/tests.ts +606 -0
  125. package/src/netplay/protocol/types.ts +20 -0
  126. package/src/netplay/types.ts +395 -0
  127. package/src/rendering/KittyRenderer/index.ts +616 -0
  128. package/src/rendering/NativeRenderer/index.ts +279 -0
  129. package/src/rendering/NativeRenderer/tests.ts +133 -0
  130. package/src/rendering/TerminalRenderer/index.ts +770 -0
  131. package/src/rendering/consts.ts +401 -0
  132. package/src/rendering/fonts/CozetteVector.ttf +0 -0
  133. package/src/rendering/index.ts +26 -0
  134. package/src/rendering/nativeUi/NativeWindowManager/index.ts +158 -0
  135. package/src/rendering/nativeUi/NativeWindowManager/tests.ts +81 -0
  136. package/src/rendering/nativeUi/consts.ts +6 -0
  137. package/src/rendering/nativeUi/index.ts +20 -0
  138. package/src/rendering/postProcessing/consts.ts +38 -0
  139. package/src/rendering/postProcessing/index.ts +923 -0
  140. package/src/rendering/shared/ansi/consts.ts +12 -0
  141. package/src/rendering/shared/ansi/index.ts +104 -0
  142. package/src/rendering/shared/consts.ts +2 -0
  143. package/src/rendering/shared/fitToTerminal/index.ts +67 -0
  144. package/src/ui/AddRomsPrompt/consts.ts +13 -0
  145. package/src/ui/AddRomsPrompt/index.tsx +781 -0
  146. package/src/ui/App/consts.ts +2 -0
  147. package/src/ui/App/index.tsx +456 -0
  148. package/src/ui/AppCapabilities/index.tsx +67 -0
  149. package/src/ui/ConfigContext/index.tsx +56 -0
  150. package/src/ui/CoreManager/consts.ts +11 -0
  151. package/src/ui/CoreManager/index.tsx +779 -0
  152. package/src/ui/CoreSelector/consts.ts +2 -0
  153. package/src/ui/CoreSelector/index.tsx +251 -0
  154. package/src/ui/DialogContainer/index.tsx +42 -0
  155. package/src/ui/DialogOptionsList/index.tsx +61 -0
  156. package/src/ui/DuplicateCrcPrompt/consts.ts +5 -0
  157. package/src/ui/DuplicateCrcPrompt/index.tsx +146 -0
  158. package/src/ui/GamepadContext/consts.ts +15 -0
  159. package/src/ui/GamepadContext/index.tsx +295 -0
  160. package/src/ui/NativeDialog/index.tsx +120 -0
  161. package/src/ui/NetplayDisconnectedDialog/index.tsx +93 -0
  162. package/src/ui/NetplayPauseMenu/consts.ts +2 -0
  163. package/src/ui/NetplayPauseMenu/index.tsx +97 -0
  164. package/src/ui/RomBrowser/NetplayPanel/consts.ts +24 -0
  165. package/src/ui/RomBrowser/NetplayPanel/index.tsx +520 -0
  166. package/src/ui/RomBrowser/SettingsPanel/index.tsx +478 -0
  167. package/src/ui/RomBrowser/consts.ts +61 -0
  168. package/src/ui/RomBrowser/index.tsx +1164 -0
  169. package/src/ui/RomBrowser/settingsConfig/index.ts +320 -0
  170. package/src/ui/RomBrowser/types.ts +67 -0
  171. package/src/ui/SaveStateDialog/consts.ts +2 -0
  172. package/src/ui/SaveStateDialog/index.tsx +225 -0
  173. package/src/ui/WarningDialog/index.tsx +113 -0
  174. package/src/ui/consts.ts +27 -0
  175. package/src/ui/hooks/useClearTerminal/consts.ts +2 -0
  176. package/src/ui/hooks/useClearTerminal/index.ts +37 -0
  177. package/src/ui/hooks/useDialogNavigation/index.ts +99 -0
  178. package/src/ui/hooks/useGamepad/consts.ts +21 -0
  179. package/src/ui/hooks/useGamepad/index.ts +194 -0
  180. package/src/ui/index.ts +27 -0
  181. package/src/utils/buffer/consts.ts +17 -0
  182. package/src/utils/buffer/index.ts +129 -0
  183. package/src/utils/color/consts.ts +58 -0
  184. package/src/utils/color/index.ts +183 -0
  185. package/src/utils/compression/consts.ts +50 -0
  186. package/src/utils/compression/index.ts +101 -0
  187. package/src/utils/consts.ts +2 -0
  188. package/src/utils/crc32/consts.ts +22 -0
  189. package/src/utils/crc32/index.ts +83 -0
  190. package/src/utils/ensureDirectory/index.ts +10 -0
  191. package/src/utils/format/consts.ts +8 -0
  192. package/src/utils/format/index.ts +53 -0
  193. package/src/utils/getErrorMessage/index.ts +10 -0
  194. package/src/utils/index.ts +113 -0
  195. package/src/utils/ini/index.ts +200 -0
  196. package/src/utils/kitty/consts.ts +13 -0
  197. package/src/utils/kitty/index.ts +181 -0
  198. package/src/utils/logger/consts.ts +35 -0
  199. package/src/utils/logger/index.ts +217 -0
  200. package/src/utils/paths/consts.ts +18 -0
  201. package/src/utils/paths/index.ts +151 -0
  202. package/src/utils/png/consts.ts +34 -0
  203. package/src/utils/png/index.ts +131 -0
  204. package/src/utils/readJsonFile/index.ts +16 -0
  205. package/src/utils/rotateLogFile/index.ts +44 -0
  206. package/src/utils/safeClose/index.ts +10 -0
  207. package/src/utils/terminal/consts.ts +8 -0
  208. package/src/utils/terminal/index.ts +102 -0
  209. package/src/utils/thumbnailRenderer/consts.ts +2 -0
  210. package/src/utils/thumbnailRenderer/index.ts +147 -0
  211. package/src/utils/typedError/index.ts +26 -0
  212. package/tsconfig.json +31 -0
  213. package/vitest.config.ts +13 -0
@@ -0,0 +1,751 @@
1
+ # Mupen64Plus Software-Only Rendering Build - Technical Requirements Document
2
+
3
+ This document describes the requirements and implementation plan for building a software-rendering-only version of the Mupen64Plus-Next libretro core for use in emoemu's terminal-based emulator.
4
+
5
+ ## Problem Statement
6
+
7
+ emoemu is a terminal-based emulator that renders graphics using the Kitty graphics protocol, Unicode half-blocks, ASCII, or emoji characters. It has **no access to GPU acceleration** (OpenGL, Vulkan, Metal) because:
8
+
9
+ 1. Terminal emulators don't provide GPU context
10
+ 2. The emulator runs in headless/terminal environments
11
+ 3. All rendering must go through a CPU-generated framebuffer
12
+
13
+ The pre-built Mupen64Plus-Next cores from the RetroArch buildbot **require OpenGL** because they default to GlideN64, a high-level emulation (HLE) renderer that requires OpenGL. Even when configured to use the Angrylion software renderer at runtime, the core still links against OpenGL and may fail to load or initialize in environments without OpenGL support.
14
+
15
+ ### Previous Success
16
+
17
+ A custom build of mupen64plus_next was previously created that:
18
+ - Successfully ran N64 games in terminal mode
19
+ - Used Angrylion for software-only RDP rendering
20
+ - Used Parallel RSP with ARM64 dynarec for fast RSP emulation
21
+ - Did not require any GPU/OpenGL at runtime
22
+
23
+ This build has been lost and we need to recreate it.
24
+
25
+ ---
26
+
27
+ ## Goals
28
+
29
+ 1. **Software-Only Rendering**: Build a mupen64plus_next core that uses Angrylion RDP exclusively
30
+ 2. **No GPU Dependencies**: Eliminate OpenGL/Vulkan/Metal linking and runtime requirements
31
+ 3. **Optimal Performance**: Enable ARM64 dynarec for CPU emulation and Parallel RSP
32
+ 4. **Automated Building**: Integrate the build process into emoemu's core-builder system
33
+ 5. **Cross-Platform**: Support ARM64 macOS initially, with potential for Linux ARM64
34
+
35
+ ---
36
+
37
+ ## Background: Mupen64Plus-Next Architecture
38
+
39
+ ### Renderer Plugins
40
+
41
+ The mupen64plus-libretro-nx core includes multiple RDP (graphics) plugins:
42
+
43
+ | Plugin | Type | GPU Required | Accuracy | Performance |
44
+ |--------|------|--------------|----------|-------------|
45
+ | **GlideN64** | HLE | Yes (OpenGL) | Good | Fast |
46
+ | **ParaLLEl RDP** | LLE | Yes (Vulkan) | Pixel-perfect | Fast |
47
+ | **Angrylion** | LLE | **No** (CPU) | Pixel-perfect | Slow-Medium |
48
+
49
+ For emoemu, **Angrylion is the only viable option**.
50
+
51
+ ### RSP Plugins
52
+
53
+ | Plugin | Type | Notes |
54
+ |--------|------|-------|
55
+ | **HLE RSP** | High-level | Fast but incompatible with Angrylion |
56
+ | **Parallel RSP** | Dynarec | Fast, required for Angrylion |
57
+ | **Cxd4** | Interpreter | Slower fallback |
58
+
59
+ **Parallel RSP is recommended** for use with Angrylion.
60
+
61
+ ### Build Flags (from Makefile analysis)
62
+
63
+ | Flag | Default (macOS) | Purpose |
64
+ |------|-----------------|---------|
65
+ | `HAVE_PARALLEL_RDP` | 1 | Vulkan-based RDP (we need 0) |
66
+ | `HAVE_PARALLEL_RSP` | 1 | RSP dynarec (we need 1) |
67
+ | `HAVE_THR_AL` | 1 | Angrylion threading (we need 1) |
68
+ | `LLE` | 1 | Low-level emulation (we need 1) |
69
+ | `WITH_DYNAREC` | (empty) | CPU dynarec (we need `aarch64`) |
70
+ | `GL_LIB` | `-framework OpenGL` | OpenGL linking (problematic) |
71
+
72
+ ---
73
+
74
+ ## Analysis of Current Build Configuration
75
+
76
+ ### Current emoemu Build Config
77
+
78
+ From `src/frontend/core-builder.ts`:
79
+
80
+ ```typescript
81
+ mupen64plus_next: {
82
+ repo: "https://github.com/libretro/mupen64plus-libretro-nx.git",
83
+ buildArgs: [
84
+ "platform=osx",
85
+ "HAVE_PARALLEL_RDP=0", // Disable Vulkan-dependent ParaLLEl RDP
86
+ "WITH_DYNAREC=aarch64", // Enable ARM64 dynamic recompiler
87
+ ],
88
+ outputFile: "mupen64plus_next_libretro.dylib",
89
+ installedFile: "mupen64plus_next_libretro.dylib",
90
+ description: "Nintendo 64 (Mupen64Plus-Next with Angrylion software renderer)",
91
+ }
92
+ ```
93
+
94
+ ### Problems with Current Config
95
+
96
+ 1. **OpenGL Still Linked**: The Makefile's `platform=osx` section hardcodes `GL_LIB := -framework OpenGL`
97
+ 2. **GlideN64 Still Compiled**: No flag to exclude GlideN64 sources, which depend on OpenGL
98
+ 3. **Runtime OpenGL Check**: The core may still check for OpenGL at runtime even when using Angrylion
99
+ 4. **Missing Flags**: `HAVE_THR_AL` and `HAVE_PARALLEL_RSP` should be explicitly set
100
+
101
+ ---
102
+
103
+ ## Proposed Solution
104
+
105
+ ### Option A: Makefile Modifications (Recommended)
106
+
107
+ Create a modified build that:
108
+ 1. Sets `GL_LIB` to empty to prevent OpenGL linking
109
+ 2. Explicitly enables software-only flags
110
+ 3. Potentially patches out GlideN64 compilation
111
+
112
+ **Proposed Build Command:**
113
+
114
+ ```bash
115
+ make -j$(nproc) \
116
+ platform=osx \
117
+ HAVE_PARALLEL_RDP=0 \
118
+ HAVE_PARALLEL_RSP=1 \
119
+ HAVE_THR_AL=1 \
120
+ LLE=1 \
121
+ WITH_DYNAREC=aarch64 \
122
+ GL_LIB=
123
+ ```
124
+
125
+ The key addition is `GL_LIB=` (empty) to prevent OpenGL framework linking.
126
+
127
+ ### Option B: Source Patching
128
+
129
+ If Option A doesn't work, patch the source:
130
+
131
+ 1. Modify `Makefile` to add a `SOFTWARE_ONLY=1` flag
132
+ 2. When set, skip GlideN64 sources and clear `GL_LIB`
133
+ 3. Add preprocessor define to disable OpenGL code paths
134
+
135
+ ### Option C: Fork with Software-Only Configuration
136
+
137
+ Create a minimal fork that:
138
+ 1. Removes GlideN64 entirely
139
+ 2. Removes OpenGL/Vulkan dependencies
140
+ 3. Keeps only Angrylion + Parallel RSP + Cxd4
141
+
142
+ ---
143
+
144
+ ## Implementation Plan
145
+
146
+ ### Phase 1: Investigate Build Failure
147
+
148
+ 1. Clone mupen64plus-libretro-nx repository
149
+ 2. Attempt build with current config
150
+ 3. Capture and analyze build errors
151
+ 4. Identify exactly what's failing and why
152
+
153
+ ```bash
154
+ git clone --depth 1 https://github.com/libretro/mupen64plus-libretro-nx.git
155
+ cd mupen64plus-libretro-nx
156
+ make -j$(sysctl -n hw.ncpu) platform=osx HAVE_PARALLEL_RDP=0 WITH_DYNAREC=aarch64 2>&1 | tee build.log
157
+ ```
158
+
159
+ ### Phase 2: Test GL_LIB Override
160
+
161
+ 1. Try building with `GL_LIB=` (empty)
162
+ 2. If linker fails, identify which objects require OpenGL symbols
163
+ 3. Determine if those objects can be excluded
164
+
165
+ ```bash
166
+ make -j$(sysctl -n hw.ncpu) \
167
+ platform=osx \
168
+ HAVE_PARALLEL_RDP=0 \
169
+ HAVE_PARALLEL_RSP=1 \
170
+ HAVE_THR_AL=1 \
171
+ WITH_DYNAREC=aarch64 \
172
+ GL_LIB= \
173
+ 2>&1 | tee build-no-gl.log
174
+ ```
175
+
176
+ ### Phase 3: Source Analysis
177
+
178
+ If Phase 2 fails, analyze which source files require OpenGL:
179
+
180
+ 1. Search for OpenGL includes: `grep -r "OpenGL\|GL/gl\|GLES" --include="*.c" --include="*.cpp" --include="*.h"`
181
+ 2. Identify conditional compilation: `grep -r "#ifdef.*GL\|#if.*OPENGL" --include="*.c" --include="*.cpp"`
182
+ 3. Find GlideN64 source boundaries in Makefile.common
183
+ 4. Determine minimal exclusion set
184
+
185
+ ### Phase 4: Implement Software-Only Build
186
+
187
+ Based on findings, implement one of:
188
+
189
+ **4A: Build Flag Solution**
190
+ ```typescript
191
+ // Updated core-builder.ts
192
+ mupen64plus_next: {
193
+ repo: "https://github.com/libretro/mupen64plus-libretro-nx.git",
194
+ buildArgs: [
195
+ "platform=osx",
196
+ "HAVE_PARALLEL_RDP=0",
197
+ "HAVE_PARALLEL_RSP=1",
198
+ "HAVE_THR_AL=1",
199
+ "LLE=1",
200
+ "WITH_DYNAREC=aarch64",
201
+ "GL_LIB=", // Prevent OpenGL linking
202
+ ],
203
+ // ...
204
+ }
205
+ ```
206
+
207
+ **4B: Patch-Based Solution**
208
+ ```typescript
209
+ // Add pre-build patching step
210
+ const patchMakefile = (repoDir: string): void => {
211
+ const makefilePath = join(repoDir, 'Makefile');
212
+ let content = readFileSync(makefilePath, 'utf-8');
213
+
214
+ // Remove OpenGL linking for osx platform
215
+ content = content.replace(
216
+ /GL_LIB := -framework OpenGL/g,
217
+ 'GL_LIB :='
218
+ );
219
+
220
+ writeFileSync(makefilePath, content);
221
+ };
222
+ ```
223
+
224
+ **4C: Fork Solution**
225
+
226
+ Create `https://github.com/emoemu/mupen64plus-software-only` with:
227
+ - Removed GlideN64 directory
228
+ - Removed ParaLLEl RDP sources
229
+ - Modified Makefile with no OpenGL dependencies
230
+ - Simplified build for software-only operation
231
+
232
+ ### Phase 5: Runtime Configuration
233
+
234
+ Ensure core options are set correctly at runtime:
235
+
236
+ ```typescript
237
+ // In core-options.ts - DEFAULT_CORE_OPTIONS
238
+ 'mupen64plus_next': {
239
+ 'mupen64plus-rdp-plugin': 'angrylion',
240
+ 'mupen64plus-rsp-plugin': 'parallel',
241
+ 'mupen64plus-cpucore': 'dynamic_recompiler',
242
+ 'mupen64plus-angrylion-multithread': 'all threads',
243
+ }
244
+ ```
245
+
246
+ ### Phase 6: Validation
247
+
248
+ 1. Build completes without errors
249
+ 2. Core loads without OpenGL errors
250
+ 3. N64 ROM boots and renders
251
+ 4. Frame output appears in terminal
252
+ 5. Performance is acceptable (30+ FPS on M1)
253
+
254
+ ---
255
+
256
+ ## Core Options for Software Rendering
257
+
258
+ These options must be applied when loading the core:
259
+
260
+ ```ini
261
+ ; RDP Plugin - MUST be angrylion for software rendering
262
+ mupen64plus-rdp-plugin = "angrylion"
263
+
264
+ ; RSP Plugin - parallel is fastest, cxd4 is fallback
265
+ mupen64plus-rsp-plugin = "parallel"
266
+
267
+ ; CPU Core - dynamic recompiler for performance
268
+ mupen64plus-cpucore = "dynamic_recompiler"
269
+
270
+ ; Threading - use all CPU cores for Angrylion
271
+ mupen64plus-angrylion-multithread = "all threads"
272
+
273
+ ; VI output - bilinear filtering (optional)
274
+ mupen64plus-angrylion-vioverlay = "Filtered"
275
+ ```
276
+
277
+ ---
278
+
279
+ ## Testing the Build
280
+
281
+ To test building the core with emoemu's built-in core builder:
282
+
283
+ ```bash
284
+ pnpm run start -- --install-core mupen64plus_next
285
+ ```
286
+
287
+ This will:
288
+ 1. Detect that mupen64plus_next requires building from source on ARM Mac
289
+ 2. Clone the repository
290
+ 3. Build with the configured flags
291
+ 4. Install to cores directory (see Data Directories below)
292
+ 5. Display progress and any errors to the console
293
+
294
+ For development, you can also build manually to capture full output:
295
+
296
+ ```bash
297
+ git clone --depth 1 https://github.com/libretro/mupen64plus-libretro-nx.git
298
+ cd mupen64plus-libretro-nx
299
+ make -j$(sysctl -n hw.ncpu) platform=osx HAVE_PARALLEL_RDP=0 WITH_DYNAREC=aarch64 2>&1 | tee build.log
300
+ ```
301
+
302
+ ---
303
+
304
+ ## Testing Checklist
305
+
306
+ ### Build Verification
307
+ - [ ] Clone repository succeeds
308
+ - [ ] Build completes without errors
309
+ - [ ] Output .dylib is ARM64 architecture (`file` command)
310
+ - [ ] No OpenGL symbols in binary (`nm -u` shows no GL references)
311
+ - [ ] File size is reasonable (smaller than full build)
312
+
313
+ ### Runtime Verification
314
+ - [ ] Core loads in emoemu without OpenGL errors
315
+ - [ ] `retro_load_game()` succeeds
316
+ - [ ] Frame callback receives valid framebuffer data
317
+ - [ ] Video renders correctly in terminal
318
+
319
+ ### Game Compatibility
320
+ - [ ] Super Mario 64 boots and is playable
321
+ - [ ] The Legend of Zelda: Ocarina of Time renders correctly
322
+ - [ ] Audio plays without issues
323
+ - [ ] Save states work
324
+
325
+ ### Performance
326
+ - [ ] Achieves 30+ FPS on Apple M1
327
+ - [ ] Achieves 60 FPS on Apple M1 Pro/Max
328
+ - [ ] Multi-threading is functional
329
+
330
+ ---
331
+
332
+ ## Risk Assessment
333
+
334
+ | Risk | Likelihood | Impact | Mitigation |
335
+ |------|------------|--------|------------|
336
+ | GlideN64 cannot be excluded | Medium | High | Fork with sources removed |
337
+ | OpenGL symbols required at runtime | Low | High | Patch source to remove checks |
338
+ | Angrylion too slow | Low | Medium | Enable all multi-threading options |
339
+ | Build system changes upstream | Low | Low | Pin to specific commit |
340
+
341
+ ---
342
+
343
+ ## Alternative Approaches
344
+
345
+ ### Use Different N64 Core
346
+
347
+ **parallel-n64** is another libretro N64 core that may be easier to build software-only. However:
348
+ - Less actively maintained
349
+ - May have different accuracy/compatibility
350
+ - Still includes GPU renderers
351
+
352
+ ### RetroArch Headless Mode
353
+
354
+ RetroArch has a `--video-driver=null` option but this doesn't help with core linking requirements.
355
+
356
+ ### Emscripten/WASM Build
357
+
358
+ Some libretro cores have been compiled to WebAssembly which doesn't have OpenGL linking. This approach is complex but guarantees no GPU dependencies.
359
+
360
+ ---
361
+
362
+ ## Resources
363
+
364
+ - [mupen64plus-libretro-nx Repository](https://github.com/libretro/mupen64plus-libretro-nx)
365
+ - [Mupen64Plus-Next 2.0 Announcement](https://www.libretro.com/index.php/mupen64plus-next-2-0-64dd-support-angrylion-and-gliden64-in-one-build-parallel-rsp-support-and-android/)
366
+ - [Angrylion RDP Plus](https://github.com/ata4/angrylion-rdp-plus)
367
+ - [Libretro Docs - Mupen64Plus](https://docs.libretro.com/library/mupen64plus/)
368
+ - [Emulation General Wiki - N64 Plugins](https://emulation.gametechwiki.com/index.php/Recommended_N64_plugins)
369
+
370
+ ---
371
+
372
+ ## Appendix: Makefile Deep Dive
373
+
374
+ ### macOS Platform Block (from Makefile)
375
+
376
+ ```makefile
377
+ else ifeq ($(platform), osx)
378
+ TARGET := $(TARGET_NAME)_libretro.dylib
379
+ LDFLAGS += -dynamiclib
380
+ OSXVER = $(shell sw_vers -productVersion | cut -d. -f 1)
381
+ OSX_LT_MAVERICKS = $(shell [ $(OSXVER) -lt 10 ] && echo "YES")
382
+ LDFLAGS += -mmacosx-version-min=10.7
383
+ LDFLAGS += -stdlib=libc++
384
+
385
+ PLATFLAGS += -DOS_MAC_OS_X
386
+ GL_LIB := -framework OpenGL # <-- PROBLEM: hardcoded
387
+
388
+ CFLAGS += -DOS_MAC_OS_X
389
+ CPUFLAGS := -msse -msse2
390
+ WITH_DYNAREC = # <-- PROBLEM: dynarec disabled by default
391
+ HAVE_PARALLEL_RSP = 1
392
+ HAVE_PARALLEL_RDP = 1 # <-- We override to 0
393
+ HAVE_THR_AL = 1
394
+ LLE = 1
395
+ ```
396
+
397
+ ### Key Variables to Override
398
+
399
+ ```bash
400
+ # Required overrides for software-only build
401
+ HAVE_PARALLEL_RDP=0 # Disable Vulkan RDP
402
+ WITH_DYNAREC=aarch64 # Enable ARM64 CPU dynarec
403
+ GL_LIB= # Empty to prevent OpenGL linking (needs testing)
404
+
405
+ # Should already be set by platform=osx but explicit is safer
406
+ HAVE_PARALLEL_RSP=1 # Enable fast RSP dynarec
407
+ HAVE_THR_AL=1 # Enable Angrylion threading
408
+ LLE=1 # Enable low-level emulation
409
+ ```
410
+
411
+ ---
412
+
413
+ ## Implementation Status: COMPLETE
414
+
415
+ The software-only build is now working. The solution required:
416
+
417
+ ### 1. Source Patches for macOS Compatibility
418
+
419
+ Two patches are automatically applied before building:
420
+
421
+ **Patch 1: libpng fp.h fix**
422
+ ```c
423
+ // custom/dependencies/libpng/pngpriv.h
424
+ // Replace: #include <fp.h>
425
+ // With: #include <math.h>
426
+ ```
427
+ The `fp.h` header was for Classic Mac OS and doesn't exist on modern macOS.
428
+
429
+ **Patch 2: libzlib fdopen fix**
430
+ ```c
431
+ // custom/dependencies/libzlib/zutil.h
432
+ // Replace: #if defined(MACOS) || defined(TARGET_OS_MAC)
433
+ // With: #if (defined(MACOS) || defined(TARGET_OS_MAC)) && !defined(__APPLE__)
434
+ ```
435
+ The bundled zlib incorrectly defines `fdopen()` as NULL for `TARGET_OS_MAC`, but modern macOS has `fdopen()`. This breaks when `_stdio.h` is included.
436
+
437
+ ### 2. Build Flags
438
+
439
+ The final working build configuration:
440
+ ```bash
441
+ make platform=osx \
442
+ HAVE_PARALLEL_RDP=0 \ # Disable Vulkan RDP
443
+ HAVE_PARALLEL_RSP=1 \ # Enable RSP dynarec
444
+ HAVE_THR_AL=1 \ # Enable Angrylion threading
445
+ LLE=1 # Enable low-level emulation
446
+ ```
447
+
448
+ **Note:** `WITH_DYNAREC=aarch64` is intentionally omitted because the ARM64 dynarec assembly uses GNU syntax (`.hidden`, `.type %function`) that's incompatible with macOS's LLVM assembler. The CPU runs in interpreter mode, which is slower but compatible. The Parallel RSP still uses its own dynarec which does work.
449
+
450
+ ### 3. What Gets Built
451
+
452
+ The resulting core includes:
453
+ - **Angrylion RDP**: Software renderer (CPU-only, no GPU)
454
+ - **Parallel RSP**: Fast RSP with dynarec
455
+ - **GlideN64**: Also compiled (OpenGL), but runtime core options select Angrylion
456
+
457
+ At runtime, the core options are set to use Angrylion:
458
+ ```ini
459
+ mupen64plus-rdp-plugin = "angrylion"
460
+ mupen64plus-rsp-plugin = "parallel"
461
+ ```
462
+
463
+ ### Installation
464
+
465
+ ```bash
466
+ pnpm run start -- --install-core mupen64plus_next
467
+ ```
468
+
469
+ This will:
470
+ 1. Clone the repository
471
+ 2. Apply source patches automatically
472
+ 3. Build with software rendering flags
473
+ 4. Install to cores directory (see Data Directories below)
474
+
475
+ ---
476
+
477
+ ## Data Directories
478
+
479
+ emoemu stores data in platform-specific directories:
480
+
481
+ | Platform | Base Directory |
482
+ |----------|----------------|
483
+ | macOS | `~/Library/Application Support/emoemu/` |
484
+ | Linux | `~/.config/emoemu/` |
485
+ | Windows | `%APPDATA%\emoemu\` |
486
+
487
+ **Subdirectories:**
488
+
489
+ | Directory | Purpose |
490
+ |-----------|---------|
491
+ | `cores/` | Installed libretro cores (`.dylib`, `.so`, `.dll`) |
492
+ | `logs/` | Log files including `emoemu.log` |
493
+ | `saves/` | Save data (SRAM, memory cards) |
494
+ | `states/` | Save states |
495
+ | `system/` | BIOS files and system data |
496
+ | `config/` | Per-core configuration files |
497
+
498
+ ---
499
+
500
+ ## Debugging the N64 Core
501
+
502
+ If the core builds and installs but displays all black when running, follow these steps to debug:
503
+
504
+ ### Step 1: Clean Environment
505
+
506
+ Kill any lingering node processes that might be holding state:
507
+
508
+ ```bash
509
+ killall -9 node
510
+ ```
511
+
512
+ ### Step 2: Remove Existing Core
513
+
514
+ Remove the currently installed core to ensure a fresh build:
515
+
516
+ ```bash
517
+ pnpm run start -- --remove-core mupen64plus_next
518
+ ```
519
+
520
+ ### Step 3: Rebuild the Project
521
+
522
+ If you made changes to the core builder, rebuild:
523
+
524
+ ```bash
525
+ pnpm run build
526
+ ```
527
+
528
+ ### Step 4: Install Fresh Core
529
+
530
+ Install the core with the updated build process:
531
+
532
+ ```bash
533
+ pnpm run start -- --install-core mupen64plus_next
534
+ ```
535
+
536
+ ### Step 5: Test with Emoji Mode
537
+
538
+ Run a ROM with emoji mode (good for debugging since it's simple output):
539
+
540
+ ```bash
541
+ pnpm run start -- --core mupen64plus_next ~/ROMs/N64/Super\ Mario\ 64\ \(USA\).z64 --emoji
542
+ ```
543
+
544
+ **Expected behavior:**
545
+ - After a brief loading period, you should see colored emoji output
546
+ - If you only see black emojis after 10-15 seconds, the core is not rendering properly
547
+
548
+ ### Step 6: Check Logs
549
+
550
+ If rendering fails, kill the process and check the logs:
551
+
552
+ ```bash
553
+ killall -9 node
554
+ ```
555
+
556
+ Then examine the log file:
557
+
558
+ ```bash
559
+ cat ~/Library/Application\ Support/emoemu/logs/emoemu.log # macOS
560
+ cat ~/.config/emoemu/logs/emoemu.log # Linux
561
+ ```
562
+
563
+ Look for errors related to:
564
+ - `mupen64plus` or `angrylion` - RDP plugin issues
565
+ - `OpenGL` or `GL` - Indicates the core is trying to use GPU rendering
566
+ - `GET_VARIABLE` - Core options not being applied correctly
567
+ - `video callback` - Framebuffer issues
568
+
569
+ ### Common Issues
570
+
571
+ | Symptom | Likely Cause | Fix |
572
+ |---------|--------------|-----|
573
+ | All black output | Core using GlideN64 instead of Angrylion | Check core options are set correctly |
574
+ | "OpenGL support required" | Core built with wrong flags | Rebuild with `HAVE_PARALLEL_RDP=0` |
575
+ | Crash on load | Missing dependencies | Check build output for errors |
576
+ | Very slow / stuttering | CPU dynarec not working | Expected on macOS (interpreter mode) |
577
+
578
+ ### Verify Core Options
579
+
580
+ The core options should be set to use Angrylion. Check the core options file (macOS: `~/Library/Application Support/emoemu/retroarch-core-options.cfg`, Linux: `~/.config/emoemu/retroarch-core-options.cfg`) contains:
581
+
582
+ ```ini
583
+ mupen64plus-rdp-plugin = "angrylion"
584
+ mupen64plus-rsp-plugin = "parallel"
585
+ ```
586
+
587
+ If these aren't being applied, the core may default to GlideN64 which requires OpenGL.
588
+
589
+ ### Adding More Logging
590
+
591
+ If the logs don't provide enough information to diagnose the issue, you may need to add additional logging or adjust log verbosity:
592
+
593
+ **1. Enable debug logging in config:**
594
+
595
+ Edit `~/.config/emoemu/emoemu.cfg` (or `~/Library/Application Support/emoemu/emoemu.cfg` on macOS):
596
+
597
+ ```ini
598
+ log_verbosity = 2
599
+ ```
600
+
601
+ **2. Add logging to libretro environment handler:**
602
+
603
+ Key file: `src/cores/libretro/environment/index.ts`
604
+
605
+ Add `logger.info()` calls to trace:
606
+ - Which environment commands are being called
607
+ - What values are being requested/returned for `GET_VARIABLE`
608
+ - When video callbacks are triggered
609
+
610
+ Example:
611
+ ```typescript
612
+ case RETRO_ENVIRONMENT.GET_VARIABLE:
613
+ logger.info(`GET_VARIABLE called with data: ${data}`, 'Environ');
614
+ const result = this.handleGetVariable(data);
615
+ logger.info(`GET_VARIABLE result: ${result}`, 'Environ');
616
+ return result;
617
+ ```
618
+
619
+ **3. Add logging to video callback:**
620
+
621
+ Key file: `src/cores/libretro/callbacks/index.ts`
622
+
623
+ Log framebuffer details when video callback is invoked:
624
+ ```typescript
625
+ logger.info(`Video callback: ${width}x${height}, pitch=${pitch}, format=${pixelFormat}`, 'Video');
626
+ ```
627
+
628
+ **4. Add logging to core options:**
629
+
630
+ Key file: `src/cores/libretro/core-options.ts`
631
+
632
+ Log when default options are applied:
633
+ ```typescript
634
+ logger.info(`Applying default options for ${coreName}: ${JSON.stringify(options)}`, 'CoreOptions');
635
+ ```
636
+
637
+ **5. Rebuild and test:**
638
+
639
+ After adding logging:
640
+ ```bash
641
+ pnpm run build
642
+ killall -9 node
643
+ pnpm run start -- --remove-core mupen64plus_next
644
+ pnpm run start -- --install-core mupen64plus_next
645
+ pnpm run start -- --core mupen64plus_next ~/ROMs/N64/game.z64 --emoji
646
+ ```
647
+
648
+ Then check the logs again for the additional output.
649
+
650
+ ---
651
+
652
+ ## Solved Issue: macOS Library Loading Hang
653
+
654
+ **Status: RESOLVED**
655
+
656
+ On macOS with ARM64 (Apple Silicon), the pre-built mupen64plus_next core (and even custom builds linked against OpenGL) hung indefinitely during the initial library loading phase (`koffi.load()`). This happened before any libretro API functions were called.
657
+
658
+ ### Root Cause
659
+
660
+ The mupen64plus core links against OpenGL.framework even when configured to use the Angrylion software renderer. When the OpenGL framework is loaded on macOS, it performs GPU capability probing during its static initialization. In a terminal environment without a GPU context, this probing appears to block indefinitely.
661
+
662
+ ### Solution: Stub OpenGL Library
663
+
664
+ The solution was to create a stub OpenGL library that provides empty implementations of the OpenGL functions the core links against. Since we use Angrylion (which is CPU-only software rendering), the OpenGL functions are never actually called at runtime.
665
+
666
+ **Implementation:**
667
+
668
+ 1. **Stub Library Creation**: The core-builder creates a stub library (`libGL_stub.dylib`) with no-op implementations of the ~35 OpenGL functions the core references.
669
+
670
+ 2. **Build with Stub**: The core is built linking against the stub library instead of OpenGL.framework:
671
+ ```bash
672
+ make platform=osx HAVE_PARALLEL_RDP=0 GL_LIB="-L/path/to/stub -lGL_stub"
673
+ ```
674
+
675
+ 3. **Library Path Fix**: After building, `install_name_tool` is used to change the stub library reference to an absolute path so it can be found at runtime.
676
+
677
+ **Files involved:**
678
+ - `src/frontend/core-builder.ts` - Creates stub library and builds with it
679
+ - The stub library is installed alongside the core in the cores directory
680
+
681
+ ### Technical Details
682
+
683
+ The stub library provides empty implementations for these OpenGL functions:
684
+ - `glBindTexture`, `glBlendFunc`, `glClear`, `glClearColor`, etc.
685
+ - `glGenTextures` (returns nothing), `glGetError` (returns 0)
686
+ - `glGetString` (returns empty string)
687
+ - ~35 functions total
688
+
689
+ Since Angrylion doesn't use OpenGL for rendering, these functions are never actually called. The stub just satisfies the linker and allows the library to load without pulling in the real OpenGL framework.
690
+
691
+ ### For Users
692
+
693
+ N64 emulation now works out of the box on macOS ARM64:
694
+
695
+ ```bash
696
+ # Install the core (builds automatically with stub library)
697
+ pnpm run start -- --install-core mupen64plus_next
698
+
699
+ # Run an N64 game
700
+ pnpm run start -- ~/ROMs/N64/game.z64
701
+ ```
702
+
703
+ The core uses Angrylion for software rendering, which is pixel-perfect accurate but slower than GPU-based renderers. Performance is acceptable on Apple Silicon Macs.
704
+
705
+ ---
706
+
707
+ ## Solved Issue: Initial Black Screen During Boot
708
+
709
+ **Status: RESOLVED - Normal Behavior**
710
+
711
+ After fixing the library loading hang, the N64 emulator would show "all black" video output for the first few seconds. Investigation revealed this is **normal N64 boot behavior**, not a bug.
712
+
713
+ ### What Happens During N64 Boot
714
+
715
+ 1. **Frames 1-130 (~2 seconds)**: The N64 bootstrap runs. During this time, the RDP (graphics processor) hasn't been initialized yet, so the video callback receives `null` data (frame dupes).
716
+
717
+ 2. **Frames 131-150**: The game starts initializing graphics. First frames are very dark (max pixel value ~1).
718
+
719
+ 3. **Frames 150+**: Full video output with normal brightness (max pixel value 249).
720
+
721
+ This is the same boot sequence that happens on real N64 hardware and in RetroArch with the same core.
722
+
723
+ ### Key Findings
724
+
725
+ - **Video callback IS being called** with correct dimensions (640x240) and pixel format (XRGB8888)
726
+ - **Frame duping** (null data) is used during boot when no RDP output is available
727
+ - **getFramebuffer()** correctly waits for valid frame data before returning non-empty buffers
728
+ - **Pixel format conversion** (XRGB8888 → RGB24) works correctly
729
+
730
+ ### Core Options Confirmed Working
731
+
732
+ The following options are correctly applied:
733
+
734
+ ```ini
735
+ mupen64plus-rdp-plugin = "angrylion" # Software RDP (confirmed)
736
+ mupen64plus-rsp-plugin = "cxd4" # RSP interpreter (required for Angrylion)
737
+ mupen64plus-cpucore = "cached_interpreter" # ARM64 has no dynarec support
738
+ mupen64plus-angrylion-multithread = "all threads"
739
+ mupen64plus-FrameDuping = "False"
740
+ ```
741
+
742
+ Note: `mupen64plus-cpucore = "dynamic_recompiler"` is requested, but ARM64 macOS doesn't have a compatible dynarec, so the core automatically falls back to "Cached Interpreter". This is slower but works correctly.
743
+
744
+ ### For Developers
745
+
746
+ When debugging N64 video issues:
747
+
748
+ 1. **Run at least 200 frames** before concluding video is broken (boot takes ~140 frames)
749
+ 2. **Check the video callback** - if it receives null data, that's expected during boot
750
+ 3. **Check max pixel values** - should increase from 0 to ~249 as the game fades in
751
+ 4. **Frame buffer size changes** - initial size matches SystemInfo, then changes to actual RDP output size