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.
- package/.claude/settings.local.json +77 -0
- package/.node-version +1 -0
- package/CLAUDE.md +435 -0
- package/README.md +404 -0
- package/TODO.md +655 -0
- package/dist/index.cjs +25108 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +25085 -0
- package/docs/building-libretro-cores-arm-mac.md +237 -0
- package/docs/config-file-format.md +488 -0
- package/docs/cores-trd.md +425 -0
- package/docs/headless-hardware-rendering-trd.md +676 -0
- package/docs/libretro-cores-trd.md +997 -0
- package/docs/mupen64-software-rendering-trd.md +751 -0
- package/docs/n64-support-trd.md +306 -0
- package/docs/native-rendering-trd.md +540 -0
- package/docs/native-ui-rendering-trd.md +1195 -0
- package/docs/netplay-trd.md +665 -0
- package/docs/retroarch-netplay-docs.md +277 -0
- package/docs/save-state-format.md +666 -0
- package/eslint.config.js +111 -0
- package/icon/icon.png +0 -0
- package/icon/icon.pxd +0 -0
- package/package.json +63 -0
- package/pnpm-workspace.yaml +10 -0
- package/src/Emulator/consts.ts +14 -0
- package/src/Emulator/index.ts +2496 -0
- package/src/Emulator/saveState/index.ts +155 -0
- package/src/Emulator/screenshot/index.ts +160 -0
- package/src/Emulator/terminalDimensions/index.ts +79 -0
- package/src/Emulator/types.ts +83 -0
- package/src/cli/commands/consts.ts +10 -0
- package/src/cli/commands/index.ts +462 -0
- package/src/cli/parseArgs/consts.ts +17 -0
- package/src/cli/parseArgs/index.ts +457 -0
- package/src/cli/parseArgs/types.ts +61 -0
- package/src/cli/runEmulator/index.ts +406 -0
- package/src/cli/runEmulator/types.ts +7 -0
- package/src/consts.ts +19 -0
- package/src/core/button/consts.ts +35 -0
- package/src/core/button/index.ts +123 -0
- package/src/core/core.ts +300 -0
- package/src/core/index.ts +19 -0
- package/src/cores/libretro/api/index.ts +334 -0
- package/src/cores/libretro/api/types.ts +148 -0
- package/src/cores/libretro/callbacks/consts.ts +41 -0
- package/src/cores/libretro/callbacks/index.ts +456 -0
- package/src/cores/libretro/consts.ts +45 -0
- package/src/cores/libretro/coreOptions/consts.ts +36 -0
- package/src/cores/libretro/coreOptions/index.ts +222 -0
- package/src/cores/libretro/environment/consts.ts +118 -0
- package/src/cores/libretro/environment/index.ts +1095 -0
- package/src/cores/libretro/index.ts +937 -0
- package/src/cores/libretro/loader/index.ts +496 -0
- package/src/cores/libretro/pixelFormat/consts.ts +43 -0
- package/src/cores/libretro/pixelFormat/index.ts +397 -0
- package/src/cores/libretro/types.ts +339 -0
- package/src/frontend/AudioManager/index.ts +420 -0
- package/src/frontend/SettingsManager/index.ts +250 -0
- package/src/frontend/config/index.ts +608 -0
- package/src/frontend/config/tests.ts +354 -0
- package/src/frontend/config/types.ts +36 -0
- package/src/frontend/consts.ts +114 -0
- package/src/frontend/coreBuilder/index.ts +644 -0
- package/src/frontend/coreBuilder/types.ts +15 -0
- package/src/frontend/coreDownloader/index.ts +620 -0
- package/src/frontend/coreDownloader/types.ts +17 -0
- package/src/frontend/corePreferences/index.ts +69 -0
- package/src/frontend/coreRegistry/index.ts +276 -0
- package/src/frontend/directoryCache/index.ts +75 -0
- package/src/frontend/index.ts +79 -0
- package/src/frontend/notifications/consts.ts +14 -0
- package/src/frontend/notifications/index.ts +250 -0
- package/src/frontend/playlist/consts.ts +168 -0
- package/src/frontend/playlist/index.ts +899 -0
- package/src/frontend/playlist/labelFormatter/consts.ts +15 -0
- package/src/frontend/playlist/labelFormatter/index.ts +155 -0
- package/src/frontend/playlist/labelFormatter/tests.ts +153 -0
- package/src/frontend/playlist/reader/index.ts +559 -0
- package/src/frontend/playlist/sync/index.ts +511 -0
- package/src/frontend/playlist/systemLookup/index.ts +233 -0
- package/src/frontend/playlist/utils/index.ts +50 -0
- package/src/frontend/romScanner/consts.ts +348 -0
- package/src/frontend/romScanner/index.ts +1957 -0
- package/src/frontend/saveServices/consts.ts +2 -0
- package/src/frontend/saveServices/index.ts +313 -0
- package/src/frontend/serviceProvider/index.ts +108 -0
- package/src/frontend/serviceProvider/types.ts +13 -0
- package/src/index.ts +428 -0
- package/src/input/Controller/consts.ts +50 -0
- package/src/input/Controller/index.ts +81 -0
- package/src/input/GamepadManager/consts.ts +22 -0
- package/src/input/GamepadManager/index.ts +418 -0
- package/src/input/InputManager/consts.ts +86 -0
- package/src/input/InputManager/index.ts +593 -0
- package/src/input/InputMapper/consts.ts +33 -0
- package/src/input/InputMapper/index.ts +436 -0
- package/src/input/consts.ts +410 -0
- package/src/input/gamepadProfiles/index.ts +1100 -0
- package/src/input/index.ts +38 -0
- package/src/input/inputUtils/index.ts +31 -0
- package/src/netplay/FrameBuffer/consts.ts +2 -0
- package/src/netplay/FrameBuffer/index.ts +364 -0
- package/src/netplay/FrameBuffer/tests.ts +286 -0
- package/src/netplay/InputBuffer/consts.ts +7 -0
- package/src/netplay/InputBuffer/index.ts +347 -0
- package/src/netplay/InputBuffer/tests.ts +166 -0
- package/src/netplay/NetplayClient/index.ts +976 -0
- package/src/netplay/NetplayConnection/index.ts +536 -0
- package/src/netplay/NetplayDiscovery/consts.ts +41 -0
- package/src/netplay/NetplayDiscovery/index.ts +525 -0
- package/src/netplay/NetplayServer/index.ts +1407 -0
- package/src/netplay/SyncManager/index.ts +984 -0
- package/src/netplay/SyncManager/tests.ts +419 -0
- package/src/netplay/consts.ts +371 -0
- package/src/netplay/crc32/consts.ts +14 -0
- package/src/netplay/crc32/index.ts +97 -0
- package/src/netplay/crc32/tests.ts +40 -0
- package/src/netplay/index.ts +41 -0
- package/src/netplay/netplayLogger/consts.ts +30 -0
- package/src/netplay/netplayLogger/index.ts +345 -0
- package/src/netplay/protocol/consts.ts +86 -0
- package/src/netplay/protocol/index.ts +1280 -0
- package/src/netplay/protocol/tests.ts +606 -0
- package/src/netplay/protocol/types.ts +20 -0
- package/src/netplay/types.ts +395 -0
- package/src/rendering/KittyRenderer/index.ts +616 -0
- package/src/rendering/NativeRenderer/index.ts +279 -0
- package/src/rendering/NativeRenderer/tests.ts +133 -0
- package/src/rendering/TerminalRenderer/index.ts +770 -0
- package/src/rendering/consts.ts +401 -0
- package/src/rendering/fonts/CozetteVector.ttf +0 -0
- package/src/rendering/index.ts +26 -0
- package/src/rendering/nativeUi/NativeWindowManager/index.ts +158 -0
- package/src/rendering/nativeUi/NativeWindowManager/tests.ts +81 -0
- package/src/rendering/nativeUi/consts.ts +6 -0
- package/src/rendering/nativeUi/index.ts +20 -0
- package/src/rendering/postProcessing/consts.ts +38 -0
- package/src/rendering/postProcessing/index.ts +923 -0
- package/src/rendering/shared/ansi/consts.ts +12 -0
- package/src/rendering/shared/ansi/index.ts +104 -0
- package/src/rendering/shared/consts.ts +2 -0
- package/src/rendering/shared/fitToTerminal/index.ts +67 -0
- package/src/ui/AddRomsPrompt/consts.ts +13 -0
- package/src/ui/AddRomsPrompt/index.tsx +781 -0
- package/src/ui/App/consts.ts +2 -0
- package/src/ui/App/index.tsx +456 -0
- package/src/ui/AppCapabilities/index.tsx +67 -0
- package/src/ui/ConfigContext/index.tsx +56 -0
- package/src/ui/CoreManager/consts.ts +11 -0
- package/src/ui/CoreManager/index.tsx +779 -0
- package/src/ui/CoreSelector/consts.ts +2 -0
- package/src/ui/CoreSelector/index.tsx +251 -0
- package/src/ui/DialogContainer/index.tsx +42 -0
- package/src/ui/DialogOptionsList/index.tsx +61 -0
- package/src/ui/DuplicateCrcPrompt/consts.ts +5 -0
- package/src/ui/DuplicateCrcPrompt/index.tsx +146 -0
- package/src/ui/GamepadContext/consts.ts +15 -0
- package/src/ui/GamepadContext/index.tsx +295 -0
- package/src/ui/NativeDialog/index.tsx +120 -0
- package/src/ui/NetplayDisconnectedDialog/index.tsx +93 -0
- package/src/ui/NetplayPauseMenu/consts.ts +2 -0
- package/src/ui/NetplayPauseMenu/index.tsx +97 -0
- package/src/ui/RomBrowser/NetplayPanel/consts.ts +24 -0
- package/src/ui/RomBrowser/NetplayPanel/index.tsx +520 -0
- package/src/ui/RomBrowser/SettingsPanel/index.tsx +478 -0
- package/src/ui/RomBrowser/consts.ts +61 -0
- package/src/ui/RomBrowser/index.tsx +1164 -0
- package/src/ui/RomBrowser/settingsConfig/index.ts +320 -0
- package/src/ui/RomBrowser/types.ts +67 -0
- package/src/ui/SaveStateDialog/consts.ts +2 -0
- package/src/ui/SaveStateDialog/index.tsx +225 -0
- package/src/ui/WarningDialog/index.tsx +113 -0
- package/src/ui/consts.ts +27 -0
- package/src/ui/hooks/useClearTerminal/consts.ts +2 -0
- package/src/ui/hooks/useClearTerminal/index.ts +37 -0
- package/src/ui/hooks/useDialogNavigation/index.ts +99 -0
- package/src/ui/hooks/useGamepad/consts.ts +21 -0
- package/src/ui/hooks/useGamepad/index.ts +194 -0
- package/src/ui/index.ts +27 -0
- package/src/utils/buffer/consts.ts +17 -0
- package/src/utils/buffer/index.ts +129 -0
- package/src/utils/color/consts.ts +58 -0
- package/src/utils/color/index.ts +183 -0
- package/src/utils/compression/consts.ts +50 -0
- package/src/utils/compression/index.ts +101 -0
- package/src/utils/consts.ts +2 -0
- package/src/utils/crc32/consts.ts +22 -0
- package/src/utils/crc32/index.ts +83 -0
- package/src/utils/ensureDirectory/index.ts +10 -0
- package/src/utils/format/consts.ts +8 -0
- package/src/utils/format/index.ts +53 -0
- package/src/utils/getErrorMessage/index.ts +10 -0
- package/src/utils/index.ts +113 -0
- package/src/utils/ini/index.ts +200 -0
- package/src/utils/kitty/consts.ts +13 -0
- package/src/utils/kitty/index.ts +181 -0
- package/src/utils/logger/consts.ts +35 -0
- package/src/utils/logger/index.ts +217 -0
- package/src/utils/paths/consts.ts +18 -0
- package/src/utils/paths/index.ts +151 -0
- package/src/utils/png/consts.ts +34 -0
- package/src/utils/png/index.ts +131 -0
- package/src/utils/readJsonFile/index.ts +16 -0
- package/src/utils/rotateLogFile/index.ts +44 -0
- package/src/utils/safeClose/index.ts +10 -0
- package/src/utils/terminal/consts.ts +8 -0
- package/src/utils/terminal/index.ts +102 -0
- package/src/utils/thumbnailRenderer/consts.ts +2 -0
- package/src/utils/thumbnailRenderer/index.ts +147 -0
- package/src/utils/typedError/index.ts +26 -0
- package/tsconfig.json +31 -0
- package/vitest.config.ts +13 -0
|
@@ -0,0 +1,462 @@
|
|
|
1
|
+
import { GamepadManager } from '../../input/GamepadManager';
|
|
2
|
+
import HID from 'node-hid';
|
|
3
|
+
import { existsSync, readdirSync, unlinkSync } from 'fs';
|
|
4
|
+
import { getDefaultLogsDirectory } from '../../utils/paths';
|
|
5
|
+
import { logger } from '../../utils/logger';
|
|
6
|
+
import { getErrorMessage } from '../../utils/getErrorMessage';
|
|
7
|
+
import { join } from 'path';
|
|
8
|
+
import {
|
|
9
|
+
listCores,
|
|
10
|
+
getSupportedExtensions,
|
|
11
|
+
unregisterCore,
|
|
12
|
+
} from '../../frontend/coreRegistry';
|
|
13
|
+
import { HEX_RADIX, BYTE_DECIMAL_PAD_WIDTH } from '../../frontend';
|
|
14
|
+
import { scanDirectory } from '../../frontend/romScanner';
|
|
15
|
+
import {
|
|
16
|
+
generatePlaylistsBySystem,
|
|
17
|
+
generateConsolidatedPlaylist,
|
|
18
|
+
} from '../../frontend/playlist';
|
|
19
|
+
import type { PlaylistOptions } from '../../frontend/playlist';
|
|
20
|
+
import { downloadCore } from '../../frontend/coreDownloader';
|
|
21
|
+
import { registerLibretroCore, unloadLibretroCore } from '../../cores/libretro/loader';
|
|
22
|
+
import {
|
|
23
|
+
LINE_CLEAR_WIDTH,
|
|
24
|
+
PERCENT_MULTIPLIER,
|
|
25
|
+
BYTES_PER_KB,
|
|
26
|
+
PERCENT_SUFFIX_WIDTH,
|
|
27
|
+
ELLIPSIS_LENGTH,
|
|
28
|
+
} from './consts';
|
|
29
|
+
|
|
30
|
+
export * from './consts';
|
|
31
|
+
|
|
32
|
+
export const printUsage = (): void => {
|
|
33
|
+
// Get supported extensions from core registry
|
|
34
|
+
const extensions = getSupportedExtensions().join(", ");
|
|
35
|
+
|
|
36
|
+
console.log(`
|
|
37
|
+
emoemu - Terminal Retro Emulator
|
|
38
|
+
|
|
39
|
+
Usage: emoemu <rom> [options]
|
|
40
|
+
|
|
41
|
+
Options:
|
|
42
|
+
--config <path> Use a custom config file path
|
|
43
|
+
--core <id> Use a specific emulator core (see --list-cores)
|
|
44
|
+
--list-cores List available emulator cores and exit
|
|
45
|
+
--install-core <name> Download/build and install a libretro core by name
|
|
46
|
+
(e.g., --install-core mupen64plus_next)
|
|
47
|
+
--remove-core <id> Remove an installed libretro core (use same ID as --core)
|
|
48
|
+
--retroarch Also load libretro cores from RetroArch installation paths
|
|
49
|
+
--native Use native window rendering (best performance)
|
|
50
|
+
--kitty Use Kitty graphics protocol (default, best quality)
|
|
51
|
+
--terminal Use terminal character rendering (Unicode half-blocks)
|
|
52
|
+
--ascii Use colored ASCII character rendering
|
|
53
|
+
--emoji Use emoji character rendering
|
|
54
|
+
--no-color Disable colors (use with --ascii or --terminal)
|
|
55
|
+
--scale <n> Internal render scale for Kitty mode (default: 2)
|
|
56
|
+
--png-level <n> PNG compression level 1-9 for Kitty mode (default: 4)
|
|
57
|
+
Higher = smaller output, reduces terminal I/O bottleneck.
|
|
58
|
+
--crt CRT effect preset: --scale 1 --ntsc 1.0 --scanlines 0.1 --gamma 1.3 --vignette 0.5
|
|
59
|
+
--curvature 0.1 --chromatic-aberration 0.3
|
|
60
|
+
Individual flags can override these defaults
|
|
61
|
+
--gamma <n> Gamma correction (default: 1.0)
|
|
62
|
+
Values > 1.0 darken midtones (CRT-like), try 1.1-1.4
|
|
63
|
+
--scanlines [n] Scanline intensity (default: 0.3 if enabled)
|
|
64
|
+
Values 0.0-1.0, try 0.2-0.4 for subtle CRT effect
|
|
65
|
+
--saturation <n> Color saturation (default: 1.0)
|
|
66
|
+
Values > 1.0 boost colors (CRT-like), try 1.1-1.3
|
|
67
|
+
--brightness <n> Brightness multiplier (default: 1.0)
|
|
68
|
+
Values > 1.0 brighten, < 1.0 darken
|
|
69
|
+
--contrast <n> Contrast adjustment (default: 1.0)
|
|
70
|
+
Values > 1.0 increase contrast, < 1.0 decrease
|
|
71
|
+
--vignette [n] Vignette edge darkening (default: 1.0 if enabled)
|
|
72
|
+
Values 0.3-0.5 for subtle CRT-like edge darkening
|
|
73
|
+
--bloom [n] Phosphor bloom/glow for Kitty mode only (default: 0.5 if enabled)
|
|
74
|
+
Values 0.3-0.6 for subtle CRT phosphor glow
|
|
75
|
+
--bloom-threshold <n> Brightness threshold for bloom (default: 0.6)
|
|
76
|
+
Pixels brighter than this emit glow (range 0-1)
|
|
77
|
+
--ntsc [n] NTSC color artifacts for Kitty mode only (default: 1.0 if enabled)
|
|
78
|
+
Simulates horizontal color bleeding from composite video
|
|
79
|
+
--curvature [n] CRT screen curvature for Kitty mode only (default: 0.1 if enabled)
|
|
80
|
+
Barrel distortion to simulate curved CRT glass, try 0.1-0.3
|
|
81
|
+
--chromatic-aberration [n] RGB color fringing for Kitty mode only (default: 0.5 if enabled)
|
|
82
|
+
Simulates CRT electron beam convergence errors, try 0.3-1.0
|
|
83
|
+
--width <n> Set display width in characters (terminal/ascii mode)
|
|
84
|
+
--height <n> Set display height in characters (terminal/ascii mode)
|
|
85
|
+
--list-gamepads List detected gamepad/controller devices and exit
|
|
86
|
+
--no-gamepad Disable gamepad support
|
|
87
|
+
--no-audio Disable audio output
|
|
88
|
+
--no-save-state Disable save state loading and saving
|
|
89
|
+
--no-battery-save Disable battery save (.srm) loading and saving
|
|
90
|
+
--status Show the status bar (disabled by default)
|
|
91
|
+
--no-diff-render Disable diff-based rendering optimization
|
|
92
|
+
--no-render Disable video rendering (for debugging, audio/emulation still run)
|
|
93
|
+
--fps-limit <n> Override FPS limit (0 = uncapped, default: core native)
|
|
94
|
+
--frame-limit <n> Limit rendering to N fps (0 = off, default: 0)
|
|
95
|
+
Common values: 30, 60. Reduces terminal I/O while
|
|
96
|
+
emulation runs at full speed. Useful over SSH.
|
|
97
|
+
--debug-gamepad Show raw gamepad HID data (for debugging)
|
|
98
|
+
--clear-logs Delete all emoemu log files
|
|
99
|
+
--verbose Enable verbose logging to stderr (RetroArch-style)
|
|
100
|
+
--help Show this help message
|
|
101
|
+
|
|
102
|
+
Browser:
|
|
103
|
+
--scan-depth <n> Max depth to scan for ROMs (default: 1)
|
|
104
|
+
0 = only specified directory
|
|
105
|
+
1 = directory + immediate subdirectories
|
|
106
|
+
-1 = unlimited (scan all subdirectories)
|
|
107
|
+
|
|
108
|
+
Playlist Generation (RetroArch-compatible .lpl files):
|
|
109
|
+
--generate-playlist [path] Scan directory for ROMs and generate a playlist
|
|
110
|
+
If path is omitted, uses current directory
|
|
111
|
+
Creates per-system playlists (e.g., "Nintendo - NES.lpl")
|
|
112
|
+
--playlist-output <dir> Output directory for playlists (default: ./playlists)
|
|
113
|
+
--single-playlist <name> Generate one consolidated playlist instead of per-system
|
|
114
|
+
Specify the playlist name (without .lpl extension)
|
|
115
|
+
--windows-paths Use Windows-style backslash separators in paths
|
|
116
|
+
|
|
117
|
+
Netplay (RetroArch-compatible multiplayer):
|
|
118
|
+
--netplay-host Host a netplay session (server mode)
|
|
119
|
+
--netplay-connect [host] Connect to a netplay server (host or host:port)
|
|
120
|
+
If host is omitted, auto-discovers LAN hosts
|
|
121
|
+
--netplay-port <n> Port for netplay (default: 55435)
|
|
122
|
+
--netplay-password <pw> Password for netplay session
|
|
123
|
+
--netplay-spectate Join as spectator (view only)
|
|
124
|
+
--netplay-nick <name> Set your nickname (default: Player)
|
|
125
|
+
--netplay-frames <n> Input delay frames 0-16 (default: 0)
|
|
126
|
+
Higher values reduce rollbacks at cost of latency
|
|
127
|
+
|
|
128
|
+
Supported ROM formats: ${extensions}
|
|
129
|
+
|
|
130
|
+
Controls:
|
|
131
|
+
W/Arrow Up D-Pad Up
|
|
132
|
+
S/Arrow Down D-Pad Down
|
|
133
|
+
A/Arrow Left D-Pad Left
|
|
134
|
+
D/Arrow Right D-Pad Right
|
|
135
|
+
K/Z A Button (Jump/Action)
|
|
136
|
+
J/X B Button (Run)
|
|
137
|
+
Enter Start
|
|
138
|
+
Space Select
|
|
139
|
+
Escape/Ctrl+C Quit
|
|
140
|
+
|
|
141
|
+
Note: You can hold buttons and press multiple buttons simultaneously
|
|
142
|
+
(e.g., hold D + J to run right, then press K to jump)
|
|
143
|
+
`);
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
export const debugGamepad = (): void => {
|
|
147
|
+
console.log("Gamepad Debug Mode");
|
|
148
|
+
console.log("==================");
|
|
149
|
+
console.log("Press Ctrl+C to exit\n");
|
|
150
|
+
|
|
151
|
+
const devices = GamepadManager.listDevices();
|
|
152
|
+
if (devices.length === 0) {
|
|
153
|
+
console.log("No gamepad devices found.");
|
|
154
|
+
process.exit(1);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const deviceInfo = devices[0];
|
|
158
|
+
console.log(`Connecting to: ${deviceInfo.product}`);
|
|
159
|
+
console.log(`Path: ${deviceInfo.path}\n`);
|
|
160
|
+
|
|
161
|
+
try {
|
|
162
|
+
const device = new HID.HID(deviceInfo.path);
|
|
163
|
+
let lastData = "";
|
|
164
|
+
|
|
165
|
+
device.on("data", (data: Buffer) => {
|
|
166
|
+
// Only print if data changed (reduces noise)
|
|
167
|
+
const hexStr = Array.from(data)
|
|
168
|
+
.map(
|
|
169
|
+
(b, i) =>
|
|
170
|
+
`${i.toString().padStart(2)}:${b.toString(HEX_RADIX).padStart(2, "0")}`
|
|
171
|
+
)
|
|
172
|
+
.join(" ");
|
|
173
|
+
|
|
174
|
+
if (hexStr !== lastData) {
|
|
175
|
+
// Show byte index and value in both hex and decimal
|
|
176
|
+
console.log(`\nBytes (${data.length}):`);
|
|
177
|
+
const parts: string[] = [];
|
|
178
|
+
for (let i = 0; i < data.length; i++) {
|
|
179
|
+
parts.push(
|
|
180
|
+
`[${i}]=0x${data[i].toString(HEX_RADIX).padStart(2, "0")}(${data[i]
|
|
181
|
+
.toString()
|
|
182
|
+
.padStart(BYTE_DECIMAL_PAD_WIDTH)})`
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
console.log(parts.join(" "));
|
|
186
|
+
lastData = hexStr;
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
device.on("error", (err) => {
|
|
191
|
+
console.error("Device error:", err);
|
|
192
|
+
process.exit(1);
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
// Keep running
|
|
196
|
+
process.on("SIGINT", () => {
|
|
197
|
+
device.close();
|
|
198
|
+
process.exit(0);
|
|
199
|
+
});
|
|
200
|
+
} catch (err) {
|
|
201
|
+
console.error("Failed to open device:", err);
|
|
202
|
+
process.exit(1);
|
|
203
|
+
}
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
export const listGamepads = (): void => {
|
|
207
|
+
console.log("Detected Gamepad Devices");
|
|
208
|
+
console.log("========================\n");
|
|
209
|
+
|
|
210
|
+
const devices = GamepadManager.listDevices();
|
|
211
|
+
|
|
212
|
+
if (devices.length === 0) {
|
|
213
|
+
console.log("No gamepad devices detected.\n");
|
|
214
|
+
console.log("Tips:");
|
|
215
|
+
console.log(
|
|
216
|
+
" - Make sure your controller is connected and paired (for Bluetooth)"
|
|
217
|
+
);
|
|
218
|
+
console.log(" - Try pressing a button on the controller to wake it up");
|
|
219
|
+
console.log(
|
|
220
|
+
' - On Linux, you may need to add your user to the "input" group\n'
|
|
221
|
+
);
|
|
222
|
+
} else {
|
|
223
|
+
const HEX_ID_WIDTH = 4;
|
|
224
|
+
for (const device of devices) {
|
|
225
|
+
console.log(`${device.product}`);
|
|
226
|
+
console.log(` Manufacturer: ${device.manufacturer}`);
|
|
227
|
+
console.log(
|
|
228
|
+
` Vendor ID: 0x${device.vendorId.toString(HEX_RADIX).padStart(HEX_ID_WIDTH, "0")}`
|
|
229
|
+
);
|
|
230
|
+
console.log(
|
|
231
|
+
` Product ID: 0x${device.productId.toString(HEX_RADIX).padStart(HEX_ID_WIDTH, "0")}`
|
|
232
|
+
);
|
|
233
|
+
console.log(` Profile: ${device.profile}`);
|
|
234
|
+
console.log("");
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
console.log("Supported Controllers:");
|
|
239
|
+
for (const profile of GamepadManager.getSupportedProfiles()) {
|
|
240
|
+
console.log(` - ${profile}`);
|
|
241
|
+
}
|
|
242
|
+
console.log(" - Generic USB Gamepad (fallback)");
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
export const listCoresCommand = (): void => {
|
|
246
|
+
console.log("Available Emulator Cores");
|
|
247
|
+
console.log("========================\n");
|
|
248
|
+
|
|
249
|
+
const cores = listCores();
|
|
250
|
+
|
|
251
|
+
if (cores.length === 0) {
|
|
252
|
+
console.log("No cores registered.\n");
|
|
253
|
+
} else {
|
|
254
|
+
for (const core of cores) {
|
|
255
|
+
console.log(`${core.name} (--core ${core.id})`);
|
|
256
|
+
console.log(` Extensions: ${core.extensions.join(", ")}`);
|
|
257
|
+
console.log(` Path: ${core.path}`);
|
|
258
|
+
console.log("");
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
console.log("Note: Cores are auto-detected by ROM file extension.");
|
|
263
|
+
console.log(" Use --core <id> to override auto-detection.");
|
|
264
|
+
};
|
|
265
|
+
|
|
266
|
+
/** Format bytes as human-readable string */
|
|
267
|
+
const formatBytes = (bytes: number): string => {
|
|
268
|
+
const mb = BYTES_PER_KB * BYTES_PER_KB;
|
|
269
|
+
if (bytes >= mb) {
|
|
270
|
+
return `${(bytes / mb).toFixed(1)} MB`;
|
|
271
|
+
} else if (bytes >= BYTES_PER_KB) {
|
|
272
|
+
return `${(bytes / BYTES_PER_KB).toFixed(1)} KB`;
|
|
273
|
+
}
|
|
274
|
+
return `${bytes} B`;
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
/** Install/build a libretro core by name */
|
|
278
|
+
export const installCoreCommand = async (coreName: string): Promise<void> => {
|
|
279
|
+
console.log(`Installing core: ${coreName}`);
|
|
280
|
+
console.log("");
|
|
281
|
+
|
|
282
|
+
try {
|
|
283
|
+
const corePath = await downloadCore(coreName, (progress) => {
|
|
284
|
+
if (progress.phase === "downloading") {
|
|
285
|
+
const percent = progress.totalBytes
|
|
286
|
+
? Math.round((progress.bytesDownloaded / progress.totalBytes) * PERCENT_MULTIPLIER)
|
|
287
|
+
: 0;
|
|
288
|
+
const downloaded = formatBytes(progress.bytesDownloaded);
|
|
289
|
+
const total = progress.totalBytes ? formatBytes(progress.totalBytes) : "unknown";
|
|
290
|
+
// Use carriage return to update the same line
|
|
291
|
+
process.stdout.write(`\rDownloading: ${downloaded} / ${total} (${percent}%)`);
|
|
292
|
+
} else if (progress.phase === "extracting") {
|
|
293
|
+
// Clear the download line and show extracting status
|
|
294
|
+
process.stdout.write("\r" + " ".repeat(LINE_CLEAR_WIDTH) + "\r");
|
|
295
|
+
console.log("Extracting...");
|
|
296
|
+
} else if (progress.phase === "building") {
|
|
297
|
+
// Building from source - show progress percentage with build output
|
|
298
|
+
if (progress.buildProgressPercent !== undefined && progress.buildMessage) {
|
|
299
|
+
const percent = progress.buildProgressPercent;
|
|
300
|
+
// Truncate message to fit on one line with percentage
|
|
301
|
+
const maxMsgLen = LINE_CLEAR_WIDTH - PERCENT_SUFFIX_WIDTH;
|
|
302
|
+
const msg = progress.buildMessage.length > maxMsgLen
|
|
303
|
+
? progress.buildMessage.slice(0, maxMsgLen - ELLIPSIS_LENGTH) + "..."
|
|
304
|
+
: progress.buildMessage.padEnd(maxMsgLen);
|
|
305
|
+
// Use carriage return to update the same line
|
|
306
|
+
process.stdout.write(`\r${msg} (${percent}%)`);
|
|
307
|
+
} else if (progress.buildMessage) {
|
|
308
|
+
// No progress data - just log the message (used during initial analysis)
|
|
309
|
+
process.stdout.write("\r" + " ".repeat(LINE_CLEAR_WIDTH) + "\r");
|
|
310
|
+
console.log(progress.buildMessage);
|
|
311
|
+
}
|
|
312
|
+
} else {
|
|
313
|
+
// phase === "complete" - clear any partial line
|
|
314
|
+
process.stdout.write("\r" + " ".repeat(LINE_CLEAR_WIDTH) + "\r");
|
|
315
|
+
}
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
// Register the newly installed core so it can be used immediately
|
|
319
|
+
registerLibretroCore(corePath);
|
|
320
|
+
|
|
321
|
+
console.log(`Successfully installed: ${corePath}`);
|
|
322
|
+
} catch (error) {
|
|
323
|
+
const errorMessage = getErrorMessage(error);
|
|
324
|
+
console.error(`\nError installing core: ${errorMessage}`);
|
|
325
|
+
logger.error(`Failed to install core ${coreName}: ${errorMessage}`, "CLI");
|
|
326
|
+
process.exit(1);
|
|
327
|
+
}
|
|
328
|
+
};
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* Remove an installed libretro core by ID
|
|
332
|
+
* Uses the same core ID shown in --list-cores and accepted by --core
|
|
333
|
+
*/
|
|
334
|
+
export const removeCoreCommand = (coreId: string): void => {
|
|
335
|
+
// Look up the core in the registry by ID
|
|
336
|
+
const cores = listCores();
|
|
337
|
+
const core = cores.find(c => c.id === coreId);
|
|
338
|
+
|
|
339
|
+
if (!core) {
|
|
340
|
+
console.error(`Core "${coreId}" is not installed.`);
|
|
341
|
+
console.error("Use --list-cores to see installed cores.");
|
|
342
|
+
process.exit(1);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// Don't allow removing native cores
|
|
346
|
+
try {
|
|
347
|
+
// Delete the core file
|
|
348
|
+
unlinkSync(core.path);
|
|
349
|
+
// Unregister from the core registry
|
|
350
|
+
unregisterCore(coreId);
|
|
351
|
+
// Clean up libretro loader tracking
|
|
352
|
+
unloadLibretroCore(core.path, coreId);
|
|
353
|
+
console.log(`Successfully removed: ${core.path}`);
|
|
354
|
+
logger.info(`Removed core ${coreId} from ${core.path}`, "CLI");
|
|
355
|
+
} catch (error) {
|
|
356
|
+
const errorMessage = getErrorMessage(error);
|
|
357
|
+
console.error(`Error removing core: ${errorMessage}`);
|
|
358
|
+
logger.error(`Failed to remove core ${coreId}: ${errorMessage}`, "CLI");
|
|
359
|
+
process.exit(1);
|
|
360
|
+
}
|
|
361
|
+
};
|
|
362
|
+
|
|
363
|
+
/** Delete all emoemu log files */
|
|
364
|
+
export const clearLogsCommand = (): void => {
|
|
365
|
+
const logsDir = getDefaultLogsDirectory();
|
|
366
|
+
|
|
367
|
+
if (!existsSync(logsDir)) {
|
|
368
|
+
console.log("No logs directory found. Nothing to delete.");
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
const files = readdirSync(logsDir);
|
|
373
|
+
const logFiles = files.filter(f => f.endsWith('.log') || /\.log\.\d+$/.test(f));
|
|
374
|
+
|
|
375
|
+
if (logFiles.length === 0) {
|
|
376
|
+
console.log("No log files found.");
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
let deleted = 0;
|
|
381
|
+
for (const file of logFiles) {
|
|
382
|
+
try {
|
|
383
|
+
unlinkSync(join(logsDir, file));
|
|
384
|
+
deleted++;
|
|
385
|
+
} catch {
|
|
386
|
+
console.error(`Failed to delete: ${file}`);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
console.log(`Deleted ${deleted} log file${deleted === 1 ? '' : 's'} from ${logsDir}`);
|
|
391
|
+
};
|
|
392
|
+
|
|
393
|
+
/** Generate RetroArch-compatible playlists from scanned ROMs */
|
|
394
|
+
export const generatePlaylistCommand = (
|
|
395
|
+
scanPath: string,
|
|
396
|
+
scanDepth: number,
|
|
397
|
+
outputDir: string,
|
|
398
|
+
singlePlaylist: string | undefined,
|
|
399
|
+
windowsPaths: boolean
|
|
400
|
+
): void => {
|
|
401
|
+
console.log("Generating RetroArch Playlists");
|
|
402
|
+
console.log("==============================\n");
|
|
403
|
+
|
|
404
|
+
console.log(`Scanning: ${scanPath}`);
|
|
405
|
+
console.log(`Scan depth: ${scanDepth === -1 ? 'unlimited' : scanDepth}`);
|
|
406
|
+
console.log(`Output: ${outputDir}`);
|
|
407
|
+
if (singlePlaylist) {
|
|
408
|
+
console.log(`Mode: single playlist (${singlePlaylist}.lpl)`);
|
|
409
|
+
} else {
|
|
410
|
+
console.log(`Mode: per-system playlists`);
|
|
411
|
+
}
|
|
412
|
+
console.log("");
|
|
413
|
+
|
|
414
|
+
// Scan for ROMs (CRC cache built automatically from playlists)
|
|
415
|
+
console.log("Scanning for ROMs...");
|
|
416
|
+
const roms = scanDirectory(scanPath, scanDepth);
|
|
417
|
+
|
|
418
|
+
if (roms.length === 0) {
|
|
419
|
+
console.log("No ROMs found.\n");
|
|
420
|
+
console.log("Make sure you have libretro cores installed to detect ROM types.");
|
|
421
|
+
console.log("Use --retroarch to load cores from RetroArch installation.");
|
|
422
|
+
return;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
console.log(`Found ${roms.length} ROM(s)\n`);
|
|
426
|
+
|
|
427
|
+
// Build playlist options
|
|
428
|
+
const playlistOptions: PlaylistOptions = {
|
|
429
|
+
windowsPaths,
|
|
430
|
+
};
|
|
431
|
+
|
|
432
|
+
// Generate playlists
|
|
433
|
+
if (singlePlaylist) {
|
|
434
|
+
// Single consolidated playlist
|
|
435
|
+
const outputPath = join(outputDir, singlePlaylist);
|
|
436
|
+
const result = generateConsolidatedPlaylist(roms, outputPath, playlistOptions);
|
|
437
|
+
|
|
438
|
+
if (result.success) {
|
|
439
|
+
console.log(`Created: ${result.outputPath} (${result.entryCount} entries)`);
|
|
440
|
+
} else {
|
|
441
|
+
console.error(`Error: ${result.error}`);
|
|
442
|
+
}
|
|
443
|
+
} else {
|
|
444
|
+
// Per-system playlists
|
|
445
|
+
const results = generatePlaylistsBySystem(roms, outputDir, playlistOptions);
|
|
446
|
+
|
|
447
|
+
let successCount = 0;
|
|
448
|
+
let totalEntries = 0;
|
|
449
|
+
|
|
450
|
+
for (const result of results) {
|
|
451
|
+
if (result.success) {
|
|
452
|
+
console.log(`Created: ${result.outputPath} (${result.entryCount} entries)`);
|
|
453
|
+
successCount++;
|
|
454
|
+
totalEntries += result.entryCount;
|
|
455
|
+
} else {
|
|
456
|
+
console.error(`Error: ${result.error}`);
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
console.log(`\nGenerated ${successCount} playlist(s) with ${totalEntries} total entries.`);
|
|
461
|
+
}
|
|
462
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// Default values for optional CLI flags
|
|
2
|
+
export const DEFAULT_SCANLINES = 0.3;
|
|
3
|
+
export const DEFAULT_VIGNETTE = 1.0;
|
|
4
|
+
export const DEFAULT_BLOOM = 0.5;
|
|
5
|
+
export const MIN_FRAME_LIMIT = 1; // Minimum allowed frame limit (fps)
|
|
6
|
+
export const DEFAULT_NTSC = 1.0;
|
|
7
|
+
export const DEFAULT_CURVATURE = 0.1;
|
|
8
|
+
export const DEFAULT_CHROMATIC_ABERRATION = 0.5;
|
|
9
|
+
|
|
10
|
+
// Default values for --crt shortcut
|
|
11
|
+
export const CRT_SCALE = 1;
|
|
12
|
+
export const CRT_NTSC = 1.0;
|
|
13
|
+
export const CRT_SCANLINES = 0.1;
|
|
14
|
+
export const CRT_GAMMA = 1.3;
|
|
15
|
+
export const CRT_VIGNETTE = 0.5;
|
|
16
|
+
export const CRT_CURVATURE = 0.1;
|
|
17
|
+
export const CRT_CHROMATIC_ABERRATION = 0.3;
|