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,457 @@
1
+ import { clamp } from 'remeda';
2
+ import type { RenderMode } from '../../Emulator';
3
+ import type { Config, VideoDriver } from '../../frontend/config';
4
+ import { loadConfig, getPlaylistsDirectory } from '../../frontend/config';
5
+ import { initializeServices } from '../../frontend/serviceProvider';
6
+ import { setNotificationsEnabled } from '../../frontend/notifications';
7
+ import { PNG_COMPRESSION_MIN, PNG_COMPRESSION_MAX } from '../../rendering';
8
+ import { MAX_INPUT_DELAY_FRAMES } from '../../netplay';
9
+ import type { CliOptions } from './types';
10
+ import {
11
+ DEFAULT_SCANLINES,
12
+ DEFAULT_VIGNETTE,
13
+ DEFAULT_BLOOM,
14
+ MIN_FRAME_LIMIT,
15
+ DEFAULT_NTSC,
16
+ DEFAULT_CURVATURE,
17
+ DEFAULT_CHROMATIC_ABERRATION,
18
+ CRT_SCALE,
19
+ CRT_NTSC,
20
+ CRT_SCANLINES,
21
+ CRT_GAMMA,
22
+ CRT_VIGNETTE,
23
+ CRT_CURVATURE,
24
+ CRT_CHROMATIC_ABERRATION,
25
+ } from './consts';
26
+
27
+ export * from './consts';
28
+ export * from './types';
29
+
30
+ // Map config video_driver to RenderMode (null = Auto, use system-specific default)
31
+ const videoDriverToRenderMode = (driver: VideoDriver | null): RenderMode | undefined => {
32
+ switch (driver) {
33
+ case null: return undefined; // Auto (system-specific default)
34
+ case "native": return "native";
35
+ case "kitty": return "kitty";
36
+ case "terminal": return "terminal";
37
+ case "ascii": return "ascii";
38
+ case "emoji": return "emoji";
39
+ default: return "kitty";
40
+ }
41
+ };
42
+
43
+ export const parseArgs = (args: string[]): CliOptions => {
44
+ // First pass: find --config flag to load config file before processing other args
45
+ let configPath: string | undefined;
46
+ for (let i = 0; i < args.length; i++) {
47
+ if (args[i] === "--config" && args[i + 1]) {
48
+ configPath = args[i + 1];
49
+ break;
50
+ }
51
+ }
52
+
53
+ // Load config (will use defaults if no config file exists)
54
+ const { config, loadedFrom: _loadedFrom } = loadConfig(configPath);
55
+
56
+ // Initialize services with config (makes them available globally)
57
+ initializeServices(config);
58
+
59
+ // Initialize notifications setting from config
60
+ setNotificationsEnabled(config.notifications_enable);
61
+
62
+ // Initialize result with values from config (CLI flags will override)
63
+ const result: CliOptions = {
64
+ romPath: undefined,
65
+ width: config.custom_viewport_width ?? undefined,
66
+ height: config.custom_viewport_height ?? undefined,
67
+ colorEnabled: config.video_color_enable,
68
+ renderMode: videoDriverToRenderMode(config.video_driver), // undefined = Auto
69
+ scale: config.video_scale ?? undefined, // null = Auto (system-specific default)
70
+ help: false,
71
+ showVersion: false,
72
+ listGamepads: false,
73
+ listCoresFlag: false,
74
+ installCore: undefined,
75
+ removeCore: undefined,
76
+ core: config.core_default || undefined,
77
+ enableGamepad: config.input_joypad_enable,
78
+ enableAudio: config.audio_enable,
79
+ startMuted: config.audio_mute_enable,
80
+ enableSaveState: config.savestate_auto_load && config.savestate_auto_save,
81
+ enableBatterySave: config.battery_save_enable,
82
+ showStatusBar: config.fps_show_enable,
83
+ enableDiffRendering: config.video_diff_render,
84
+ noRender: false,
85
+ debugGamepad: false,
86
+ fpsLimit: config.fps_limit || undefined,
87
+ loadRetroArch: config.retroarch_cores_enable,
88
+ pngCompressionLevel: config.kitty_png_level,
89
+ // Post-processing effects based on mode: off (no effects), crt (presets), or custom (user values)
90
+ gamma: config.video_postprocessing_mode === 'crt' ? config.crt_gamma :
91
+ config.video_postprocessing_mode === 'custom' ? config.video_gamma : 1.0,
92
+ scanlines: config.video_postprocessing_mode === 'crt' ? config.crt_scanlines :
93
+ config.video_postprocessing_mode === 'custom' ? config.video_scanlines : 0,
94
+ saturation: config.video_postprocessing_mode === 'crt' ? config.crt_saturation :
95
+ config.video_postprocessing_mode === 'custom' ? config.video_saturation : 1.0,
96
+ brightness: config.video_postprocessing_mode === 'custom' ? config.video_brightness : 1.0,
97
+ contrast: config.video_postprocessing_mode === 'custom' ? config.video_contrast : 1.0,
98
+ vignette: config.video_postprocessing_mode === 'crt' ? config.crt_vignette :
99
+ config.video_postprocessing_mode === 'custom' ? config.video_vignette : 0,
100
+ bloom: config.video_postprocessing_mode === 'custom' ? config.video_bloom : 0,
101
+ bloomThreshold: config.video_bloom_threshold,
102
+ ntsc: config.video_postprocessing_mode === 'crt' ? config.crt_ntsc :
103
+ config.video_postprocessing_mode === 'custom' ? config.video_ntsc : 0,
104
+ curvature: config.video_postprocessing_mode === 'crt' ? config.crt_curvature :
105
+ config.video_postprocessing_mode === 'custom' ? config.video_curvature : 0,
106
+ chromaticAberration: config.video_postprocessing_mode === 'crt' ? config.crt_chromatic_aberration :
107
+ config.video_postprocessing_mode === 'custom' ? config.video_chromatic_aberration : 0,
108
+ hasUserEffects: false, // Will be set to true if any effect flag is specified
109
+ configPath,
110
+ config,
111
+ scanDepth: config.browser_scan_depth,
112
+ // Playlist generation options
113
+ generatePlaylist: false,
114
+ playlistOutput: getPlaylistsDirectory(config),
115
+ singlePlaylist: undefined,
116
+ windowsPaths: false,
117
+ // Netplay options
118
+ netplayHost: false,
119
+ netplayConnect: undefined,
120
+ netplayPort: 55435,
121
+ netplayPassword: undefined,
122
+ netplaySpectate: false,
123
+ netplayNickname: 'Player',
124
+ netplayInputDelay: 0,
125
+ clearLogs: false,
126
+ verbose: false,
127
+ frameLimit: config.video_frame_limit,
128
+ };
129
+
130
+ // Track if config had non-default effect values (for hasUserEffects)
131
+ const configHasEffects = config.video_gamma !== 1.0 ||
132
+ config.video_scanlines !== 0 ||
133
+ config.video_saturation !== 1.0 ||
134
+ config.video_brightness !== 1.0 ||
135
+ config.video_contrast !== 1.0 ||
136
+ config.video_vignette !== 0 ||
137
+ config.video_bloom !== 0 ||
138
+ config.video_ntsc !== 0 ||
139
+ config.video_curvature !== 0 ||
140
+ config.video_chromatic_aberration !== 0;
141
+
142
+ // Track which CRT-related flags were explicitly set (for --crt override logic)
143
+ let crtMode = false;
144
+ let scaleExplicit = false;
145
+ let gammaExplicit = false;
146
+ let scanlinesExplicit = false;
147
+ let saturationExplicit = false;
148
+ let brightnessExplicit = false;
149
+ let contrastExplicit = false;
150
+ let ntscExplicit = false;
151
+ let vignetteExplicit = false;
152
+ let bloomExplicit = false;
153
+ let bloomThresholdExplicit = false;
154
+ let curvatureExplicit = false;
155
+ let chromaticAberrationExplicit = false;
156
+
157
+ for (let i = 0; i < args.length; i++) {
158
+ const arg = args[i];
159
+
160
+ if (arg === "--help" || arg === "-h") {
161
+ result.help = true;
162
+ } else if (arg === "--version" || arg === "-V") {
163
+ result.showVersion = true;
164
+ } else if (arg === "--width" && args[i + 1]) {
165
+ result.width = parseInt(args[++i], 10);
166
+ } else if (arg === "--height" && args[i + 1]) {
167
+ result.height = parseInt(args[++i], 10);
168
+ } else if (arg === "--scale" && args[i + 1]) {
169
+ result.scale = parseFloat(args[++i]);
170
+ scaleExplicit = true;
171
+ } else if (arg === "--png-level" && args[i + 1]) {
172
+ const level = parseInt(args[++i], 10);
173
+ result.pngCompressionLevel = clamp(level, { min: PNG_COMPRESSION_MIN, max: PNG_COMPRESSION_MAX });
174
+ } else if (arg === "--ascii") {
175
+ result.renderMode = "ascii";
176
+ result.config.video_driver = "ascii";
177
+ } else if (arg === "--emoji") {
178
+ result.renderMode = "emoji";
179
+ result.config.video_driver = "emoji";
180
+ } else if (arg === "--no-color") {
181
+ result.colorEnabled = false;
182
+ } else if (arg === "--native") {
183
+ result.renderMode = "native";
184
+ result.config.video_driver = "native";
185
+ } else if (arg === "--kitty") {
186
+ result.renderMode = "kitty";
187
+ result.config.video_driver = "kitty";
188
+ } else if (arg === "--terminal") {
189
+ result.renderMode = "terminal";
190
+ result.config.video_driver = "terminal";
191
+ } else if (arg === "--list-gamepads") {
192
+ result.listGamepads = true;
193
+ } else if (arg === "--list-cores") {
194
+ result.listCoresFlag = true;
195
+ } else if (arg === "--install-core" && args[i + 1]) {
196
+ result.installCore = args[++i];
197
+ } else if (arg === "--remove-core" && args[i + 1]) {
198
+ result.removeCore = args[++i];
199
+ } else if (arg === "--core" && args[i + 1]) {
200
+ result.core = args[++i];
201
+ } else if (arg === "--no-gamepad") {
202
+ result.enableGamepad = false;
203
+ } else if (arg === "--no-audio") {
204
+ result.enableAudio = false;
205
+ } else if (arg === "--no-save-state") {
206
+ result.enableSaveState = false;
207
+ } else if (arg === "--no-battery-save") {
208
+ result.enableBatterySave = false;
209
+ } else if (arg === "--status") {
210
+ result.showStatusBar = true;
211
+ } else if (arg === "--no-diff-render") {
212
+ result.enableDiffRendering = false;
213
+ } else if (arg === "--no-render") {
214
+ result.noRender = true;
215
+ } else if (arg === "--debug-gamepad") {
216
+ result.debugGamepad = true;
217
+ } else if (arg === "--clear-logs") {
218
+ result.clearLogs = true;
219
+ } else if (arg === "--verbose" || arg === "-v") {
220
+ result.verbose = true;
221
+ } else if (arg === "--fps-limit" && args[i + 1]) {
222
+ result.fpsLimit = parseInt(args[++i], 10);
223
+ } else if (arg === "--frame-limit" && args[i + 1]) {
224
+ const limit = parseInt(args[++i], 10);
225
+ result.frameLimit = limit <= 0 ? 0 : Math.max(MIN_FRAME_LIMIT, limit); // 0 = off, otherwise min 1
226
+ } else if (arg === "--retroarch") {
227
+ result.loadRetroArch = true;
228
+ } else if (arg === "--crt") {
229
+ crtMode = true;
230
+ } else if (arg === "--gamma" && args[i + 1]) {
231
+ result.gamma = parseFloat(args[++i]);
232
+ gammaExplicit = true;
233
+ } else if (arg === "--scanlines") {
234
+ // Check if next arg is a number (not another flag)
235
+ const nextArg = args[i + 1];
236
+ if (nextArg && !nextArg.startsWith("-") && !isNaN(parseFloat(nextArg))) {
237
+ result.scanlines = parseFloat(args[++i]);
238
+ } else {
239
+ result.scanlines = DEFAULT_SCANLINES;
240
+ }
241
+ scanlinesExplicit = true;
242
+ } else if (arg === "--saturation" && args[i + 1]) {
243
+ result.saturation = parseFloat(args[++i]);
244
+ saturationExplicit = true;
245
+ } else if (arg === "--brightness" && args[i + 1]) {
246
+ result.brightness = parseFloat(args[++i]);
247
+ brightnessExplicit = true;
248
+ } else if (arg === "--contrast" && args[i + 1]) {
249
+ result.contrast = parseFloat(args[++i]);
250
+ contrastExplicit = true;
251
+ } else if (arg === "--vignette") {
252
+ // Check if next arg is a number (not another flag)
253
+ const nextArg = args[i + 1];
254
+ if (nextArg && !nextArg.startsWith("-") && !isNaN(parseFloat(nextArg))) {
255
+ result.vignette = parseFloat(args[++i]);
256
+ } else {
257
+ result.vignette = DEFAULT_VIGNETTE;
258
+ }
259
+ vignetteExplicit = true;
260
+ } else if (arg === "--bloom") {
261
+ // Check if next arg is a number (not another flag)
262
+ const nextArg = args[i + 1];
263
+ if (nextArg && !nextArg.startsWith("-") && !isNaN(parseFloat(nextArg))) {
264
+ result.bloom = parseFloat(args[++i]);
265
+ } else {
266
+ result.bloom = DEFAULT_BLOOM;
267
+ }
268
+ bloomExplicit = true;
269
+ } else if (arg === "--bloom-threshold" && args[i + 1]) {
270
+ result.bloomThreshold = parseFloat(args[++i]);
271
+ bloomThresholdExplicit = true;
272
+ } else if (arg === "--ntsc") {
273
+ // Check if next arg is a number (not another flag)
274
+ const nextArg = args[i + 1];
275
+ if (nextArg && !nextArg.startsWith("-") && !isNaN(parseFloat(nextArg))) {
276
+ result.ntsc = parseFloat(args[++i]);
277
+ } else {
278
+ result.ntsc = DEFAULT_NTSC;
279
+ }
280
+ ntscExplicit = true;
281
+ } else if (arg === "--curvature") {
282
+ // Check if next arg is a number (not another flag)
283
+ const nextArg = args[i + 1];
284
+ if (nextArg && !nextArg.startsWith("-") && !isNaN(parseFloat(nextArg))) {
285
+ result.curvature = parseFloat(args[++i]);
286
+ } else {
287
+ result.curvature = DEFAULT_CURVATURE;
288
+ }
289
+ curvatureExplicit = true;
290
+ } else if (arg === "--chromatic-aberration") {
291
+ // Check if next arg is a number (not another flag)
292
+ const nextArg = args[i + 1];
293
+ if (nextArg && !nextArg.startsWith("-") && !isNaN(parseFloat(nextArg))) {
294
+ result.chromaticAberration = parseFloat(args[++i]);
295
+ } else {
296
+ result.chromaticAberration = DEFAULT_CHROMATIC_ABERRATION;
297
+ }
298
+ chromaticAberrationExplicit = true;
299
+ } else if (arg === "--scan-depth" && args[i + 1]) {
300
+ result.scanDepth = parseInt(args[++i], 10);
301
+ } else if (arg === "--generate-playlist") {
302
+ // Check if next arg is a path (not another flag)
303
+ const nextArg = args[i + 1];
304
+ if (nextArg && !nextArg.startsWith("-")) {
305
+ result.generatePlaylist = args[++i];
306
+ } else {
307
+ result.generatePlaylist = true; // Use cwd
308
+ }
309
+ } else if (arg === "--playlist-output" && args[i + 1]) {
310
+ result.playlistOutput = args[++i];
311
+ } else if (arg === "--single-playlist" && args[i + 1]) {
312
+ result.singlePlaylist = args[++i];
313
+ } else if (arg === "--windows-paths") {
314
+ result.windowsPaths = true;
315
+ } else if (arg === "--config" && args[i + 1]) {
316
+ // Already handled in first pass, just skip the argument
317
+ i++;
318
+ } else if (arg === "--netplay-host") {
319
+ result.netplayHost = true;
320
+ } else if (arg === "--netplay-connect") {
321
+ // Check if next arg is a host (not another flag)
322
+ const nextArg = args[i + 1];
323
+ if (nextArg && !nextArg.startsWith("-")) {
324
+ result.netplayConnect = args[++i];
325
+ } else {
326
+ // No host specified - use LAN discovery
327
+ result.netplayConnect = '';
328
+ }
329
+ } else if (arg === "--netplay-port" && args[i + 1]) {
330
+ result.netplayPort = parseInt(args[++i], 10);
331
+ } else if (arg === "--netplay-password" && args[i + 1]) {
332
+ result.netplayPassword = args[++i];
333
+ } else if (arg === "--netplay-spectate") {
334
+ result.netplaySpectate = true;
335
+ } else if (arg === "--netplay-nick" && args[i + 1]) {
336
+ result.netplayNickname = args[++i];
337
+ } else if (arg === "--netplay-frames" && args[i + 1]) {
338
+ const frames = parseInt(args[++i], 10);
339
+ result.netplayInputDelay = clamp(frames, { min: 0, max: MAX_INPUT_DELAY_FRAMES });
340
+ } else if (!arg.startsWith("-")) {
341
+ result.romPath = arg;
342
+ }
343
+ }
344
+
345
+ // Apply --crt preset defaults for flags not explicitly set
346
+ if (crtMode) {
347
+ if (!scaleExplicit) {
348
+ result.scale = CRT_SCALE;
349
+ }
350
+ if (!ntscExplicit) {
351
+ result.ntsc = CRT_NTSC;
352
+ }
353
+ if (!scanlinesExplicit) {
354
+ result.scanlines = CRT_SCANLINES;
355
+ }
356
+ if (!gammaExplicit) {
357
+ result.gamma = CRT_GAMMA;
358
+ }
359
+ if (!vignetteExplicit) {
360
+ result.vignette = CRT_VIGNETTE;
361
+ }
362
+ if (!curvatureExplicit) {
363
+ result.curvature = CRT_CURVATURE;
364
+ }
365
+ if (!chromaticAberrationExplicit) {
366
+ result.chromaticAberration = CRT_CHROMATIC_ABERRATION;
367
+ }
368
+ }
369
+
370
+ // Determine if user explicitly specified any post-processing effects
371
+ // --crt counts as having user effects since it's an explicit choice
372
+ // video_postprocessing_mode !== 'off' means effects are enabled in config
373
+ // configHasEffects means the config file had non-default custom effect values
374
+ result.hasUserEffects = crtMode ||
375
+ config.video_postprocessing_mode !== 'off' ||
376
+ configHasEffects ||
377
+ gammaExplicit ||
378
+ scanlinesExplicit ||
379
+ saturationExplicit ||
380
+ brightnessExplicit ||
381
+ contrastExplicit ||
382
+ vignetteExplicit ||
383
+ bloomExplicit ||
384
+ bloomThresholdExplicit ||
385
+ ntscExplicit ||
386
+ curvatureExplicit ||
387
+ chromaticAberrationExplicit;
388
+
389
+ return result;
390
+ };
391
+
392
+ /**
393
+ * Update runtime options from a fresh config (for settings changed in browser)
394
+ * Only updates settings that can be changed at runtime, preserving CLI overrides
395
+ */
396
+ export const updateOptionsFromConfig = (options: CliOptions, config: Config): void => {
397
+ // Update video settings
398
+ options.renderMode = videoDriverToRenderMode(config.video_driver); // undefined = Auto
399
+ options.scale = config.video_scale ?? undefined; // null = Auto (system-specific default)
400
+
401
+ // Update audio/input settings
402
+ options.enableAudio = config.audio_enable;
403
+ options.startMuted = config.audio_mute_enable;
404
+ options.enableGamepad = config.input_joypad_enable;
405
+
406
+ // Update save state settings
407
+ options.enableSaveState = config.savestate_auto_load && config.savestate_auto_save;
408
+
409
+ // Update status bar setting
410
+ options.showStatusBar = config.fps_show_enable;
411
+
412
+ // Update frame limit setting
413
+ options.frameLimit = config.video_frame_limit;
414
+
415
+ // Update notifications
416
+ setNotificationsEnabled(config.notifications_enable);
417
+
418
+ // Update post-processing effects based on mode
419
+ if (config.video_postprocessing_mode === 'crt') {
420
+ options.gamma = config.crt_gamma;
421
+ options.scanlines = config.crt_scanlines;
422
+ options.saturation = config.crt_saturation;
423
+ options.vignette = config.crt_vignette;
424
+ options.ntsc = config.crt_ntsc;
425
+ options.curvature = config.crt_curvature;
426
+ options.chromaticAberration = config.crt_chromatic_aberration;
427
+ options.hasUserEffects = true;
428
+ } else if (config.video_postprocessing_mode === 'custom') {
429
+ options.gamma = config.video_gamma;
430
+ options.scanlines = config.video_scanlines;
431
+ options.saturation = config.video_saturation;
432
+ options.brightness = config.video_brightness;
433
+ options.contrast = config.video_contrast;
434
+ options.vignette = config.video_vignette;
435
+ options.bloom = config.video_bloom;
436
+ options.ntsc = config.video_ntsc;
437
+ options.curvature = config.video_curvature;
438
+ options.chromaticAberration = config.video_chromatic_aberration;
439
+ options.hasUserEffects = true;
440
+ } else {
441
+ // 'off' mode - reset to defaults
442
+ options.gamma = 1.0;
443
+ options.scanlines = 0;
444
+ options.saturation = 1.0;
445
+ options.brightness = 1.0;
446
+ options.contrast = 1.0;
447
+ options.vignette = 0;
448
+ options.bloom = 0;
449
+ options.ntsc = 0;
450
+ options.curvature = 0;
451
+ options.chromaticAberration = 0;
452
+ options.hasUserEffects = false;
453
+ }
454
+
455
+ // Update browser scan depth
456
+ options.scanDepth = config.browser_scan_depth;
457
+ };
@@ -0,0 +1,61 @@
1
+ import type { RenderMode } from '../../Emulator';
2
+ import type { Config } from '../../frontend/config';
3
+
4
+ export interface CliOptions {
5
+ romPath?: string;
6
+ width: number | undefined;
7
+ height: number | undefined;
8
+ colorEnabled: boolean;
9
+ renderMode: RenderMode | undefined; // undefined = Auto (system-specific default)
10
+ scale: number | undefined;
11
+ help: boolean;
12
+ showVersion: boolean;
13
+ listGamepads: boolean;
14
+ listCoresFlag: boolean;
15
+ installCore: string | undefined;
16
+ removeCore: string | undefined;
17
+ core: string | undefined;
18
+ enableGamepad: boolean;
19
+ enableAudio: boolean;
20
+ startMuted: boolean;
21
+ enableSaveState: boolean;
22
+ enableBatterySave: boolean;
23
+ showStatusBar: boolean;
24
+ enableDiffRendering: boolean;
25
+ noRender: boolean;
26
+ debugGamepad: boolean;
27
+ fpsLimit: number | undefined;
28
+ loadRetroArch: boolean;
29
+ pngCompressionLevel: number;
30
+ gamma: number;
31
+ scanlines: number;
32
+ saturation: number;
33
+ brightness: number;
34
+ contrast: number;
35
+ vignette: number;
36
+ bloom: number;
37
+ bloomThreshold: number;
38
+ ntsc: number;
39
+ curvature: number;
40
+ chromaticAberration: number;
41
+ hasUserEffects: boolean; // Whether user explicitly specified any post-processing effects
42
+ configPath: string | undefined;
43
+ config: Config;
44
+ scanDepth: number; // Max depth for ROM scanning
45
+ // Playlist generation options
46
+ generatePlaylist: string | boolean; // Path to scan, or true for cwd, or false for disabled
47
+ playlistOutput: string;
48
+ singlePlaylist: string | undefined; // Single playlist name, or undefined for per-system
49
+ windowsPaths: boolean;
50
+ // Netplay options
51
+ netplayHost: boolean; // Host a netplay session
52
+ netplayConnect: string | undefined; // Connect to server (hostname or hostname:port)
53
+ netplayPort: number; // Netplay port (default: 55435)
54
+ netplayPassword: string | undefined; // Netplay password
55
+ netplaySpectate: boolean; // Join as spectator
56
+ netplayNickname: string; // Player nickname
57
+ netplayInputDelay: number; // Input delay frames (0-16)
58
+ clearLogs: boolean; // Delete all log files and exit
59
+ verbose: boolean; // Enable verbose logging to stderr
60
+ frameLimit: number; // Limit rendering to N fps (0=off)
61
+ }