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 <algorithm>
13#include <chrono>
14#include <fstream>
15#include <iostream>
16#include <set>
17#include <thread>
18
19#include "absl/debugging/symbolize.h"
20#include "absl/strings/ascii.h"
21#include "absl/strings/str_split.h"
22#include "absl/strings/strip.h"
23#include "app/application.h"
24#include "app/controller.h"
27#include "app/startup_flags.h"
28#if !defined(__EMSCRIPTEN__) && defined(YAZE_HTTP_API_ENABLED)
30#endif
31#include "core/features.h"
32#include "util/crash_handler.h"
33#include "util/flag.h"
34#include "util/log.h"
35#include "util/platform_paths.h"
36#include "yaze.h"
37
38// ============================================================================
39// Global Accessors for WASM Integration
40DEFINE_FLAG(std::string, log_file, "", "Output log file path for debugging.");
41DEFINE_FLAG(std::string, rom_file, "", "ROM file to load on startup.");
42DEFINE_FLAG(bool, debug, false, "Enable debug logging and verbose output.");
43DEFINE_FLAG(std::string, log_level, "info",
44 "Minimum log level: debug, info, warn, error, or fatal.");
45DEFINE_FLAG(bool, log_to_console, false,
46 "Mirror logs to stderr even when writing to a file.");
48 std::string, log_categories, "",
49 "Comma-separated list of log categories to enable or disable. "
50 "Prefix with '-' to disable a category. "
51 "Example: \"Room,DungeonEditor\" (allowlist) or \"-Input,-Graphics\" "
52 "(blocklist).");
53
54// Navigation flags
56 std::string, editor, "",
57 "The editor to open on startup (e.g., Dungeon, Overworld, Assembly).");
58
59DEFINE_FLAG(std::string, open_panels, "",
60 "Comma-separated list of panel IDs to open (e.g. "
61 "'dungeon.room_list,emulator.cpu_debugger')");
62
63// UI visibility flags
64DEFINE_FLAG(std::string, startup_welcome, "auto",
65 "Welcome screen behavior at startup: auto, show, or hide.");
66DEFINE_FLAG(std::string, startup_dashboard, "auto",
67 "Dashboard panel behavior at startup: auto, show, or hide.");
68DEFINE_FLAG(std::string, startup_sidebar, "auto",
69 "Panel sidebar visibility at startup: auto, show, or hide.");
70DEFINE_FLAG(std::string, asset_mode, "auto",
71 "Asset load mode: auto, full, or lazy.");
72
73DEFINE_FLAG(int, room, -1, "Open Dungeon Editor at specific room ID (0-295).");
74DEFINE_FLAG(int, map, -1, "Open Overworld Editor at specific map ID (0-159).");
75
76// AI Agent API flags
77DEFINE_FLAG(bool, enable_api, false, "Enable the AI Agent API server.");
78DEFINE_FLAG(int, api_port, 8080, "Port for the AI Agent API server.");
79
80DEFINE_FLAG(bool, headless, false,
81 "Run in headless mode without a GUI window.");
82DEFINE_FLAG(bool, service, false,
83 "Run in service mode (GUI backend initialized but window hidden).");
85 bool, server, false,
86 "Run in server mode (implies --enable_api, --enable_test_harness). "
87 "Defaults to --headless unless --service or --no-headless is specified.");
88
89#ifdef YAZE_WITH_GRPC
90// gRPC test harness flags
91DEFINE_FLAG(bool, enable_test_harness, false,
92 "Start gRPC test harness server for automated GUI testing.");
93
94DEFINE_FLAG(int, test_harness_port, 50052,
95 "Port for Unified gRPC server (default: 50052).");
96DEFINE_FLAG(std::string, backend, "internal",
97 "Emulator backend for gRPC service: 'internal' or 'mesen'.");
98#endif
99
100// Symbol Export Flags
101DEFINE_FLAG(std::string, export_symbols, "",
102 "Export symbols to file (requires --rom_file).");
103DEFINE_FLAG(std::string, symbol_format, "mesen",
104 "Format for symbol export: mesen, wla, asar, bsnes.");
105DEFINE_FLAG(std::string, load_symbols, "",
106 "Load symbol file (.mlb, .sym, .asm) on startup.");
107DEFINE_FLAG(std::string, load_asar_symbols, "",
108 "Load Asar symbols from directory on startup.");
109DEFINE_FLAG(bool, export_symbols_fast, false,
110 "Export symbols without initializing UI. Requires --load_symbols "
111 "or --load_asar_symbols.");
112
113// ============================================================================
114// Global Accessors for WASM Integration
115// These are used by yaze_debug_inspector.cc and wasm_terminal_bridge.cc
116// ============================================================================
117namespace yaze {
118namespace emu {
119class Emulator;
120}
121namespace editor {
122class EditorManager;
123}
124} // namespace yaze
125
126namespace yaze::app {
127
130 if (ctrl && ctrl->editor_manager()) {
131 return &ctrl->editor_manager()->emulator();
132 }
133 return nullptr;
134}
135
138 if (ctrl) {
139 return ctrl->editor_manager();
140 }
141 return nullptr;
142}
143
144} // namespace yaze::app
145
146namespace {
147
148yaze::util::LogLevel ParseLogLevelFlag(const std::string& raw_level,
149 bool debug_flag) {
150 if (debug_flag) {
152 }
153
154 const std::string lower = absl::AsciiStrToLower(raw_level);
155 if (lower == "debug") {
157 }
158 if (lower == "warn" || lower == "warning") {
160 }
161 if (lower == "error") {
163 }
164 if (lower == "fatal") {
166 }
168}
169
170std::set<std::string> ParseLogCategories(const std::string& raw) {
171 std::set<std::string> categories;
172 for (absl::string_view token :
173 absl::StrSplit(raw, ',', absl::SkipWhitespace())) {
174 if (!token.empty()) {
175 categories.insert(std::string(absl::StripAsciiWhitespace(token)));
176 }
177 }
178 return categories;
179}
180
182 switch (level) {
184 return "debug";
186 return "info";
188 return "warn";
190 return "error";
192 return "fatal";
193 }
194 return "info";
195}
196
197std::vector<std::string> ParseCommaList(const std::string& raw) {
198 std::vector<std::string> tokens;
199 for (absl::string_view token :
200 absl::StrSplit(raw, ',', absl::SkipWhitespace())) {
201 if (!token.empty()) {
202 tokens.emplace_back(absl::StripAsciiWhitespace(token));
203 }
204 }
205 return tokens;
206}
207
208// Handles --help/-h and --version before normal flag parsing so they work
209// regardless of any other arguments. Returns true if handled (caller exits).
210bool HandleHelpOrVersion(int argc, char** argv) {
211 for (int i = 1; i < argc; ++i) {
212 const std::string arg = argv[i];
213 if (arg == "--version") {
214 std::cout << "yaze " << YAZE_VERSION_STRING << "\n";
215 return true;
216 }
217 if (arg == "--help" || arg == "-h") {
218 std::cout << argv[0] << " (yaze " << YAZE_VERSION_STRING << ")\n"
219 << "Usage: " << argv[0] << " [flags]\n\nFlags:\n";
221 std::sort(flags.begin(), flags.end(),
222 [](const yaze::util::IFlag* a, const yaze::util::IFlag* b) {
223 return a->name() < b->name();
224 });
225 for (const auto* flag : flags) {
226 std::cout << " " << flag->name() << "\n " << flag->help() << "\n";
227 }
228 return true;
229 }
230 }
231 return false;
232}
233
234} // namespace
235
239
240// Helper Functions
241
248
250 bool log_to_console_flag) {
251 yaze::util::LogLevel log_level =
252 ParseLogLevelFlag(FLAGS_log_level->Get(), debug_flag);
253 std::set<std::string> log_categories =
254 ParseLogCategories(FLAGS_log_categories->Get());
255
256 std::string log_path = FLAGS_log_file->Get();
257 if (log_path.empty()) {
259 if (logs_dir.ok()) {
260 log_path = (*logs_dir / "yaze.log").string();
261 }
262 }
263
264 yaze::util::LogManager::instance().configure(log_level, log_path,
265 log_categories);
266
267 if (debug_flag || log_to_console_flag) {
269 }
270 if (debug_flag) {
271 LOG_INFO("Main", "🚀 YAZE started in debug mode");
272 }
273 LOG_INFO("Main",
274 "Logging configured (level=%s, file=%s, console=%s, categories=%zu)",
275 LogLevelToString(log_level),
276 log_path.empty() ? "<stderr>" : log_path.c_str(),
277 (debug_flag || log_to_console_flag) ? "on" : "off",
278 log_categories.size());
279 return log_level;
280}
281
283 std::string log_path) {
284 yaze::AppConfig config;
285
286 bool server_mode = FLAGS_server->Get();
287 bool service_mode = FLAGS_service->Get();
288
289 config.headless = FLAGS_headless->Get() || (server_mode && !service_mode);
290 config.service_mode = service_mode;
291 config.enable_api = FLAGS_enable_api->Get() || server_mode || service_mode;
292
293#ifdef YAZE_WITH_GRPC
294 config.enable_test_harness =
295 FLAGS_enable_test_harness->Get() || server_mode || service_mode;
296 config.test_harness_port = FLAGS_test_harness_port->Get();
297 config.backend = FLAGS_backend->Get();
298#endif
299
300 config.rom_file = FLAGS_rom_file->Get();
301 config.log_file = log_path;
302 config.debug = (log_level == yaze::util::LogLevel::YAZE_DEBUG);
303 config.log_categories = FLAGS_log_categories->Get();
304 config.startup_editor = FLAGS_editor->Get();
305 config.jump_to_room = FLAGS_room->Get();
306 config.jump_to_map = FLAGS_map->Get();
307 config.api_port = FLAGS_api_port->Get();
308
309 config.welcome_mode =
310 yaze::StartupVisibilityFromString(FLAGS_startup_welcome->Get());
311 config.dashboard_mode =
312 yaze::StartupVisibilityFromString(FLAGS_startup_dashboard->Get());
313 config.sidebar_mode =
314 yaze::StartupVisibilityFromString(FLAGS_startup_sidebar->Get());
315 config.asset_load_mode =
316 yaze::AssetLoadModeFromString(FLAGS_asset_mode->Get());
318 config.asset_load_mode = (config.headless || server_mode || service_mode)
321 }
322
323 if (!FLAGS_open_panels->Get().empty()) {
324 config.open_panels = ParseCommaList(FLAGS_open_panels->Get());
325 }
326 return config;
327}
328
330 if (!FLAGS_export_symbols->Get().empty() &&
331 FLAGS_export_symbols_fast->Get()) {
333 bool loaded = false;
334
335 if (!FLAGS_load_symbols->Get().empty()) {
336 LOG_INFO("Main", "Loading symbols from %s...",
337 FLAGS_load_symbols->Get().c_str());
338 auto status = symbols.LoadSymbolFile(FLAGS_load_symbols->Get());
339 if (!status.ok()) {
340 LOG_ERROR("Main", "Failed to load symbols: %s",
341 status.ToString().c_str());
342 } else {
343 loaded = true;
344 }
345 }
346
347 if (!FLAGS_load_asar_symbols->Get().empty()) {
348 LOG_INFO("Main", "Loading Asar symbols from %s...",
349 FLAGS_load_asar_symbols->Get().c_str());
350 auto status =
351 symbols.LoadAsarAsmDirectory(FLAGS_load_asar_symbols->Get());
352 if (!status.ok()) {
353 LOG_ERROR("Main", "Failed to load Asar symbols: %s",
354 status.ToString().c_str());
355 } else {
356 loaded = true;
357 }
358 }
359
360 if (!loaded) {
361 LOG_ERROR(
362 "Main",
363 "No symbols loaded. Use --load_symbols or --load_asar_symbols.");
364 return false;
365 }
366
367 LOG_INFO("Main", "Exporting symbols to %s...",
368 FLAGS_export_symbols->Get().c_str());
371 std::string format_str = absl::AsciiStrToLower(FLAGS_symbol_format->Get());
372 if (format_str == "asar")
374 else if (format_str == "wla")
376 else if (format_str == "bsnes")
378
379 auto export_or = symbols.ExportSymbols(format);
380 if (export_or.ok()) {
381 std::ofstream out(FLAGS_export_symbols->Get());
382 if (out.is_open()) {
383 out << *export_or;
384 LOG_INFO("Main", "Symbols exported successfully (fast path)");
385 return true;
386 }
387 LOG_ERROR("Main", "Failed to open output file: %s",
388 FLAGS_export_symbols->Get().c_str());
389 return false;
390 }
391 LOG_ERROR("Main", "Failed to export symbols: %s",
392 export_or.status().ToString().c_str());
393 return false;
394 }
395 return false;
396}
397
398#if !defined(__EMSCRIPTEN__) && defined(YAZE_HTTP_API_ENABLED)
399std::unique_ptr<yaze::cli::api::HttpServer> SetupApiServer(
400 const yaze::AppConfig& config) {
401 if (!config.enable_api) {
402 return nullptr;
403 }
404
405 auto api_server = std::make_unique<yaze::cli::api::HttpServer>();
406
407 // Wire up symbol provider source
408 api_server->SetSymbolProviderSource(
410 auto* manager = yaze::app::GetGlobalEditorManager();
411 if (manager) {
412 return &manager->emulator().symbol_provider();
413 }
414 return nullptr;
415 });
416
417 // Window control endpoints (service mode)
418 api_server->SetWindowActions(
419 []() -> bool {
420 auto* controller = yaze::Application::Instance().GetController();
421 return controller ? (controller->ShowWindow(), true) : false;
422 },
423 []() -> bool {
424 auto* controller = yaze::Application::Instance().GetController();
425 return controller ? (controller->HideWindow(), false)
426 : false; // HideWindow returns void usually
427 });
428
429 auto status = api_server->Start(config.api_port);
430 if (!status.ok()) {
431 LOG_ERROR("Main", "Failed to start API server: %s",
432 std::string(status.message()).c_str());
433 return nullptr;
434 }
435
436 LOG_INFO("Main", "API Server started on port %d", config.api_port);
437 return api_server;
438}
439#endif
440
441// Main Entry Point
442
443int main(int argc, char** argv) {
444 absl::InitializeSymbolizer(argv[0]);
445
446 if (HandleHelpOrVersion(argc, argv)) {
447 return EXIT_SUCCESS;
448 }
449
451
453 RETURN_IF_EXCEPTION(parser.Parse(argc, argv));
454
455 // Set up logging
456 const bool debug_flag = FLAGS_debug->Get();
457 const bool log_to_console_flag = FLAGS_log_to_console->Get();
458 auto log_level = ResolveLogConfig(debug_flag, log_to_console_flag);
459 std::string log_path = FLAGS_log_file->Get();
460 if (log_path.empty()) {
462 if (logs_dir.ok()) {
463 log_path = (*logs_dir / "yaze.log").string();
464 }
465 }
466
467 // Build AppConfig from flags
468 auto config = BuildAppConfig(log_level, log_path);
469
470 // Fast symbol export path (no UI initialization)
472 return EXIT_SUCCESS;
473 }
474#if !defined(__EMSCRIPTEN__)
475 // If fast export failed but was requested, and we are not emscripten, we should probably exit or continue?
476 // The helper returns false if not requested OR if failed.
477 // If requested and failed, it logs errors.
478 // To match original logic:
479 if (!FLAGS_export_symbols->Get().empty() &&
480 FLAGS_export_symbols_fast->Get()) {
481 return EXIT_FAILURE;
482 }
483#endif
484
485#ifdef __APPLE__
486 if (!config.headless) {
487 return yaze_run_cocoa_app_delegate(config);
488 }
489#endif
490
491#if defined(_WIN32) && !defined(__EMSCRIPTEN__)
492 SDL_SetMainReady();
493#endif
494
495#ifdef __EMSCRIPTEN__
496 yaze::app::wasm::InitializeWasmPlatform();
497
498 // Store config for deferred initialization
499 static yaze::AppConfig s_wasm_config = config;
500 static bool s_wasm_initialized = false;
501
502 // Main loop that handles deferred initialization for filesystem readiness
503 auto WasmMainLoop = []() {
504 // Wait for filesystem to be ready before initializing application
505 if (!s_wasm_initialized) {
506 if (yaze::app::wasm::IsFileSystemReady()) {
507 LOG_INFO("Main", "Filesystem ready, initializing application...");
509 s_wasm_initialized = true;
510 } else {
511 // Still waiting for filesystem - do nothing this frame
512 return;
513 }
514 }
515
516 // Normal tick once initialized
517 TickFrame();
518 };
519
520 // Use 0 for frame rate to enable requestAnimationFrame (better performance)
521 // The third parameter (1) simulates infinite loop
522 emscripten_set_main_loop(WasmMainLoop, 0, 1);
523#else
524 // Desktop Main Loop (Linux/Windows)
525
526 // API Server
527#if defined(YAZE_HTTP_API_ENABLED)
528 std::unique_ptr<yaze::cli::api::HttpServer> api_server;
529#endif
530#if defined(YAZE_HTTP_API_ENABLED)
531 api_server = SetupApiServer(config);
532#else
533 if (config.enable_api) {
534 LOG_WARN("Main",
535 "HTTP API requested but not enabled at build time "
536 "(set -DYAZE_ENABLE_HTTP_API=ON and rebuild).");
537 }
538#endif
539
541
542 // Handle symbol loading if requested
544 if (ctrl && ctrl->editor_manager()) {
545 auto& symbols = ctrl->editor_manager()->emulator().symbol_provider();
546
547 if (!FLAGS_load_symbols->Get().empty()) {
548 LOG_INFO("Main", "Loading symbols from %s...",
549 FLAGS_load_symbols->Get().c_str());
550 auto status = symbols.LoadSymbolFile(FLAGS_load_symbols->Get());
551 if (!status.ok()) {
552 LOG_ERROR("Main", "Failed to load symbols: %s",
553 status.ToString().c_str());
554 }
555 }
556
557 if (!FLAGS_load_asar_symbols->Get().empty()) {
558 LOG_INFO("Main", "Loading Asar symbols from %s...",
559 FLAGS_load_asar_symbols->Get().c_str());
560 auto status =
561 symbols.LoadAsarAsmDirectory(FLAGS_load_asar_symbols->Get());
562 if (!status.ok()) {
563 LOG_ERROR("Main", "Failed to load Asar symbols: %s",
564 status.ToString().c_str());
565 }
566 }
567 }
568
569 // Handle symbol export if requested (GUI mode)
570 if (!FLAGS_export_symbols->Get().empty()) {
571 LOG_INFO("Main", "Exporting symbols to %s...",
572 FLAGS_export_symbols->Get().c_str());
573
575 if (ctrl && ctrl->editor_manager()) {
576 auto* manager = ctrl->editor_manager();
577
578 // Attempt to find symbols from the current session
579 // For now, we'll assume they are in the emulator's symbol provider
580 auto& symbols = manager->emulator().symbol_provider();
581
584 std::string format_str =
585 absl::AsciiStrToLower(FLAGS_symbol_format->Get());
586 if (format_str == "asar")
588 else if (format_str == "wla")
590 else if (format_str == "bsnes")
592
593 auto export_or = symbols.ExportSymbols(format);
594 if (export_or.ok()) {
595 std::ofstream out(FLAGS_export_symbols->Get());
596 if (out.is_open()) {
597 out << *export_or;
598 LOG_INFO("Main", "Symbols exported successfully");
599 } else {
600 LOG_ERROR("Main", "Failed to open output file: %s",
601 FLAGS_export_symbols->Get().c_str());
602 }
603 } else {
604 LOG_ERROR("Main", "Failed to export symbols: %s",
605 export_or.status().ToString().c_str());
606 }
607 }
608
610 return EXIT_SUCCESS;
611 }
612
613 if (config.headless) {
614 LOG_INFO("Main", "Running in HEADLESS mode (no GUI window)");
615 // Optimized headless loop
616 while (yaze::Application::Instance().GetController()->IsActive()) {
618 // Sleep to reduce CPU usage in headless mode
619 // 60Hz = ~16ms, but we can sleep longer if just serving API/gRPC
620 std::this_thread::sleep_for(std::chrono::milliseconds(16));
621 }
622 } else {
623 // Normal GUI loop (also used for Service Mode with hidden window)
624 if (config.service_mode) {
625 LOG_INFO("Main", "Running in SERVICE mode (Hidden GUI window)");
626 }
627 while (yaze::Application::Instance().GetController()->IsActive()) {
628 TickFrame();
629 }
630 }
631
633
634#if defined(YAZE_HTTP_API_ENABLED)
635 if (api_server) {
636 api_server->Stop();
637 }
638#endif
639
640#endif // __EMSCRIPTEN__
641
642 return EXIT_SUCCESS;
643}
void TickFrame()
Definition main.cc:236
bool RunSymbolExportFastPath()
Definition main.cc:329
yaze::AppConfig BuildAppConfig(yaze::util::LogLevel log_level, std::string log_path)
Definition main.cc:282
int main(int argc, char **argv)
Definition main.cc:443
yaze::util::LogLevel ResolveLogConfig(bool debug_flag, bool log_to_console_flag)
Definition main.cc:249
void SetupCrashHandling()
Definition main.cc:242
Controller * GetController()
Definition application.h:81
static Application & Instance()
void Initialize(const AppConfig &config)
editor::EditorManager * editor_manager()
Definition controller.h:76
static Flags & get()
Definition features.h:119
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:41
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
std::vector< IFlag * > AllFlags() const
Definition flag.h:106
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
bool HandleHelpOrVersion(int argc, char **argv)
Definition main.cc:210
const char * LogLevelToString(yaze::util::LogLevel level)
Definition main.cc:181
std::set< std::string > ParseLogCategories(const std::string &raw)
Definition main.cc:170
yaze::util::LogLevel ParseLogLevelFlag(const std::string &raw_level, bool debug_flag)
Definition main.cc:148
std::vector< std::string > ParseCommaList(const std::string &raw)
Definition main.cc:197
yaze::editor::EditorManager * GetGlobalEditorManager()
Definition main.cc:136
yaze::emu::Emulator * GetGlobalEmulator()
Definition main.cc:128
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.