yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
main.cc
Go to the documentation of this file.
1#if __APPLE__
3#endif
4
5#ifdef __EMSCRIPTEN__
6#include <emscripten.h>
9#endif
10
11#define IMGUI_DEFINE_MATH_OPERATORS
12#include <set>
13#include <thread>
14#include <chrono>
15#include <fstream>
16#include <iostream>
17
18#include "absl/debugging/symbolize.h"
19#include "absl/strings/ascii.h"
20#include "absl/strings/str_split.h"
21#include "absl/strings/strip.h"
22#include "app/application.h"
23#include "app/startup_flags.h"
24#include "app/controller.h"
27#if !defined(__EMSCRIPTEN__) && defined(YAZE_HTTP_API_ENABLED)
29#endif
30#include "core/features.h"
31#include "util/crash_handler.h"
32#include "util/flag.h"
33#include "util/log.h"
34#include "util/platform_paths.h"
35#include "yaze.h"
36
37// ============================================================================
38// Global Accessors for WASM Integration
39DEFINE_FLAG(std::string, log_file, "", "Output log file path for debugging.");
40DEFINE_FLAG(std::string, rom_file, "", "ROM file to load on startup.");
41DEFINE_FLAG(bool, debug, false, "Enable debug logging and verbose output.");
42DEFINE_FLAG(std::string, log_level, "info",
43 "Minimum log level: debug, info, warn, error, or fatal.");
44DEFINE_FLAG(bool, log_to_console, false,
45 "Mirror logs to stderr even when writing to a file.");
47 std::string, log_categories, "",
48 "Comma-separated list of log categories to enable or disable. "
49 "Prefix with '-' to disable a category. "
50 "Example: \"Room,DungeonEditor\" (allowlist) or \"-Input,-Graphics\" "
51 "(blocklist).");
52
53// Navigation flags
54DEFINE_FLAG(std::string, editor, "",
55 "The editor to open on startup (e.g., Dungeon, Overworld, Assembly).");
56
57DEFINE_FLAG(std::string, open_panels, "",
58 "Comma-separated list of panel IDs to open (e.g. 'dungeon.room_list,emulator.cpu_debugger')");
59
60// UI visibility flags
61DEFINE_FLAG(std::string, startup_welcome, "auto",
62 "Welcome screen behavior at startup: auto, show, or hide.");
63DEFINE_FLAG(std::string, startup_dashboard, "auto",
64 "Dashboard panel behavior at startup: auto, show, or hide.");
65DEFINE_FLAG(std::string, startup_sidebar, "auto",
66 "Panel sidebar visibility at startup: auto, show, or hide.");
67DEFINE_FLAG(std::string, asset_mode, "auto",
68 "Asset load mode: auto, full, or lazy.");
69
70DEFINE_FLAG(int, room, -1, "Open Dungeon Editor at specific room ID (0-295).");
71DEFINE_FLAG(int, map, -1, "Open Overworld Editor at specific map ID (0-159).");
72
73// AI Agent API flags
74DEFINE_FLAG(bool, enable_api, false, "Enable the AI Agent API server.");
75DEFINE_FLAG(int, api_port, 8080, "Port for the AI Agent API server.");
76
77DEFINE_FLAG(bool, headless, false, "Run in headless mode without a GUI window.");
78DEFINE_FLAG(bool, service, false, "Run in service mode (GUI backend initialized but window hidden).");
79DEFINE_FLAG(bool, server, false, "Run in server mode (implies --enable_api, --enable_test_harness). Defaults to --headless unless --service or --no-headless is specified.");
80
81#ifdef YAZE_WITH_GRPC
82// gRPC test harness flags
83DEFINE_FLAG(bool, enable_test_harness, false,
84 "Start gRPC test harness server for automated GUI testing.");
85
86DEFINE_FLAG(int, test_harness_port, 50052,
87 "Port for Unified gRPC server (default: 50052).");
88DEFINE_FLAG(std::string, backend, "internal",
89 "Emulator backend for gRPC service: 'internal' or 'mesen'.");
90#endif
91
92// Symbol Export Flags
93DEFINE_FLAG(std::string, export_symbols, "", "Export symbols to file (requires --rom_file).");
94DEFINE_FLAG(std::string, symbol_format, "mesen", "Format for symbol export: mesen, wla, asar, bsnes.");
95DEFINE_FLAG(std::string, load_symbols, "", "Load symbol file (.mlb, .sym, .asm) on startup.");
96DEFINE_FLAG(std::string, load_asar_symbols, "", "Load Asar symbols from directory on startup.");
97DEFINE_FLAG(bool, export_symbols_fast, false,
98 "Export symbols without initializing UI. Requires --load_symbols or --load_asar_symbols.");
99
100// ============================================================================
101// Global Accessors for WASM Integration
102// These are used by yaze_debug_inspector.cc and wasm_terminal_bridge.cc
103// ============================================================================
104namespace yaze {
105namespace emu {
106class Emulator;
107}
108namespace editor {
109class EditorManager;
110}
111}
112
113namespace yaze::app {
114
117 if (ctrl && ctrl->editor_manager()) {
118 return &ctrl->editor_manager()->emulator();
119 }
120 return nullptr;
121}
122
125 if (ctrl) {
126 return ctrl->editor_manager();
127 }
128 return nullptr;
129}
130
131} // namespace yaze::app
132
133namespace {
134
135yaze::util::LogLevel ParseLogLevelFlag(const std::string& raw_level,
136 bool debug_flag) {
137 if (debug_flag) {
139 }
140
141 const std::string lower = absl::AsciiStrToLower(raw_level);
142 if (lower == "debug") {
144 }
145 if (lower == "warn" || lower == "warning") {
147 }
148 if (lower == "error") {
150 }
151 if (lower == "fatal") {
153 }
155}
156
157std::set<std::string> ParseLogCategories(const std::string& raw) {
158 std::set<std::string> categories;
159 for (absl::string_view token :
160 absl::StrSplit(raw, ',', absl::SkipWhitespace())) {
161 if (!token.empty()) {
162 categories.insert(std::string(absl::StripAsciiWhitespace(token)));
163 }
164 }
165 return categories;
166}
167
169 switch (level) {
171 return "debug";
173 return "info";
175 return "warn";
177 return "error";
179 return "fatal";
180 }
181 return "info";
182}
183
184std::vector<std::string> ParseCommaList(const std::string& raw) {
185 std::vector<std::string> tokens;
186 for (absl::string_view token :
187 absl::StrSplit(raw, ',', absl::SkipWhitespace())) {
188 if (!token.empty()) {
189 tokens.emplace_back(absl::StripAsciiWhitespace(token));
190 }
191 }
192 return tokens;
193}
194
195} // namespace
196
200
201// Helper Functions
202
209
210yaze::util::LogLevel ResolveLogConfig(bool debug_flag, bool log_to_console_flag) {
211 yaze::util::LogLevel log_level =
212 ParseLogLevelFlag(FLAGS_log_level->Get(), debug_flag);
213 std::set<std::string> log_categories =
214 ParseLogCategories(FLAGS_log_categories->Get());
215
216 std::string log_path = FLAGS_log_file->Get();
217 if (log_path.empty()) {
219 if (logs_dir.ok()) {
220 log_path = (*logs_dir / "yaze.log").string();
221 }
222 }
223
224 yaze::util::LogManager::instance().configure(log_level, log_path,
225 log_categories);
226
227 if (debug_flag || log_to_console_flag) {
229 }
230 if (debug_flag) {
231 LOG_INFO("Main", "🚀 YAZE started in debug mode");
232 }
233 LOG_INFO("Main",
234 "Logging configured (level=%s, file=%s, console=%s, categories=%zu)",
235 LogLevelToString(log_level),
236 log_path.empty() ? "<stderr>" : log_path.c_str(),
237 (debug_flag || log_to_console_flag) ? "on" : "off",
238 log_categories.size());
239 return log_level;
240}
241
242yaze::AppConfig BuildAppConfig(yaze::util::LogLevel log_level, std::string log_path) {
243 yaze::AppConfig config;
244
245 bool server_mode = FLAGS_server->Get();
246 bool service_mode = FLAGS_service->Get();
247
248 config.headless = FLAGS_headless->Get() || (server_mode && !service_mode);
249 config.service_mode = service_mode;
250 config.enable_api = FLAGS_enable_api->Get() || server_mode || service_mode;
251
252#ifdef YAZE_WITH_GRPC
253 config.enable_test_harness = FLAGS_enable_test_harness->Get() || server_mode || service_mode;
254 config.test_harness_port = FLAGS_test_harness_port->Get();
255 config.backend = FLAGS_backend->Get();
256#endif
257
258 config.rom_file = FLAGS_rom_file->Get();
259 config.log_file = log_path;
260 config.debug = (log_level == yaze::util::LogLevel::YAZE_DEBUG);
261 config.log_categories = FLAGS_log_categories->Get();
262 config.startup_editor = FLAGS_editor->Get();
263 config.jump_to_room = FLAGS_room->Get();
264 config.jump_to_map = FLAGS_map->Get();
265 config.api_port = FLAGS_api_port->Get();
266
267 config.welcome_mode =
268 yaze::StartupVisibilityFromString(FLAGS_startup_welcome->Get());
269 config.dashboard_mode =
270 yaze::StartupVisibilityFromString(FLAGS_startup_dashboard->Get());
271 config.sidebar_mode =
272 yaze::StartupVisibilityFromString(FLAGS_startup_sidebar->Get());
273 config.asset_load_mode =
274 yaze::AssetLoadModeFromString(FLAGS_asset_mode->Get());
276 config.asset_load_mode = (config.headless || server_mode || service_mode)
279 }
280
281 if (!FLAGS_open_panels->Get().empty()) {
282 config.open_panels = ParseCommaList(FLAGS_open_panels->Get());
283 }
284 return config;
285}
286
288 if (!FLAGS_export_symbols->Get().empty() && FLAGS_export_symbols_fast->Get()) {
290 bool loaded = false;
291
292 if (!FLAGS_load_symbols->Get().empty()) {
293 LOG_INFO("Main", "Loading symbols from %s...", FLAGS_load_symbols->Get().c_str());
294 auto status = symbols.LoadSymbolFile(FLAGS_load_symbols->Get());
295 if (!status.ok()) {
296 LOG_ERROR("Main", "Failed to load symbols: %s", status.ToString().c_str());
297 } else {
298 loaded = true;
299 }
300 }
301
302 if (!FLAGS_load_asar_symbols->Get().empty()) {
303 LOG_INFO("Main", "Loading Asar symbols from %s...", FLAGS_load_asar_symbols->Get().c_str());
304 auto status = symbols.LoadAsarAsmDirectory(FLAGS_load_asar_symbols->Get());
305 if (!status.ok()) {
306 LOG_ERROR("Main", "Failed to load Asar symbols: %s", status.ToString().c_str());
307 } else {
308 loaded = true;
309 }
310 }
311
312 if (!loaded) {
313 LOG_ERROR("Main", "No symbols loaded. Use --load_symbols or --load_asar_symbols.");
314 return false;
315 }
316
317 LOG_INFO("Main", "Exporting symbols to %s...", FLAGS_export_symbols->Get().c_str());
319 std::string format_str = absl::AsciiStrToLower(FLAGS_symbol_format->Get());
320 if (format_str == "asar") format = yaze::emu::debug::SymbolFormat::kAsar;
321 else if (format_str == "wla") format = yaze::emu::debug::SymbolFormat::kWlaDx;
322 else if (format_str == "bsnes") format = yaze::emu::debug::SymbolFormat::kBsnes;
323
324 auto export_or = symbols.ExportSymbols(format);
325 if (export_or.ok()) {
326 std::ofstream out(FLAGS_export_symbols->Get());
327 if (out.is_open()) {
328 out << *export_or;
329 LOG_INFO("Main", "Symbols exported successfully (fast path)");
330 return true;
331 }
332 LOG_ERROR("Main", "Failed to open output file: %s", FLAGS_export_symbols->Get().c_str());
333 return false;
334 }
335 LOG_ERROR("Main", "Failed to export symbols: %s", export_or.status().ToString().c_str());
336 return false;
337 }
338 return false;
339}
340
341#if !defined(__EMSCRIPTEN__) && defined(YAZE_HTTP_API_ENABLED)
342std::unique_ptr<yaze::cli::api::HttpServer> SetupApiServer(
343 const yaze::AppConfig& config) {
344 if (!config.enable_api) {
345 return nullptr;
346 }
347
348 auto api_server = std::make_unique<yaze::cli::api::HttpServer>();
349
350 // Wire up symbol provider source
351 api_server->SetSymbolProviderSource([]() -> yaze::emu::debug::SymbolProvider* {
352 auto* manager = yaze::app::GetGlobalEditorManager();
353 if (manager) {
354 return &manager->emulator().symbol_provider();
355 }
356 return nullptr;
357 });
358
359 // Window control endpoints (service mode)
360 api_server->SetWindowActions(
361 []() -> bool {
362 auto* controller = yaze::Application::Instance().GetController();
363 return controller ? (controller->ShowWindow(), true) : false;
364 },
365 []() -> bool {
366 auto* controller = yaze::Application::Instance().GetController();
367 return controller ? (controller->HideWindow(), false) : false; // HideWindow returns void usually
368 });
369
370 auto status = api_server->Start(config.api_port);
371 if (!status.ok()) {
372 LOG_ERROR("Main", "Failed to start API server: %s", std::string(status.message()).c_str());
373 return nullptr;
374 }
375
376 LOG_INFO("Main", "API Server started on port %d", config.api_port);
377 return api_server;
378}
379#endif
380
381// Main Entry Point
382
383int main(int argc, char** argv) {
384 absl::InitializeSymbolizer(argv[0]);
385
387
389 RETURN_IF_EXCEPTION(parser.Parse(argc, argv));
390
391 // Set up logging
392 const bool debug_flag = FLAGS_debug->Get();
393 const bool log_to_console_flag = FLAGS_log_to_console->Get();
394 auto log_level = ResolveLogConfig(debug_flag, log_to_console_flag);
395 std::string log_path = FLAGS_log_file->Get();
396 if (log_path.empty()) {
398 if (logs_dir.ok()) {
399 log_path = (*logs_dir / "yaze.log").string();
400 }
401 }
402
403 // Build AppConfig from flags
404 auto config = BuildAppConfig(log_level, log_path);
405
406 // Fast symbol export path (no UI initialization)
408 return EXIT_SUCCESS;
409 }
410#if !defined(__EMSCRIPTEN__)
411 // If fast export failed but was requested, and we are not emscripten, we should probably exit or continue?
412 // The helper returns false if not requested OR if failed.
413 // If requested and failed, it logs errors.
414 // To match original logic:
415 if (!FLAGS_export_symbols->Get().empty() && FLAGS_export_symbols_fast->Get()) {
416 return EXIT_FAILURE;
417 }
418#endif
419
420#ifdef __APPLE__
421 if (!config.headless) {
422 return yaze_run_cocoa_app_delegate(config);
423 }
424#endif
425
426#if defined(_WIN32) && !defined(__EMSCRIPTEN__)
427 SDL_SetMainReady();
428#endif
429
430#ifdef __EMSCRIPTEN__
431 yaze::app::wasm::InitializeWasmPlatform();
432
433 // Store config for deferred initialization
434 static yaze::AppConfig s_wasm_config = config;
435 static bool s_wasm_initialized = false;
436
437 // Main loop that handles deferred initialization for filesystem readiness
438 auto WasmMainLoop = []() {
439 // Wait for filesystem to be ready before initializing application
440 if (!s_wasm_initialized) {
441 if (yaze::app::wasm::IsFileSystemReady()) {
442 LOG_INFO("Main", "Filesystem ready, initializing application...");
444 s_wasm_initialized = true;
445 } else {
446 // Still waiting for filesystem - do nothing this frame
447 return;
448 }
449 }
450
451 // Normal tick once initialized
452 TickFrame();
453 };
454
455 // Use 0 for frame rate to enable requestAnimationFrame (better performance)
456 // The third parameter (1) simulates infinite loop
457 emscripten_set_main_loop(WasmMainLoop, 0, 1);
458#else
459 // Desktop Main Loop (Linux/Windows)
460
461 // API Server
462#if defined(YAZE_HTTP_API_ENABLED)
463 std::unique_ptr<yaze::cli::api::HttpServer> api_server;
464#endif
465#if defined(YAZE_HTTP_API_ENABLED)
466 api_server = SetupApiServer(config);
467#else
468 if (config.enable_api) {
469 LOG_WARN("Main",
470 "HTTP API requested but not enabled at build time "
471 "(set -DYAZE_ENABLE_HTTP_API=ON and rebuild).");
472 }
473#endif
474
476
477 // Handle symbol loading if requested
479 if (ctrl && ctrl->editor_manager()) {
480 auto& symbols = ctrl->editor_manager()->emulator().symbol_provider();
481
482 if (!FLAGS_load_symbols->Get().empty()) {
483 LOG_INFO("Main", "Loading symbols from %s...", FLAGS_load_symbols->Get().c_str());
484 auto status = symbols.LoadSymbolFile(FLAGS_load_symbols->Get());
485 if (!status.ok()) {
486 LOG_ERROR("Main", "Failed to load symbols: %s", status.ToString().c_str());
487 }
488 }
489
490 if (!FLAGS_load_asar_symbols->Get().empty()) {
491 LOG_INFO("Main", "Loading Asar symbols from %s...", FLAGS_load_asar_symbols->Get().c_str());
492 auto status = symbols.LoadAsarAsmDirectory(FLAGS_load_asar_symbols->Get());
493 if (!status.ok()) {
494 LOG_ERROR("Main", "Failed to load Asar symbols: %s", status.ToString().c_str());
495 }
496 }
497 }
498
499 // Handle symbol export if requested (GUI mode)
500 if (!FLAGS_export_symbols->Get().empty()) {
501 LOG_INFO("Main", "Exporting symbols to %s...", FLAGS_export_symbols->Get().c_str());
502
504 if (ctrl && ctrl->editor_manager()) {
505 auto* manager = ctrl->editor_manager();
506
507 // Attempt to find symbols from the current session
508 // For now, we'll assume they are in the emulator's symbol provider
509 auto& symbols = manager->emulator().symbol_provider();
510
512 std::string format_str = absl::AsciiStrToLower(FLAGS_symbol_format->Get());
513 if (format_str == "asar") format = yaze::emu::debug::SymbolFormat::kAsar;
514 else if (format_str == "wla") format = yaze::emu::debug::SymbolFormat::kWlaDx;
515 else if (format_str == "bsnes") format = yaze::emu::debug::SymbolFormat::kBsnes;
516
517 auto export_or = symbols.ExportSymbols(format);
518 if (export_or.ok()) {
519 std::ofstream out(FLAGS_export_symbols->Get());
520 if (out.is_open()) {
521 out << *export_or;
522 LOG_INFO("Main", "Symbols exported successfully");
523 } else {
524 LOG_ERROR("Main", "Failed to open output file: %s", FLAGS_export_symbols->Get().c_str());
525 }
526 } else {
527 LOG_ERROR("Main", "Failed to export symbols: %s", export_or.status().ToString().c_str());
528 }
529 }
530
532 return EXIT_SUCCESS;
533 }
534
535 if (config.headless) {
536 LOG_INFO("Main", "Running in HEADLESS mode (no GUI window)");
537 // Optimized headless loop
538 while (yaze::Application::Instance().GetController()->IsActive()) {
540 // Sleep to reduce CPU usage in headless mode
541 // 60Hz = ~16ms, but we can sleep longer if just serving API/gRPC
542 std::this_thread::sleep_for(std::chrono::milliseconds(16));
543 }
544 } else {
545 // Normal GUI loop (also used for Service Mode with hidden window)
546 if (config.service_mode) {
547 LOG_INFO("Main", "Running in SERVICE mode (Hidden GUI window)");
548 }
549 while (yaze::Application::Instance().GetController()->IsActive()) {
550 TickFrame();
551 }
552 }
553
555
556#if defined(YAZE_HTTP_API_ENABLED)
557 if (api_server) {
558 api_server->Stop();
559 }
560#endif
561
562#endif // __EMSCRIPTEN__
563
564 return EXIT_SUCCESS;
565}
void TickFrame()
Definition main.cc:197
bool RunSymbolExportFastPath()
Definition main.cc:287
yaze::AppConfig BuildAppConfig(yaze::util::LogLevel log_level, std::string log_path)
Definition main.cc:242
int main(int argc, char **argv)
Definition main.cc:383
yaze::util::LogLevel ResolveLogConfig(bool debug_flag, bool log_to_console_flag)
Definition main.cc:210
void SetupCrashHandling()
Definition main.cc:203
Controller * GetController()
Definition application.h:81
static Application & Instance()
void Initialize(const AppConfig &config)
editor::EditorManager * editor_manager()
Definition controller.h:69
static Flags & get()
Definition features.h:118
The EditorManager controls the main editor window and manages the various editor classes.
auto emulator() -> emu::Emulator &
A class for emulating and debugging SNES games.
Definition emulator.h:40
Provider for symbol (label) resolution in disassembly.
absl::Status LoadAsarAsmDirectory(const std::string &directory_path)
Load symbols from a directory of ASM files.
absl::StatusOr< std::string > ExportSymbols(SymbolFormat format) const
Export all symbols to a string in the specified format.
absl::Status LoadSymbolFile(const std::string &path, SymbolFormat format=SymbolFormat::kAuto)
Load symbols from a .sym file (various formats)
static void CleanupOldLogs(int keep_count=5)
Clean up old crash logs, keeping only the most recent N logs.
static void Initialize(const std::string &version)
Initialize the crash handler for the application.
void Parse(int argc, char **argv)
Definition flag.h:139
static LogManager & instance()
Definition log.cc:32
void configure(LogLevel level, const std::string &file_path, const std::set< std::string > &categories)
Configures the logging system.
Definition log.cc:46
static absl::StatusOr< std::filesystem::path > GetAppDataSubdirectory(const std::string &subdir)
Get a subdirectory within the app data folder.
#define DEFINE_FLAG(type, name, default_val, help_text)
Definition flag.h:126
#define YAZE_VERSION_STRING
#define LOG_ERROR(category, format,...)
Definition log.h:109
#define LOG_WARN(category, format,...)
Definition log.h:107
#define LOG_INFO(category, format,...)
Definition log.h:105
#define RETURN_IF_EXCEPTION(expression)
Definition macro.h:104
const char * LogLevelToString(yaze::util::LogLevel level)
Definition main.cc:168
std::set< std::string > ParseLogCategories(const std::string &raw)
Definition main.cc:157
yaze::util::LogLevel ParseLogLevelFlag(const std::string &raw_level, bool debug_flag)
Definition main.cc:135
std::vector< std::string > ParseCommaList(const std::string &raw)
Definition main.cc:184
yaze::editor::EditorManager * GetGlobalEditorManager()
Definition main.cc:123
yaze::emu::Emulator * GetGlobalEmulator()
Definition main.cc:115
SymbolFormat
Supported symbol file formats.
FlagRegistry * global_flag_registry()
Definition flag.h:119
LogLevel
Defines the severity levels for log messages. This allows for filtering messages based on their impor...
Definition log.h:23
AssetLoadMode AssetLoadModeFromString(absl::string_view value)
StartupVisibility StartupVisibilityFromString(absl::string_view value)
Configuration options for the application startup.
Definition application.h:26
std::string rom_file
Definition application.h:28
AssetLoadMode asset_load_mode
Definition application.h:35
std::string startup_editor
Definition application.h:38
std::string log_categories
Definition application.h:31
StartupVisibility welcome_mode
Definition application.h:32
std::vector< std::string > open_panels
Definition application.h:40
StartupVisibility sidebar_mode
Definition application.h:34
bool enable_test_harness
Definition application.h:51
std::string log_file
Definition application.h:29
std::string backend
Definition application.h:54
StartupVisibility dashboard_mode
Definition application.h:33
Public YAZE API umbrella header.