yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
wasm_bootstrap.cc
Go to the documentation of this file.
2
3#ifdef __EMSCRIPTEN__
4
5#include <emscripten.h>
6#include <fstream>
7#include <algorithm>
8#include <mutex>
9#include <queue>
10#include <vector>
13#include "util/log.h"
14
15namespace {
16
17bool g_filesystem_ready = false;
18std::function<void(std::string)> g_rom_load_handler;
19std::queue<std::string> g_pending_rom_loads;
20std::mutex g_rom_load_mutex;
21
22} // namespace
23
24extern "C" {
25
26EMSCRIPTEN_KEEPALIVE
27void SetFileSystemReady() {
28 g_filesystem_ready = true;
29 LOG_INFO("Wasm", "Filesystem sync complete.");
30
31 // Notify JS that FS is ready
32 EM_ASM({
33 if (Module.onFileSystemReady) {
34 Module.onFileSystemReady();
35 }
36 });
37}
38
39EMSCRIPTEN_KEEPALIVE
40void SyncFilesystem() {
41 // Sync all IDBFS mounts to IndexedDB for persistence
42 EM_ASM({
43 if (typeof FS !== 'undefined' && FS.syncfs) {
44 FS.syncfs(false, function(err) {
45 if (err) {
46 console.error('[WASM] Failed to sync filesystem:', err);
47 } else {
48 console.log('[WASM] Filesystem synced successfully');
49 }
50 });
51 }
52 });
53}
54
55EMSCRIPTEN_KEEPALIVE
56void LoadRomFromWeb(const char* filename) {
57 if (!filename) {
58 LOG_ERROR("Wasm", "LoadRomFromWeb called with null filename");
59 return;
60 }
61
62 std::string path(filename);
63
64 // Validate path is not empty
65 if (path.empty()) {
66 LOG_ERROR("Wasm", "LoadRomFromWeb called with empty filename");
67 return;
68 }
69
70 // Validate path doesn't contain path traversal
71 if (path.find("..") != std::string::npos) {
72 LOG_ERROR("Wasm", "LoadRomFromWeb: path traversal not allowed: %s", filename);
73 return;
74 }
75
76 // Validate reasonable path length (max 512 chars)
77 if (path.length() > 512) {
78 LOG_ERROR("Wasm", "LoadRomFromWeb: path too long (%zu chars)", path.length());
79 return;
80 }
81
82 yaze::app::wasm::TriggerRomLoad(path);
83}
84
85} // extern "C"
86
87EM_JS(void, MountFilesystems, (), {
88 if (typeof FS === 'undefined') {
89 if (typeof Module !== 'undefined') {
90 Module.YAZE_FS_MOUNT_ATTEMPTS = (Module.YAZE_FS_MOUNT_ATTEMPTS || 0) + 1;
91 if (Module.YAZE_FS_MOUNT_ATTEMPTS < 50) {
92 console.warn('[WASM] FS not ready, retrying mount (' + Module.YAZE_FS_MOUNT_ATTEMPTS + ')');
93 setTimeout(MountFilesystems, 50);
94 return;
95 }
96 }
97 console.error('[WASM] FS unavailable, skipping filesystem mounts');
98 if (typeof Module !== 'undefined' && Module._SetFileSystemReady) {
99 Module._SetFileSystemReady();
100 }
101 return;
102 }
103
104 function isErrno(err, code) {
105 if (!err) return false;
106 if (err.code === code) return true;
107 if (typeof err.errno === 'number' && FS.ERRNO_CODES && FS.ERRNO_CODES[code] === err.errno) {
108 return true;
109 }
110 return false;
111 }
112
113 function pathExists(path) {
114 try {
115 FS.stat(path);
116 return true;
117 } catch (e) {
118 return false;
119 }
120 }
121
122 function ensureDir(dir) {
123 try {
124 var stat = FS.stat(dir);
125 if (FS.isDir(stat.mode)) return true;
126
127 var backup = dir + '.bak';
128 if (pathExists(backup)) {
129 var suffix = 1;
130 while (suffix < 5 && pathExists(backup + suffix)) {
131 suffix++;
132 }
133 backup = backup + suffix;
134 }
135 try {
136 FS.rename(dir, backup);
137 console.warn('[WASM] Renamed file at ' + dir + ' to ' + backup);
138 } catch (renameErr) {
139 console.warn('[WASM] Failed to rename file at ' + dir + ': ' + renameErr);
140 return false;
141 }
142 } catch (e) {
143 if (!isErrno(e, 'ENOENT')) {
144 console.warn('[WASM] Failed to stat directory ' + dir + ': ' + e);
145 }
146 }
147
148 try {
149 FS.mkdir(dir);
150 return true;
151 } catch (e) {
152 if (isErrno(e, 'EEXIST')) return true;
153 console.warn('[WASM] Failed to create directory ' + dir + ': ' + e);
154 return false;
155 }
156 }
157
158 // Create all required directories
159 var directories = [
160 '/roms', // ROM files (IDBFS - persistent for session restore)
161 '/saves', // Save files (IDBFS - persistent)
162 '/config', // Configuration files (IDBFS - persistent)
163 '/projects', // Project files (IDBFS - persistent)
164 '/prompts', // Agent prompts (IDBFS - persistent)
165 '/recent', // Recent files metadata (IDBFS - persistent)
166 '/temp' // Temporary files (MEMFS - non-persistent)
167 ];
168
169 directories.forEach(function(dir) {
170 ensureDir(dir);
171 });
172
173 // Mount MEMFS for temporary files only
174 try {
175 FS.mount(MEMFS, {}, '/temp');
176 } catch (e) {
177 if (!isErrno(e, 'EBUSY') && !isErrno(e, 'EEXIST')) {
178 console.warn('[WASM] Failed to mount MEMFS for /temp: ' + e);
179 }
180 }
181
182 // Check if IDBFS is available (try multiple ways to access it)
183 var idbfs = null;
184 if (typeof IDBFS !== 'undefined') {
185 idbfs = IDBFS;
186 } else if (typeof Module !== 'undefined' && typeof Module.IDBFS !== 'undefined') {
187 idbfs = Module.IDBFS;
188 } else if (typeof FS !== 'undefined' && typeof FS.filesystems !== 'undefined' && FS.filesystems.IDBFS) {
189 idbfs = FS.filesystems.IDBFS;
190 }
191
192 // Persistent directories to mount with IDBFS
193 var persistentDirs = ['/roms', '/saves', '/config', '/projects', '/prompts', '/recent'];
194 var mountedCount = 0;
195 var totalToMount = persistentDirs.length;
196
197 if (idbfs !== null) {
198 persistentDirs.forEach(function(dir) {
199 try {
200 FS.mount(idbfs, {}, dir);
201 mountedCount++;
202 } catch (e) {
203 console.error("Error mounting IDBFS for " + dir + ": " + e);
204 // Fallback to MEMFS for this directory
205 try {
206 FS.mount(MEMFS, {}, dir);
207 } catch (e2) {
208 // May already be mounted
209 }
210 mountedCount++;
211 }
212 });
213
214 // Sync all IDBFS mounts from IndexedDB to memory
215 FS.syncfs(true, function(err) {
216 if (err) {
217 console.error("Failed to sync IDBFS: " + err);
218 } else {
219 console.log("IDBFS synced successfully");
220 }
221 // Signal C++ that we are ready regardless of success/fail
222 Module._SetFileSystemReady();
223 });
224 } else {
225 // Fallback to MEMFS if IDBFS is not available
226 console.warn("IDBFS not available, using MEMFS for all directories (no persistence)");
227 persistentDirs.forEach(function(dir) {
228 try {
229 FS.mount(MEMFS, {}, dir);
230 } catch (e) {
231 // May already be mounted
232 }
233 });
234 Module._SetFileSystemReady();
235 }
236});
237
238EM_JS(void, SetupYazeGlobalApi, (), {
239 if (typeof Module === 'undefined') return;
240
241 // Initialize global API for agents/automation
242 window.yazeApp = {
243 execute: function(cmd) {
244 if (Module.executeCommand) {
245 return Module.executeCommand(cmd);
246 }
247 return "Error: bindings not ready";
248 },
249
250 getState: function() {
251 if (Module.getFullDebugState) {
252 try { return JSON.parse(Module.getFullDebugState()); } catch(e) { return {}; }
253 }
254 return {};
255 },
256
257 getEditorState: function() {
258 if (Module.getEditorState) {
259 try { return JSON.parse(Module.getEditorState()); } catch(e) { return {}; }
260 }
261 return {};
262 },
263
264 loadRom: function(filename) {
265 return this.execute("rom load " + filename);
266 }
267 };
268
269 console.log("[yaze] window.yazeApp API initialized for agents");
270});
271
272namespace yaze::app::wasm {
273
274bool IsFileSystemReady() {
275 return g_filesystem_ready;
276}
277
278void SetRomLoadHandler(std::function<void(std::string)> handler) {
279 std::lock_guard<std::mutex> lock(g_rom_load_mutex);
280 g_rom_load_handler = handler;
281
282 // Flush all pending ROM loads
283 while (!g_pending_rom_loads.empty()) {
284 std::string path = g_pending_rom_loads.front();
285 g_pending_rom_loads.pop();
286 LOG_INFO("Wasm", "Flushing pending ROM load: %s", path.c_str());
287 handler(path);
288 }
289}
290
291void TriggerRomLoad(const std::string& path) {
292 std::lock_guard<std::mutex> lock(g_rom_load_mutex);
293 if (g_rom_load_handler) {
294 g_rom_load_handler(path);
295 } else {
296 LOG_INFO("Wasm", "Queuing ROM load (handler not ready): %s", path.c_str());
297 g_pending_rom_loads.push(path);
298 }
299}
300
301void InitializeWasmPlatform() {
302 // Load WASM configuration from JavaScript
303 app::platform::WasmConfig::Get().LoadFromJavaScript();
304
305 // Setup global API
306 SetupYazeGlobalApi();
307
308 // Initialize drop handler for Drag & Drop support
309 auto& drop_handler = yaze::platform::WasmDropHandler::GetInstance();
310 drop_handler.Initialize("",
311 [](const std::string& filename, const std::vector<uint8_t>& data) {
312 // Determine file type from extension
313 std::string ext = filename.substr(filename.find_last_of(".") + 1);
314 std::transform(ext.begin(), ext.end(), ext.begin(), ::tolower);
315
316 if (ext == "sfc" || ext == "smc" || ext == "zip") {
317 // Write to MEMFS and load
318 std::string path = "/roms/" + filename;
319 std::ofstream file(path, std::ios::binary);
320 file.write(reinterpret_cast<const char*>(data.data()), data.size());
321 file.close();
322
323 LOG_INFO("Wasm", "Wrote dropped ROM to %s (%zu bytes)", path.c_str(), data.size());
324 LoadRomFromWeb(path.c_str());
325 }
326 else if (ext == "pal" || ext == "tpl") {
327 LOG_INFO("Wasm", "Palette drop detected: %s. Feature pending UI integration.", filename.c_str());
328 }
329 },
330 [](const std::string& error) {
331 LOG_ERROR("Wasm", "Drop Handler Error: %s", error.c_str());
332 }
333 );
334
335 // Initialize filesystems asynchronously
336 MountFilesystems();
337}
338
339} // namespace yaze::app::wasm
340
341#endif // __EMSCRIPTEN__
#define LOG_ERROR(category, format,...)
Definition log.h:109
#define LOG_INFO(category, format,...)
Definition log.h:105
EM_JS(void, CallJsAiDriver,(const char *history_json), { if(window.yaze &&window.yaze.ai &&window.yaze.ai.processAgentRequest) { window.yaze.ai.processAgentRequest(UTF8ToString(history_json));} else { console.error("AI Driver not found in window.yaze.ai.processAgentRequest");} })
std::string getEditorState()