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(bool, editor_control, false,
79 "Enable guarded editor commands on the loopback HTTP API.");
80DEFINE_FLAG(int, api_port, 8080, "Port for the AI Agent API server.");
81
82DEFINE_FLAG(bool, headless, false,
83 "Run in headless mode without a GUI window.");
84DEFINE_FLAG(bool, service, false,
85 "Run in service mode (GUI backend initialized but window hidden).");
87 bool, server, false,
88 "Run in server mode (implies --enable_api, --enable_test_harness). "
89 "Defaults to --headless unless --service or --no-headless is specified.");
90
91#ifdef YAZE_WITH_GRPC
92// gRPC test harness flags
93DEFINE_FLAG(bool, enable_test_harness, false,
94 "Start gRPC test harness server for automated GUI testing.");
95
96DEFINE_FLAG(int, test_harness_port, 50052,
97 "Port for Unified gRPC server (default: 50052).");
98DEFINE_FLAG(std::string, backend, "internal",
99 "Emulator backend for gRPC service: 'internal' or 'mesen'.");
100#endif
101
102// Symbol Export Flags
103DEFINE_FLAG(std::string, export_symbols, "",
104 "Export symbols to file (requires --rom_file).");
105DEFINE_FLAG(std::string, symbol_format, "mesen",
106 "Format for symbol export: mesen, wla, asar, bsnes.");
107DEFINE_FLAG(std::string, load_symbols, "",
108 "Load symbol file (.mlb, .sym, .asm) on startup.");
109DEFINE_FLAG(std::string, load_asar_symbols, "",
110 "Load Asar symbols from directory on startup.");
111DEFINE_FLAG(bool, export_symbols_fast, false,
112 "Export symbols without initializing UI. Requires --load_symbols "
113 "or --load_asar_symbols.");
114
115// ============================================================================
116// Global Accessors for WASM Integration
117// These are used by yaze_debug_inspector.cc and wasm_terminal_bridge.cc
118// ============================================================================
119namespace yaze {
120namespace emu {
121class Emulator;
122}
123namespace editor {
124class EditorManager;
125}
126} // namespace yaze
127
128namespace yaze::app {
129
132 if (ctrl && ctrl->editor_manager()) {
133 return &ctrl->editor_manager()->emulator();
134 }
135 return nullptr;
136}
137
140 if (ctrl) {
141 return ctrl->editor_manager();
142 }
143 return nullptr;
144}
145
146} // namespace yaze::app
147
148namespace {
149
150yaze::util::LogLevel ParseLogLevelFlag(const std::string& raw_level,
151 bool debug_flag) {
152 if (debug_flag) {
154 }
155
156 const std::string lower = absl::AsciiStrToLower(raw_level);
157 if (lower == "debug") {
159 }
160 if (lower == "warn" || lower == "warning") {
162 }
163 if (lower == "error") {
165 }
166 if (lower == "fatal") {
168 }
170}
171
172std::set<std::string> ParseLogCategories(const std::string& raw) {
173 std::set<std::string> categories;
174 for (absl::string_view token :
175 absl::StrSplit(raw, ',', absl::SkipWhitespace())) {
176 if (!token.empty()) {
177 categories.insert(std::string(absl::StripAsciiWhitespace(token)));
178 }
179 }
180 return categories;
181}
182
184 switch (level) {
186 return "debug";
188 return "info";
190 return "warn";
192 return "error";
194 return "fatal";
195 }
196 return "info";
197}
198
199std::vector<std::string> ParseCommaList(const std::string& raw) {
200 std::vector<std::string> tokens;
201 for (absl::string_view token :
202 absl::StrSplit(raw, ',', absl::SkipWhitespace())) {
203 if (!token.empty()) {
204 tokens.emplace_back(absl::StripAsciiWhitespace(token));
205 }
206 }
207 return tokens;
208}
209
210// Handles --help/-h and --version before normal flag parsing so they work
211// regardless of any other arguments. Returns true if handled (caller exits).
212bool HandleHelpOrVersion(int argc, char** argv) {
213 for (int i = 1; i < argc; ++i) {
214 const std::string arg = argv[i];
215 if (arg == "--version") {
216 std::cout << "yaze " << YAZE_VERSION_STRING << "\n";
217 return true;
218 }
219 if (arg == "--help" || arg == "-h") {
220 std::cout << argv[0] << " (yaze " << YAZE_VERSION_STRING << ")\n"
221 << "Usage: " << argv[0] << " [flags]\n\nFlags:\n";
223 std::sort(flags.begin(), flags.end(),
224 [](const yaze::util::IFlag* a, const yaze::util::IFlag* b) {
225 return a->name() < b->name();
226 });
227 for (const auto* flag : flags) {
228 std::cout << " " << flag->name() << "\n " << flag->help() << "\n";
229 }
230 return true;
231 }
232 }
233 return false;
234}
235
236} // namespace
237
241
242// Helper Functions
243
250
252 bool log_to_console_flag) {
253 yaze::util::LogLevel log_level =
254 ParseLogLevelFlag(FLAGS_log_level->Get(), debug_flag);
255 std::set<std::string> log_categories =
256 ParseLogCategories(FLAGS_log_categories->Get());
257
258 std::string log_path = FLAGS_log_file->Get();
259 if (log_path.empty()) {
261 if (logs_dir.ok()) {
262 log_path = (*logs_dir / "yaze.log").string();
263 }
264 }
265
266 yaze::util::LogManager::instance().configure(log_level, log_path,
267 log_categories);
268
269 if (debug_flag || log_to_console_flag) {
271 }
272 if (debug_flag) {
273 LOG_INFO("Main", "🚀 YAZE started in debug mode");
274 }
275 LOG_INFO("Main",
276 "Logging configured (level=%s, file=%s, console=%s, categories=%zu)",
277 LogLevelToString(log_level),
278 log_path.empty() ? "<stderr>" : log_path.c_str(),
279 (debug_flag || log_to_console_flag) ? "on" : "off",
280 log_categories.size());
281 return log_level;
282}
283
285 std::string log_path) {
286 yaze::AppConfig config;
287
288 bool server_mode = FLAGS_server->Get();
289 bool service_mode = FLAGS_service->Get();
290
291 config.headless = FLAGS_headless->Get() || (server_mode && !service_mode);
292 config.service_mode = service_mode;
293 config.editor_control = FLAGS_editor_control->Get();
294 config.enable_api = FLAGS_enable_api->Get() || server_mode || service_mode ||
295 config.editor_control;
296
297#ifdef YAZE_WITH_GRPC
298 config.enable_test_harness =
299 FLAGS_enable_test_harness->Get() || server_mode || service_mode;
300 config.test_harness_port = FLAGS_test_harness_port->Get();
301 config.backend = FLAGS_backend->Get();
302#endif
303
304 config.rom_file = FLAGS_rom_file->Get();
305 config.log_file = log_path;
306 config.debug = (log_level == yaze::util::LogLevel::YAZE_DEBUG);
307 config.log_categories = FLAGS_log_categories->Get();
308 config.startup_editor = FLAGS_editor->Get();
309 config.jump_to_room = FLAGS_room->Get();
310 config.jump_to_map = FLAGS_map->Get();
311 config.api_port = FLAGS_api_port->Get();
312
313 config.welcome_mode =
314 yaze::StartupVisibilityFromString(FLAGS_startup_welcome->Get());
315 config.dashboard_mode =
316 yaze::StartupVisibilityFromString(FLAGS_startup_dashboard->Get());
317 config.sidebar_mode =
318 yaze::StartupVisibilityFromString(FLAGS_startup_sidebar->Get());
319 config.asset_load_mode =
320 yaze::AssetLoadModeFromString(FLAGS_asset_mode->Get());
322 config.asset_load_mode = (config.headless || server_mode || service_mode)
325 }
326
327 if (!FLAGS_open_panels->Get().empty()) {
328 config.open_panels = ParseCommaList(FLAGS_open_panels->Get());
329 }
330 return config;
331}
332
334 if (!FLAGS_export_symbols->Get().empty() &&
335 FLAGS_export_symbols_fast->Get()) {
337 bool loaded = false;
338
339 if (!FLAGS_load_symbols->Get().empty()) {
340 LOG_INFO("Main", "Loading symbols from %s...",
341 FLAGS_load_symbols->Get().c_str());
342 auto status = symbols.LoadSymbolFile(FLAGS_load_symbols->Get());
343 if (!status.ok()) {
344 LOG_ERROR("Main", "Failed to load symbols: %s",
345 status.ToString().c_str());
346 } else {
347 loaded = true;
348 }
349 }
350
351 if (!FLAGS_load_asar_symbols->Get().empty()) {
352 LOG_INFO("Main", "Loading Asar symbols from %s...",
353 FLAGS_load_asar_symbols->Get().c_str());
354 auto status =
355 symbols.LoadAsarAsmDirectory(FLAGS_load_asar_symbols->Get());
356 if (!status.ok()) {
357 LOG_ERROR("Main", "Failed to load Asar symbols: %s",
358 status.ToString().c_str());
359 } else {
360 loaded = true;
361 }
362 }
363
364 if (!loaded) {
365 LOG_ERROR(
366 "Main",
367 "No symbols loaded. Use --load_symbols or --load_asar_symbols.");
368 return false;
369 }
370
371 LOG_INFO("Main", "Exporting symbols to %s...",
372 FLAGS_export_symbols->Get().c_str());
375 std::string format_str = absl::AsciiStrToLower(FLAGS_symbol_format->Get());
376 if (format_str == "asar")
378 else if (format_str == "wla")
380 else if (format_str == "bsnes")
382
383 auto export_or = symbols.ExportSymbols(format);
384 if (export_or.ok()) {
385 std::ofstream out(FLAGS_export_symbols->Get());
386 if (out.is_open()) {
387 out << *export_or;
388 LOG_INFO("Main", "Symbols exported successfully (fast path)");
389 return true;
390 }
391 LOG_ERROR("Main", "Failed to open output file: %s",
392 FLAGS_export_symbols->Get().c_str());
393 return false;
394 }
395 LOG_ERROR("Main", "Failed to export symbols: %s",
396 export_or.status().ToString().c_str());
397 return false;
398 }
399 return false;
400}
401
402// Main Entry Point
403
404int main(int argc, char** argv) {
405 absl::InitializeSymbolizer(argv[0]);
406
407 if (HandleHelpOrVersion(argc, argv)) {
408 return EXIT_SUCCESS;
409 }
410
412
414 RETURN_IF_EXCEPTION(parser.Parse(argc, argv));
415
416 // Set up logging
417 const bool debug_flag = FLAGS_debug->Get();
418 const bool log_to_console_flag = FLAGS_log_to_console->Get();
419 auto log_level = ResolveLogConfig(debug_flag, log_to_console_flag);
420 std::string log_path = FLAGS_log_file->Get();
421 if (log_path.empty()) {
423 if (logs_dir.ok()) {
424 log_path = (*logs_dir / "yaze.log").string();
425 }
426 }
427
428 // Build AppConfig from flags
429 auto config = BuildAppConfig(log_level, log_path);
430
431 // Fast symbol export path (no UI initialization)
433 return EXIT_SUCCESS;
434 }
435#if !defined(__EMSCRIPTEN__)
436 // If fast export failed but was requested, and we are not emscripten, we should probably exit or continue?
437 // The helper returns false if not requested OR if failed.
438 // If requested and failed, it logs errors.
439 // To match original logic:
440 if (!FLAGS_export_symbols->Get().empty() &&
441 FLAGS_export_symbols_fast->Get()) {
442 return EXIT_FAILURE;
443 }
444#endif
445
446#ifdef __APPLE__
447 if (!config.headless) {
448 return yaze_run_cocoa_app_delegate(config);
449 }
450#endif
451
452#if defined(_WIN32) && !defined(__EMSCRIPTEN__)
453 SDL_SetMainReady();
454#endif
455
456#ifdef __EMSCRIPTEN__
457 yaze::app::wasm::InitializeWasmPlatform();
458
459 // Store config for deferred initialization
460 static yaze::AppConfig s_wasm_config = config;
461 static bool s_wasm_initialized = false;
462
463 // Main loop that handles deferred initialization for filesystem readiness
464 auto WasmMainLoop = []() {
465 // Wait for filesystem to be ready before initializing application
466 if (!s_wasm_initialized) {
467 if (yaze::app::wasm::IsFileSystemReady()) {
468 LOG_INFO("Main", "Filesystem ready, initializing application...");
470 s_wasm_initialized = true;
471 } else {
472 // Still waiting for filesystem - do nothing this frame
473 return;
474 }
475 }
476
477 // Normal tick once initialized
478 TickFrame();
479 };
480
481 // Use 0 for frame rate to enable requestAnimationFrame (better performance)
482 // The third parameter (1) simulates infinite loop
483 emscripten_set_main_loop(WasmMainLoop, 0, 1);
484#else
485 // Desktop Main Loop (Linux/Windows)
486
487 // API Server
488#if defined(YAZE_HTTP_API_ENABLED)
489 std::unique_ptr<yaze::cli::api::HttpServer> api_server;
490#endif
491#if !defined(YAZE_HTTP_API_ENABLED)
492 if (config.enable_api) {
493 LOG_WARN("Main",
494 "HTTP API requested but not enabled at build time "
495 "(set -DYAZE_ENABLE_HTTP_API=ON and rebuild).");
496 }
497#endif
498
500
501#if defined(YAZE_HTTP_API_ENABLED)
502 api_server = yaze::app::SetupEditorHttpServer(config);
503#endif
504
505 // Handle symbol loading if requested
507 if (ctrl && ctrl->editor_manager()) {
508 auto& symbols = ctrl->editor_manager()->emulator().symbol_provider();
509
510 if (!FLAGS_load_symbols->Get().empty()) {
511 LOG_INFO("Main", "Loading symbols from %s...",
512 FLAGS_load_symbols->Get().c_str());
513 auto status = symbols.LoadSymbolFile(FLAGS_load_symbols->Get());
514 if (!status.ok()) {
515 LOG_ERROR("Main", "Failed to load symbols: %s",
516 status.ToString().c_str());
517 }
518 }
519
520 if (!FLAGS_load_asar_symbols->Get().empty()) {
521 LOG_INFO("Main", "Loading Asar symbols from %s...",
522 FLAGS_load_asar_symbols->Get().c_str());
523 auto status =
524 symbols.LoadAsarAsmDirectory(FLAGS_load_asar_symbols->Get());
525 if (!status.ok()) {
526 LOG_ERROR("Main", "Failed to load Asar symbols: %s",
527 status.ToString().c_str());
528 }
529 }
530 }
531
532 // Handle symbol export if requested (GUI mode)
533 if (!FLAGS_export_symbols->Get().empty()) {
534 LOG_INFO("Main", "Exporting symbols to %s...",
535 FLAGS_export_symbols->Get().c_str());
536
538 if (ctrl && ctrl->editor_manager()) {
539 auto* manager = ctrl->editor_manager();
540
541 // Attempt to find symbols from the current session
542 // For now, we'll assume they are in the emulator's symbol provider
543 auto& symbols = manager->emulator().symbol_provider();
544
547 std::string format_str =
548 absl::AsciiStrToLower(FLAGS_symbol_format->Get());
549 if (format_str == "asar")
551 else if (format_str == "wla")
553 else if (format_str == "bsnes")
555
556 auto export_or = symbols.ExportSymbols(format);
557 if (export_or.ok()) {
558 std::ofstream out(FLAGS_export_symbols->Get());
559 if (out.is_open()) {
560 out << *export_or;
561 LOG_INFO("Main", "Symbols exported successfully");
562 } else {
563 LOG_ERROR("Main", "Failed to open output file: %s",
564 FLAGS_export_symbols->Get().c_str());
565 }
566 } else {
567 LOG_ERROR("Main", "Failed to export symbols: %s",
568 export_or.status().ToString().c_str());
569 }
570 }
571
572#if defined(YAZE_HTTP_API_ENABLED)
573 if (api_server)
574 api_server->Stop();
575#endif
577 return EXIT_SUCCESS;
578 }
579
580 if (config.headless) {
581 LOG_INFO("Main", "Running in HEADLESS mode (no GUI window)");
582 // Optimized headless loop
583 while (yaze::Application::Instance().GetController()->IsActive()) {
585 // Sleep to reduce CPU usage in headless mode
586 // 60Hz = ~16ms, but we can sleep longer if just serving API/gRPC
587 std::this_thread::sleep_for(std::chrono::milliseconds(16));
588 }
589 } else {
590 // Normal GUI loop (also used for Service Mode with hidden window)
591 if (config.service_mode) {
592 LOG_INFO("Main", "Running in SERVICE mode (Hidden GUI window)");
593 }
594 while (yaze::Application::Instance().GetController()->IsActive()) {
595 TickFrame();
596 }
597 }
598
599#if defined(YAZE_HTTP_API_ENABLED)
600 if (api_server) {
601 api_server->Stop();
602 }
603#endif
604
606
607#endif // __EMSCRIPTEN__
608
609 return EXIT_SUCCESS;
610}
void TickFrame()
Definition main.cc:238
bool RunSymbolExportFastPath()
Definition main.cc:333
yaze::AppConfig BuildAppConfig(yaze::util::LogLevel log_level, std::string log_path)
Definition main.cc:284
int main(int argc, char **argv)
Definition main.cc:404
yaze::util::LogLevel ResolveLogConfig(bool debug_flag, bool log_to_console_flag)
Definition main.cc:251
void SetupCrashHandling()
Definition main.cc:244
Controller * GetController()
Definition application.h:83
static Application & Instance()
void Initialize(const AppConfig &config)
editor::EditorManager * editor_manager()
Definition controller.h:85
static Flags & get()
Definition features.h:122
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:66
void configure(LogLevel level, const std::string &file_path, const std::set< std::string > &categories)
Configures the logging system.
Definition log.cc:80
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:140
#define LOG_WARN(category, format,...)
Definition log.h:138
#define LOG_INFO(category, format,...)
Definition log.h:136
#define RETURN_IF_EXCEPTION(expression)
Definition macro.h:104
bool HandleHelpOrVersion(int argc, char **argv)
Definition main.cc:212
const char * LogLevelToString(yaze::util::LogLevel level)
Definition main.cc:183
std::set< std::string > ParseLogCategories(const std::string &raw)
Definition main.cc:172
yaze::util::LogLevel ParseLogLevelFlag(const std::string &raw_level, bool debug_flag)
Definition main.cc:150
std::vector< std::string > ParseCommaList(const std::string &raw)
Definition main.cc:199
yaze::editor::EditorManager * GetGlobalEditorManager()
Definition main.cc:138
yaze::emu::Emulator * GetGlobalEmulator()
Definition main.cc:130
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:24
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:52
std::string log_file
Definition application.h:29
std::string backend
Definition application.h:56
StartupVisibility dashboard_mode
Definition application.h:33
Public YAZE API umbrella header.