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 // Create all required directories
89 var directories = [
90 '/roms', // ROM files (IDBFS - persistent for session restore)
91 '/saves', // Save files (IDBFS - persistent)
92 '/config', // Configuration files (IDBFS - persistent)
93 '/projects', // Project files (IDBFS - persistent)
94 '/prompts', // Agent prompts (IDBFS - persistent)
95 '/recent', // Recent files metadata (IDBFS - persistent)
96 '/temp' // Temporary files (MEMFS - non-persistent)
97 ];
98
99 directories.forEach(function(dir) {
100 try {
101 FS.mkdir(dir);
102 } catch (e) {
103 // Directory may already exist
104 if (e.code !== 'EEXIST') {
105 console.warn("Failed to create directory " + dir + ": " + e);
106 }
107 }
108 });
109
110 // Mount MEMFS for temporary files only
111 FS.mount(MEMFS, {}, '/temp');
112
113 // Check if IDBFS is available (try multiple ways to access it)
114 var idbfs = null;
115 if (typeof IDBFS !== 'undefined') {
116 idbfs = IDBFS;
117 } else if (typeof Module !== 'undefined' && typeof Module.IDBFS !== 'undefined') {
118 idbfs = Module.IDBFS;
119 } else if (typeof FS !== 'undefined' && typeof FS.filesystems !== 'undefined' && FS.filesystems.IDBFS) {
120 idbfs = FS.filesystems.IDBFS;
121 }
122
123 // Persistent directories to mount with IDBFS
124 var persistentDirs = ['/roms', '/saves', '/config', '/projects', '/prompts', '/recent'];
125 var mountedCount = 0;
126 var totalToMount = persistentDirs.length;
127
128 if (idbfs !== null) {
129 persistentDirs.forEach(function(dir) {
130 try {
131 FS.mount(idbfs, {}, dir);
132 mountedCount++;
133 } catch (e) {
134 console.error("Error mounting IDBFS for " + dir + ": " + e);
135 // Fallback to MEMFS for this directory
136 try {
137 FS.mount(MEMFS, {}, dir);
138 } catch (e2) {
139 // May already be mounted
140 }
141 mountedCount++;
142 }
143 });
144
145 // Sync all IDBFS mounts from IndexedDB to memory
146 FS.syncfs(true, function(err) {
147 if (err) {
148 console.error("Failed to sync IDBFS: " + err);
149 } else {
150 console.log("IDBFS synced successfully");
151 }
152 // Signal C++ that we are ready regardless of success/fail
153 Module._SetFileSystemReady();
154 });
155 } else {
156 // Fallback to MEMFS if IDBFS is not available
157 console.warn("IDBFS not available, using MEMFS for all directories (no persistence)");
158 persistentDirs.forEach(function(dir) {
159 try {
160 FS.mount(MEMFS, {}, dir);
161 } catch (e) {
162 // May already be mounted
163 }
164 });
165 Module._SetFileSystemReady();
166 }
167});
168
169EM_JS(void, SetupYazeGlobalApi, (), {
170 if (typeof Module === 'undefined') return;
171
172 // Initialize global API for agents/automation
173 window.yazeApp = {
174 execute: function(cmd) {
175 if (Module.executeCommand) {
176 return Module.executeCommand(cmd);
177 }
178 return "Error: bindings not ready";
179 },
180
181 getState: function() {
182 if (Module.getFullDebugState) {
183 try { return JSON.parse(Module.getFullDebugState()); } catch(e) { return {}; }
184 }
185 return {};
186 },
187
188 getEditorState: function() {
189 if (Module.getEditorState) {
190 try { return JSON.parse(Module.getEditorState()); } catch(e) { return {}; }
191 }
192 return {};
193 },
194
195 loadRom: function(filename) {
196 return this.execute("rom load " + filename);
197 }
198 };
199
200 console.log("[yaze] window.yazeApp API initialized for agents");
201});
202
203namespace yaze::app::wasm {
204
205bool IsFileSystemReady() {
206 return g_filesystem_ready;
207}
208
209void SetRomLoadHandler(std::function<void(std::string)> handler) {
210 std::lock_guard<std::mutex> lock(g_rom_load_mutex);
211 g_rom_load_handler = handler;
212
213 // Flush all pending ROM loads
214 while (!g_pending_rom_loads.empty()) {
215 std::string path = g_pending_rom_loads.front();
216 g_pending_rom_loads.pop();
217 LOG_INFO("Wasm", "Flushing pending ROM load: %s", path.c_str());
218 handler(path);
219 }
220}
221
222void TriggerRomLoad(const std::string& path) {
223 std::lock_guard<std::mutex> lock(g_rom_load_mutex);
224 if (g_rom_load_handler) {
225 g_rom_load_handler(path);
226 } else {
227 LOG_INFO("Wasm", "Queuing ROM load (handler not ready): %s", path.c_str());
228 g_pending_rom_loads.push(path);
229 }
230}
231
232void InitializeWasmPlatform() {
233 // Load WASM configuration from JavaScript
234 app::platform::WasmConfig::Get().LoadFromJavaScript();
235
236 // Setup global API
237 SetupYazeGlobalApi();
238
239 // Initialize drop handler for Drag & Drop support
240 auto& drop_handler = yaze::platform::WasmDropHandler::GetInstance();
241 drop_handler.Initialize("",
242 [](const std::string& filename, const std::vector<uint8_t>& data) {
243 // Determine file type from extension
244 std::string ext = filename.substr(filename.find_last_of(".") + 1);
245 std::transform(ext.begin(), ext.end(), ext.begin(), ::tolower);
246
247 if (ext == "sfc" || ext == "smc" || ext == "zip") {
248 // Write to MEMFS and load
249 std::string path = "/roms/" + filename;
250 std::ofstream file(path, std::ios::binary);
251 file.write(reinterpret_cast<const char*>(data.data()), data.size());
252 file.close();
253
254 LOG_INFO("Wasm", "Wrote dropped ROM to %s (%zu bytes)", path.c_str(), data.size());
255 LoadRomFromWeb(path.c_str());
256 }
257 else if (ext == "pal" || ext == "tpl") {
258 LOG_INFO("Wasm", "Palette drop detected: %s. Feature pending UI integration.", filename.c_str());
259 }
260 },
261 [](const std::string& error) {
262 LOG_ERROR("Wasm", "Drop Handler Error: %s", error.c_str());
263 }
264 );
265
266 // Initialize filesystems asynchronously
267 MountFilesystems();
268}
269
270} // namespace yaze::app::wasm
271
272#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()