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,345 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Netplay Logger
|
|
3
|
+
*
|
|
4
|
+
* Writes detailed netplay events to a log file for debugging and monitoring.
|
|
5
|
+
* Log file is stored in the platform-specific config directory.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { appendFileSync } from 'fs';
|
|
9
|
+
import { ensureDirectory } from '../../utils/ensureDirectory';
|
|
10
|
+
import { rotateLogFile } from '../../utils/rotateLogFile';
|
|
11
|
+
import { dirname, join } from 'path';
|
|
12
|
+
import { getConfigDirectory } from '../../utils/paths';
|
|
13
|
+
import { HEX_RADIX } from '..';
|
|
14
|
+
|
|
15
|
+
export * from './consts';
|
|
16
|
+
|
|
17
|
+
import { LOG_LEVEL_PRIORITY, MAX_LOG_SIZE_BYTES, MAX_BACKUP_FILES, MS_PAD_WIDTH, LEVEL_PAD_WIDTH } from './consts';
|
|
18
|
+
|
|
19
|
+
/** Log levels */
|
|
20
|
+
export type LogLevel = 'debug' | 'info' | 'warn' | 'error';
|
|
21
|
+
|
|
22
|
+
/** Format a date for log timestamps */
|
|
23
|
+
const formatTimestamp = (): string => {
|
|
24
|
+
const now = new Date();
|
|
25
|
+
const pad = (n: number, width = 2): string => String(n).padStart(width, '0');
|
|
26
|
+
return `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())} ` +
|
|
27
|
+
`${pad(now.getHours())}:${pad(now.getMinutes())}:${pad(now.getSeconds())}.${pad(now.getMilliseconds(), MS_PAD_WIDTH)}`;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
/** Get the netplay log directory */
|
|
31
|
+
const getLogDirectory = (): string => join(getConfigDirectory(), 'logs');
|
|
32
|
+
|
|
33
|
+
/** Get the netplay log file path */
|
|
34
|
+
const getLogFilePath = (): string => join(getLogDirectory(), 'netplay.log');
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* NetplayLogger provides structured logging for netplay events.
|
|
38
|
+
*/
|
|
39
|
+
class NetplayLogger {
|
|
40
|
+
private logPath: string;
|
|
41
|
+
private minLevel: LogLevel = 'debug';
|
|
42
|
+
private enabled = true;
|
|
43
|
+
private initialized = false;
|
|
44
|
+
|
|
45
|
+
constructor() {
|
|
46
|
+
this.logPath = getLogFilePath();
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Enable or disable logging */
|
|
50
|
+
setEnabled(enabled: boolean): void {
|
|
51
|
+
this.enabled = enabled;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Set minimum log level */
|
|
55
|
+
setMinLevel(level: LogLevel): void {
|
|
56
|
+
this.minLevel = level;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Get the log file path */
|
|
60
|
+
getLogPath(): string {
|
|
61
|
+
return this.logPath;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Initialize the log file (creates directory and rotates if needed) */
|
|
65
|
+
private initialize(): void {
|
|
66
|
+
if (this.initialized) {
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
ensureDirectory(dirname(this.logPath));
|
|
71
|
+
|
|
72
|
+
// Check if rotation is needed
|
|
73
|
+
this.rotateIfNeeded();
|
|
74
|
+
|
|
75
|
+
this.initialized = true;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** Rotate log file if it exceeds max size */
|
|
79
|
+
private rotateIfNeeded(): void {
|
|
80
|
+
rotateLogFile(this.logPath, MAX_LOG_SIZE_BYTES, MAX_BACKUP_FILES);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** Write a log entry */
|
|
84
|
+
private write(level: LogLevel, category: string, message: string, details?: Record<string, unknown>): void {
|
|
85
|
+
if (!this.enabled) {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (LOG_LEVEL_PRIORITY[level] < LOG_LEVEL_PRIORITY[this.minLevel]) {
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
this.initialize();
|
|
94
|
+
|
|
95
|
+
const timestamp = formatTimestamp();
|
|
96
|
+
const levelStr = level.toUpperCase().padEnd(LEVEL_PAD_WIDTH);
|
|
97
|
+
let logLine = `[${timestamp}] ${levelStr} [${category}] ${message}`;
|
|
98
|
+
|
|
99
|
+
if (details && Object.keys(details).length > 0) {
|
|
100
|
+
const detailStr = Object.entries(details)
|
|
101
|
+
.map(([k, v]) => `${k}=${JSON.stringify(v)}`)
|
|
102
|
+
.join(' ');
|
|
103
|
+
logLine += ` | ${detailStr}`;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
logLine += '\n';
|
|
107
|
+
|
|
108
|
+
try {
|
|
109
|
+
appendFileSync(this.logPath, logLine);
|
|
110
|
+
} catch {
|
|
111
|
+
// Silently ignore write errors
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/** Clear the log file and write a session header */
|
|
116
|
+
startSession(info: { nickname: string; mode: 'host' | 'client'; port?: number; host?: string }): void {
|
|
117
|
+
if (!this.enabled) {
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
this.initialize();
|
|
122
|
+
|
|
123
|
+
const header = `
|
|
124
|
+
================================================================================
|
|
125
|
+
NETPLAY SESSION STARTED
|
|
126
|
+
Time: ${formatTimestamp()}
|
|
127
|
+
Mode: ${info.mode.toUpperCase()}
|
|
128
|
+
Nickname: ${info.nickname}
|
|
129
|
+
${info.mode === 'host' ? `Port: ${info.port}` : `Host: ${info.host}:${info.port}`}
|
|
130
|
+
================================================================================
|
|
131
|
+
`;
|
|
132
|
+
|
|
133
|
+
try {
|
|
134
|
+
appendFileSync(this.logPath, header);
|
|
135
|
+
} catch {
|
|
136
|
+
// Silently ignore write errors
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/** Log session end */
|
|
141
|
+
endSession(reason: string): void {
|
|
142
|
+
this.write('info', 'SESSION', `Session ended: ${reason}`);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// ============================================================================
|
|
146
|
+
// Discovery Events
|
|
147
|
+
// ============================================================================
|
|
148
|
+
|
|
149
|
+
/** Log discovery broadcaster start */
|
|
150
|
+
discoveryStarted(port: number, addresses: string[]): void {
|
|
151
|
+
this.write('info', 'DISCOVERY', 'LAN discovery broadcasting started', {
|
|
152
|
+
port,
|
|
153
|
+
broadcastAddresses: addresses,
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/** Log discovery broadcaster stop */
|
|
158
|
+
discoveryStopped(): void {
|
|
159
|
+
this.write('info', 'DISCOVERY', 'LAN discovery broadcasting stopped');
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/** Log discovery broadcast sent */
|
|
163
|
+
discoveryBroadcast(address: string): void {
|
|
164
|
+
this.write('debug', 'DISCOVERY', `Broadcast sent to ${address}`);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/** Log discovery broadcast error */
|
|
168
|
+
discoveryError(error: string): void {
|
|
169
|
+
this.write('error', 'DISCOVERY', `Discovery error: ${error}`);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// ============================================================================
|
|
173
|
+
// Server Events
|
|
174
|
+
// ============================================================================
|
|
175
|
+
|
|
176
|
+
/** Log server start */
|
|
177
|
+
serverStarted(port: number, nickname: string, hasPassword: boolean): void {
|
|
178
|
+
this.write('info', 'SERVER', 'Netplay server started', {
|
|
179
|
+
port,
|
|
180
|
+
nickname,
|
|
181
|
+
hasPassword,
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/** Log server stop */
|
|
186
|
+
serverStopped(): void {
|
|
187
|
+
this.write('info', 'SERVER', 'Netplay server stopped');
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/** Log incoming connection attempt */
|
|
191
|
+
connectionAttempt(clientId: number, remoteAddress: string, remotePort: number): void {
|
|
192
|
+
this.write('info', 'SERVER', `Connection attempt from ${remoteAddress}:${remotePort}`, {
|
|
193
|
+
clientId,
|
|
194
|
+
remoteAddress,
|
|
195
|
+
remotePort,
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/** Log client connected (after handshake) */
|
|
200
|
+
clientConnected(clientId: number, nickname: string, isPlaying: boolean, deviceIndex: number): void {
|
|
201
|
+
this.write('info', 'SERVER', `Client connected: ${nickname}`, {
|
|
202
|
+
clientId,
|
|
203
|
+
nickname,
|
|
204
|
+
isPlaying,
|
|
205
|
+
deviceIndex,
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/** Log client disconnected */
|
|
210
|
+
clientDisconnected(clientId: number, nickname: string, reason: string, details?: {
|
|
211
|
+
state?: string;
|
|
212
|
+
handshakeCompleted?: boolean;
|
|
213
|
+
commandsReceived?: string[];
|
|
214
|
+
connectedDuration?: number;
|
|
215
|
+
}): void {
|
|
216
|
+
this.write('info', 'SERVER', `Client disconnected: ${nickname}`, {
|
|
217
|
+
clientId,
|
|
218
|
+
nickname,
|
|
219
|
+
reason,
|
|
220
|
+
...details,
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/** Log handshake step */
|
|
225
|
+
handshakeStep(clientId: number, step: string, details?: Record<string, unknown>): void {
|
|
226
|
+
this.write('debug', 'SERVER', `Handshake [${clientId}]: ${step}`, details);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/** Log handshake failure */
|
|
230
|
+
handshakeFailed(clientId: number, reason: string): void {
|
|
231
|
+
this.write('warn', 'SERVER', `Handshake failed for client ${clientId}`, { reason });
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/** Log core/content mismatch */
|
|
235
|
+
mismatch(clientId: number, type: 'core' | 'crc', expected: string | number, received: string | number): void {
|
|
236
|
+
this.write('warn', 'SERVER', `${type.toUpperCase()} mismatch from client ${clientId}`, {
|
|
237
|
+
expected,
|
|
238
|
+
received,
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/** Log password authentication */
|
|
243
|
+
passwordAuth(clientId: number, success: boolean): void {
|
|
244
|
+
this.write('info', 'SERVER', `Password authentication ${success ? 'succeeded' : 'failed'}`, {
|
|
245
|
+
clientId,
|
|
246
|
+
success,
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/** Log server error */
|
|
251
|
+
serverError(error: string, details?: Record<string, unknown>): void {
|
|
252
|
+
this.write('error', 'SERVER', error, details);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// ============================================================================
|
|
256
|
+
// Client Events
|
|
257
|
+
// ============================================================================
|
|
258
|
+
|
|
259
|
+
/** Log client connecting */
|
|
260
|
+
clientConnecting(host: string, port: number): void {
|
|
261
|
+
this.write('info', 'CLIENT', `Connecting to ${host}:${port}`, { host, port });
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/** Log client connected to server */
|
|
265
|
+
connectedToServer(host: string, port: number): void {
|
|
266
|
+
this.write('info', 'CLIENT', `Connected to server ${host}:${port}`);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/** Log client connection failed */
|
|
270
|
+
connectionFailed(host: string, port: number, reason: string): void {
|
|
271
|
+
this.write('error', 'CLIENT', `Connection failed to ${host}:${port}`, { reason });
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/** Log client disconnected from server */
|
|
275
|
+
disconnectedFromServer(reason: string): void {
|
|
276
|
+
this.write('info', 'CLIENT', `Disconnected from server`, { reason });
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/** Log client error */
|
|
280
|
+
clientError(error: string, details?: Record<string, unknown>): void {
|
|
281
|
+
this.write('error', 'CLIENT', error, details);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// ============================================================================
|
|
285
|
+
// Sync Events
|
|
286
|
+
// ============================================================================
|
|
287
|
+
|
|
288
|
+
/** Log desync detection */
|
|
289
|
+
desyncDetected(frame: number, localCrc: number, remoteCrc: number): void {
|
|
290
|
+
this.write('warn', 'SYNC', `Desync detected at frame ${frame}`, {
|
|
291
|
+
frame,
|
|
292
|
+
localCrc: localCrc.toString(HEX_RADIX),
|
|
293
|
+
remoteCrc: remoteCrc.toString(HEX_RADIX),
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/** Log desync recovery trigger */
|
|
298
|
+
desyncRecovery(frame: number, trigger: 'server' | 'client-request'): void {
|
|
299
|
+
this.write('info', 'SYNC', `Desync recovery triggered at frame ${frame} (${trigger})`, { frame, trigger });
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/** Log rollback */
|
|
303
|
+
rollback(fromFrame: number, toFrame: number): void {
|
|
304
|
+
this.write('info', 'SYNC', `Rollback from frame ${fromFrame} to ${toFrame}`, {
|
|
305
|
+
fromFrame,
|
|
306
|
+
toFrame,
|
|
307
|
+
framesDelta: fromFrame - toFrame,
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/** Log stall (waiting for remote input) */
|
|
312
|
+
stall(frame: number, waitingFor: number[]): void {
|
|
313
|
+
this.write('debug', 'SYNC', `Stalling at frame ${frame}`, {
|
|
314
|
+
frame,
|
|
315
|
+
waitingForClients: waitingFor,
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// ============================================================================
|
|
320
|
+
// Generic Events
|
|
321
|
+
// ============================================================================
|
|
322
|
+
|
|
323
|
+
/** Log a debug message */
|
|
324
|
+
debug(category: string, message: string, details?: Record<string, unknown>): void {
|
|
325
|
+
this.write('debug', category, message, details);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/** Log an info message */
|
|
329
|
+
info(category: string, message: string, details?: Record<string, unknown>): void {
|
|
330
|
+
this.write('info', category, message, details);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/** Log a warning message */
|
|
334
|
+
warn(category: string, message: string, details?: Record<string, unknown>): void {
|
|
335
|
+
this.write('warn', category, message, details);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/** Log an error message */
|
|
339
|
+
error(category: string, message: string, details?: Record<string, unknown>): void {
|
|
340
|
+
this.write('error', category, message, details);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/** Singleton logger instance */
|
|
345
|
+
export const netplayLogger = new NetplayLogger();
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import {
|
|
2
|
+
BIT_29,
|
|
3
|
+
BIT_30,
|
|
4
|
+
BIT_31,
|
|
5
|
+
MASK_16BIT,
|
|
6
|
+
MAX_NICK_LEN,
|
|
7
|
+
UINT32_SIZE,
|
|
8
|
+
} from '..';
|
|
9
|
+
|
|
10
|
+
/** Offset of payload size in command header */
|
|
11
|
+
export const PAYLOAD_SIZE_OFFSET = 4;
|
|
12
|
+
|
|
13
|
+
/** Number of bytes to reserve for null terminator in strings */
|
|
14
|
+
export const NULL_TERMINATOR_SIZE = 1;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Size of size_t to report in platform magic.
|
|
18
|
+
* Use 4 (32-bit) for broader compatibility with RetroArch clients.
|
|
19
|
+
*/
|
|
20
|
+
export const SIZEOF_SIZE_T = 4;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Protocol version to use.
|
|
24
|
+
* RetroArch requires minimum version 5 (LOW_NETPLAY_PROTOCOL_VERSION).
|
|
25
|
+
* Current HIGH_NETPLAY_PROTOCOL_VERSION is 7.
|
|
26
|
+
* We use version 7 to support NETPLAY state format in LOAD_SAVESTATE.
|
|
27
|
+
*/
|
|
28
|
+
export const OUR_PROTOCOL_VERSION = 7;
|
|
29
|
+
|
|
30
|
+
/** Highest supported protocol version (from RetroArch) */
|
|
31
|
+
export const HIGH_NETPLAY_PROTOCOL_VERSION = 7;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Compression flags.
|
|
35
|
+
* 0 = no compression support, 1 = zlib compression supported.
|
|
36
|
+
* We use zlib compression for LOAD_SAVESTATE.
|
|
37
|
+
*/
|
|
38
|
+
export const COMPRESSION_SUPPORTED = 1;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Header field 3 (offset 12) meaning depends on who sends it:
|
|
42
|
+
* - Server: salt value (0 = no password required, non-zero = password required)
|
|
43
|
+
* - Client: highest supported protocol version (hack for backwards compatibility)
|
|
44
|
+
*/
|
|
45
|
+
export const CLIENT_PROTOCOL_FIELD = HIGH_NETPLAY_PROTOCOL_VERSION;
|
|
46
|
+
|
|
47
|
+
/** Offset of field 5 in extended header */
|
|
48
|
+
export const HEADER_FIELD5_OFFSET = 16;
|
|
49
|
+
|
|
50
|
+
/** Offset of field 6 in extended header */
|
|
51
|
+
export const HEADER_FIELD6_OFFSET = 20;
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Field 5 value observed from RetroArch traffic.
|
|
55
|
+
* Purpose unknown - possibly client capabilities or ID.
|
|
56
|
+
*/
|
|
57
|
+
export const HEADER_FIELD5_VALUE = 5;
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Field 6 value observed from RetroArch traffic (0x455 = 1109).
|
|
61
|
+
* Purpose unknown - possibly related to protocol features.
|
|
62
|
+
*/
|
|
63
|
+
export const HEADER_FIELD6_VALUE = 0x455;
|
|
64
|
+
|
|
65
|
+
/** Mask for client number in INPUT command (lower 16 bits per RetroArch reference) */
|
|
66
|
+
export const INPUT_CLIENT_NUM_MASK = MASK_16BIT;
|
|
67
|
+
|
|
68
|
+
/** Size of share modes array in SYNC/MODE commands */
|
|
69
|
+
export const SHARE_MODES_SIZE = 16;
|
|
70
|
+
|
|
71
|
+
/** Bit flag for paused state in SYNC command */
|
|
72
|
+
export const NETPLAY_CMD_SYNC_BIT_PAUSED = 1 << BIT_31;
|
|
73
|
+
|
|
74
|
+
/** MODE command bits per reference */
|
|
75
|
+
export const NETPLAY_CMD_MODE_BIT_YOU = 1 << BIT_31;
|
|
76
|
+
export const NETPLAY_CMD_MODE_BIT_PLAYING = 1 << BIT_30;
|
|
77
|
+
export const NETPLAY_CMD_MODE_BIT_SLAVE = 1 << BIT_29;
|
|
78
|
+
|
|
79
|
+
/** Full MODE payload size: frame + flags + device_bitmap + share_modes + nick */
|
|
80
|
+
export const MODE_FULL_PAYLOAD_SIZE = UINT32_SIZE + UINT32_SIZE + UINT32_SIZE + SHARE_MODES_SIZE + MAX_NICK_LEN;
|
|
81
|
+
|
|
82
|
+
/** Minimum protocol version that supports NETPLAY state format */
|
|
83
|
+
export const NETPLAY_FORMAT_MIN_VERSION = 7;
|
|
84
|
+
|
|
85
|
+
/** PLAY command bit for slave mode */
|
|
86
|
+
export const NETPLAY_CMD_PLAY_BIT_SLAVE = 1 << BIT_31;
|