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 '/.yaze', // Root for all app data
161 '/.yaze/roms', // ROM files (IDBFS - persistent for session restore)
162 '/.yaze/saves', // Save files (IDBFS - persistent)
163 '/.yaze/config', // Configuration files (IDBFS - persistent)
164 '/.yaze/projects', // Project files (IDBFS - persistent)
165 '/.yaze/prompts', // Agent prompts (IDBFS - persistent)
166 '/.yaze/recent', // Recent files metadata (IDBFS - persistent)
167 '/.yaze/agent', // Agent chat/history (IDBFS - persistent)
168 '/.yaze/proposals', // Agent proposals (IDBFS - persistent)
169 '/.yaze/policies', // Agent policies (IDBFS - persistent)
170 '/.yaze/sandboxes', // ROM sandboxes (IDBFS - persistent)
171 '/.yaze/layouts', // Layout presets (IDBFS - persistent)
172 '/.yaze/workspaces', // Workspace layouts (IDBFS - persistent)
173 '/.yaze/themes', // Theme exports (IDBFS - persistent)
174 '/.yaze/logs', // App logs (IDBFS - persistent)
175 '/.yaze/crash_logs', // Crash reports (IDBFS - persistent)
176 '/.yaze/states', // Save states (IDBFS - persistent)
177 '/.yaze/screenshots', // Screenshots (IDBFS - persistent)
178 '/.yaze/temp' // Temporary files (MEMFS - non-persistent)
179 ];
180
181 directories.forEach(function(dir) {
182 ensureDir(dir);
183 });
184
185 // Mount MEMFS for temporary files only
186 try {
187 FS.mount(MEMFS, {}, '/.yaze/temp');
188 } catch (e) {
189 if (!isErrno(e, 'EBUSY') && !isErrno(e, 'EEXIST')) {
190 console.warn('[WASM] Failed to mount MEMFS for /.yaze/temp: ' + e);
191 }
192 }
193
194 // Check if IDBFS is available (try multiple ways to access it)
195 var idbfs = null;
196 if (typeof IDBFS !== 'undefined') {
197 idbfs = IDBFS;
198 } else if (typeof Module !== 'undefined' && typeof Module.IDBFS !== 'undefined') {
199 idbfs = Module.IDBFS;
200 } else if (typeof FS !== 'undefined' && typeof FS.filesystems !== 'undefined' && FS.filesystems.IDBFS) {
201 idbfs = FS.filesystems.IDBFS;
202 }
203
204 // Persistent directories to mount with IDBFS
205 var persistentDirs = [
206 '/.yaze/roms',
207 '/.yaze/saves',
208 '/.yaze/config',
209 '/.yaze/projects',
210 '/.yaze/prompts',
211 '/.yaze/recent',
212 '/.yaze/agent',
213 '/.yaze/proposals',
214 '/.yaze/policies',
215 '/.yaze/sandboxes',
216 '/.yaze/layouts',
217 '/.yaze/workspaces',
218 '/.yaze/themes',
219 '/.yaze/logs',
220 '/.yaze/crash_logs',
221 '/.yaze/states',
222 '/.yaze/screenshots'
223 ];
224 var mountedCount = 0;
225 var totalToMount = persistentDirs.length;
226
227 if (idbfs !== null) {
228 persistentDirs.forEach(function(dir) {
229 try {
230 FS.mount(idbfs, {}, dir);
231 mountedCount++;
232 } catch (e) {
233 console.error("Error mounting IDBFS for " + dir + ": " + e);
234 // Fallback to MEMFS for this directory
235 try {
236 FS.mount(MEMFS, {}, dir);
237 } catch (e2) {
238 // May already be mounted
239 }
240 mountedCount++;
241 }
242 });
243
244 // Sync all IDBFS mounts from IndexedDB to memory
245 FS.syncfs(true, function(err) {
246 if (err) {
247 console.error("Failed to sync IDBFS: " + err);
248 } else {
249 console.log("IDBFS synced successfully");
250 }
251 // Signal C++ that we are ready regardless of success/fail
252 Module._SetFileSystemReady();
253 });
254 } else {
255 // Fallback to MEMFS if IDBFS is not available
256 console.warn("IDBFS not available, using MEMFS for all directories (no persistence)");
257 persistentDirs.forEach(function(dir) {
258 try {
259 FS.mount(MEMFS, {}, dir);
260 } catch (e) {
261 // May already be mounted
262 }
263 });
264 Module._SetFileSystemReady();
265 }
266});
267
268EM_JS(void, SetupYazeGlobalApi, (), {
269 if (typeof Module === 'undefined') return;
270
271 // Initialize global API for agents/automation
272 window.yazeApp = {
273 execute: function(cmd) {
274 if (Module.executeCommand) {
275 return Module.executeCommand(cmd);
276 }
277 return "Error: bindings not ready";
278 },
279
280 getState: function() {
281 if (Module.getFullDebugState) {
282 try { return JSON.parse(Module.getFullDebugState()); } catch(e) { return {}; }
283 }
284 return {};
285 },
286
287 getEditorState: function() {
288 if (Module.getEditorState) {
289 try { return JSON.parse(Module.getEditorState()); } catch(e) { return {}; }
290 }
291 return {};
292 },
293
294 loadRom: function(filename) {
295 return this.execute("rom load " + filename);
296 }
297 };
298
299 console.log("[yaze] window.yazeApp API initialized for agents");
300});
301
302namespace yaze::app::wasm {
303
304bool IsFileSystemReady() {
305 return g_filesystem_ready;
306}
307
308void SetRomLoadHandler(std::function<void(std::string)> handler) {
309 std::lock_guard<std::mutex> lock(g_rom_load_mutex);
310 g_rom_load_handler = handler;
311
312 // Flush all pending ROM loads
313 while (!g_pending_rom_loads.empty()) {
314 std::string path = g_pending_rom_loads.front();
315 g_pending_rom_loads.pop();
316 LOG_INFO("Wasm", "Flushing pending ROM load: %s", path.c_str());
317 handler(path);
318 }
319}
320
321void TriggerRomLoad(const std::string& path) {
322 std::lock_guard<std::mutex> lock(g_rom_load_mutex);
323 if (g_rom_load_handler) {
324 g_rom_load_handler(path);
325 } else {
326 LOG_INFO("Wasm", "Queuing ROM load (handler not ready): %s", path.c_str());
327 g_pending_rom_loads.push(path);
328 }
329}
330
331void InitializeWasmPlatform() {
332 // Load WASM configuration from JavaScript
333 app::platform::WasmConfig::Get().LoadFromJavaScript();
334
335 // Setup global API
336 SetupYazeGlobalApi();
337
338 // Initialize drop handler for Drag & Drop support
339 auto& drop_handler = yaze::platform::WasmDropHandler::GetInstance();
340 drop_handler.Initialize("",
341 [](const std::string& filename, const std::vector<uint8_t>& data) {
342 // Determine file type from extension
343 std::string ext = filename.substr(filename.find_last_of(".") + 1);
344 std::transform(ext.begin(), ext.end(), ext.begin(), ::tolower);
345
346 if (ext == "sfc" || ext == "smc" || ext == "zip") {
347 // Write to MEMFS and load
348 std::string path = "/.yaze/roms/" + filename;
349 std::ofstream file(path, std::ios::binary);
350 file.write(reinterpret_cast<const char*>(data.data()), data.size());
351 file.close();
352
353 LOG_INFO("Wasm", "Wrote dropped ROM to %s (%zu bytes)", path.c_str(), data.size());
354 LoadRomFromWeb(path.c_str());
355 }
356 else if (ext == "pal" || ext == "tpl") {
357 LOG_INFO("Wasm", "Palette drop detected: %s. Feature pending UI integration.", filename.c_str());
358 }
359 },
360 [](const std::string& error) {
361 LOG_ERROR("Wasm", "Drop Handler Error: %s", error.c_str());
362 }
363 );
364
365 // Initialize filesystems asynchronously
366 MountFilesystems();
367}
368
369} // namespace yaze::app::wasm
370
371#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()