#include "core/system/director.hpp" #include #include // Para mkdir, stat, S_IRWXU #include // Para getuid #include // Para errno, EEXIST, EACCES, ENAMETOO... #include // Para printf, perror #include // Para exit, EXIT_FAILURE, srand #include // Para basic_ostream, operator<<, cout #include // Para make_unique, unique_ptr #include // Para operator+, allocator, char_traits #include "core/audio/audio.hpp" // Para Audio #include "core/input/input.hpp" // Para Input, InputAction #include "core/locale/locale.hpp" // Para Locale #include "core/rendering/render_info.hpp" // Para RenderInfo #include "core/rendering/screen.hpp" // Para Screen #include "core/resources/resource_cache.hpp" // Para Resource #include "core/resources/resource_helper.hpp" // Para ResourceHelper #include "core/resources/resource_list.hpp" // Para Asset, AssetType #include "core/resources/resource_loader.hpp" // Para ResourceLoader #include "core/system/event_buffer.hpp" // Para EventBuffer #include "game/gameplay/cheevos.hpp" // Para Cheevos #include "game/options.hpp" // Para Options, options, OptionsVideo #include "game/scene_manager.hpp" // Para SceneManager #include "game/scenes/game.hpp" // Para Game, GameMode #include "game/scenes/logo.hpp" // Para Logo #include "game/scenes/title.hpp" // Para Title #include "game/ui/console.hpp" // Para Console #include "game/ui/notifier.hpp" // Para Notifier #include "utils/defines.hpp" // Para WINDOW_CAPTION #ifdef _DEBUG #include "core/system/debug.hpp" // Para Debug #include "game/editor/map_editor.hpp" // Para MapEditor #endif #ifndef _WIN32 #include #endif // Constructor Director::Director() { std::cout << "Game start" << '\n'; // Obtiene la ruta del ejecutable std::string base = SDL_GetBasePath(); if (!base.empty() && base.back() == '/') { base.pop_back(); } executable_path_ = base; // Crea la carpeta del sistema donde guardar datos createSystemFolder("jailgames"); createSystemFolder("jailgames/projecte_2026"); // Crea el subdirectorio shaders/ dentro de system_folder_ sin modificar system_folder_ { std::string shaders_dir = system_folder_ + "/shaders"; struct stat st = {.st_dev = 0}; if (stat(shaders_dir.c_str(), &st) == -1) { errno = 0; #ifdef _WIN32 mkdir(shaders_dir.c_str()); #else mkdir(shaders_dir.c_str(), S_IRWXU); #endif } } // Determinar el prefijo de ruta según la plataforma #ifdef MACOS_BUNDLE const std::string PREFIX = "/../Resources"; #else const std::string PREFIX; #endif // Preparar ruta al pack (en macOS bundle está en Contents/Resources/) std::string pack_path = executable_path_ + PREFIX + "/resources.pack"; #ifdef RELEASE_BUILD // ============================================================ // RELEASE BUILD: Pack-first architecture // ============================================================ std::cout << "\n** RELEASE MODE: Pack-first initialization\n"; // 1. Initialize resource pack system (required, no fallback) std::cout << "Initializing resource pack: " << pack_path << '\n'; if (!Resource::Helper::initializeResourceSystem(pack_path, false)) { std::cerr << "ERROR: Failed to load resources.pack (required in release builds)\n"; exit(EXIT_FAILURE); } // 2. Validate pack integrity std::cout << "Validating pack integrity..." << '\n'; if (!Resource::Loader::get().validatePack()) { std::cerr << "ERROR: Pack validation failed\n"; exit(EXIT_FAILURE); } // 3. Load assets.yaml from pack std::cout << "Loading assets configuration from pack..." << '\n'; std::string assets_config = Resource::Loader::get().loadAssetsConfig(); if (assets_config.empty()) { std::cerr << "ERROR: Failed to load assets.yaml from pack\n"; exit(EXIT_FAILURE); } // 4. Initialize Asset system with config from pack // NOTE: In release, don't use executable_path or PREFIX - paths in pack are relative // Pass empty string to avoid issues when running from different directories Resource::List::init(""); // Empty executable_path in release Resource::List::get()->loadFromString(assets_config, "", system_folder_); // Empty PREFIX for pack std::cout << "Asset system initialized from pack\n"; #else // ============================================================ // DEVELOPMENT BUILD: Filesystem-first architecture // ============================================================ std::cout << "\n** DEVELOPMENT MODE: Filesystem-first initialization\n"; // 1. Initialize Asset system from filesystem Resource::List::init(executable_path_); // 2. Load asset configuration from disk // Note: Asset verification happens during Resource::Cache::load() setFileList(); // 3. Initialize resource pack system (optional, with fallback) std::cout << "Initializing resource pack (development mode): " << pack_path << '\n'; Resource::Helper::initializeResourceSystem(pack_path, true); #endif // Configura la ruta y carga las opciones desde un fichero Options::setConfigFile(Resource::List::get()->get("config.yaml")); // NOLINT(readability-static-accessed-through-instance) Options::loadFromFile(); // Configura la ruta y carga los presets de PostFX Options::setPostFXFile(Resource::List::get()->get("postfx.yaml")); // NOLINT(readability-static-accessed-through-instance) Options::loadPostFXFromFile(); // Configura la ruta y carga los presets del shader CrtPi Options::setCrtPiFile(Resource::List::get()->get("crtpi.yaml")); // NOLINT(readability-static-accessed-through-instance) Options::loadCrtPiFromFile(); // En mode quiosc, forçar pantalla completa independentment de la configuració if (Options::kiosk.enabled) { Options::video.fullscreen = true; } // Inicializa JailAudio Audio::init(); // Crea los objetos Screen::init(); #ifdef _DEBUG // En debug inicializamos Debug antes de Cache para leer el flag lazy_loading Debug::init(); Debug::get()->setDebugFile(Resource::List::get()->get("debug.yaml")); Debug::get()->loadFromFile(); #endif // Initialize resources (works for both release and development) #ifdef _DEBUG Resource::Cache::init(Debug::get()->getLazyLoading() ? Resource::Cache::LoadingMode::LAZY : Resource::Cache::LoadingMode::EAGER); #else Resource::Cache::init(); #endif Notifier::init("", "8bithud"); RenderInfo::init(); #ifdef _DEBUG if (Debug::get()->getRenderInfoEnabled()) { RenderInfo::get()->toggle(); } #endif Console::init("8bithud"); Screen::get()->setNotificationsEnabled(true); // Special handling for gamecontrollerdb.txt - SDL needs filesystem path #ifdef RELEASE_BUILD // In release, construct the path manually (not from Asset which has empty executable_path) std::string gamecontroller_db = executable_path_ + PREFIX + "/gamecontrollerdb.txt"; Input::init(gamecontroller_db); #else // In development, use Asset as normal Input::init(Resource::List::get()->get("gamecontrollerdb.txt")); // NOLINT(readability-static-accessed-through-instance) Carga configuración de controles #endif // Aplica las teclas y botones del gamepad configurados desde Options Input::get()->applyKeyboardBindingsFromOptions(); Input::get()->applyGamepadBindingsFromOptions(); #ifdef _DEBUG SceneManager::current = Debug::get()->getInitialScene(); MapEditor::init(); #endif std::cout << "\n"; // Fin de inicialización de sistemas // Inicializa el sistema de localización (antes de Cheevos que usa textos traducidos) #ifdef RELEASE_BUILD { // En release el locale está en el pack, no en el filesystem std::string locale_key = Resource::List::get()->get(Options::language + ".yaml"); // NOLINT(readability-static-accessed-through-instance) auto locale_bytes = Resource::Helper::loadFile(locale_key); std::string locale_content(locale_bytes.begin(), locale_bytes.end()); Locale::initFromContent(locale_content); } #else Locale::init(Resource::List::get()->get(Options::language + ".yaml")); // NOLINT(readability-static-accessed-through-instance) #endif // Special handling for cheevos.bin - also needs filesystem path #ifdef RELEASE_BUILD std::string cheevos_path = system_folder_ + "/cheevos.bin"; Cheevos::init(cheevos_path); #else Cheevos::init(Resource::List::get()->get("cheevos.bin")); #endif } Director::~Director() { // Guarda las opciones a un fichero Options::saveToFile(); // Destruye los singletones Cheevos::destroy(); Locale::destroy(); #ifdef _DEBUG MapEditor::destroy(); Debug::destroy(); #endif Input::destroy(); Console::destroy(); RenderInfo::destroy(); Notifier::destroy(); Resource::Cache::destroy(); Resource::Helper::shutdownResourceSystem(); // Shutdown resource pack system Audio::destroy(); Screen::destroy(); Resource::List::destroy(); SDL_Quit(); std::cout << "\nBye!" << '\n'; } // Crea la carpeta del sistema donde guardar datos void Director::createSystemFolder(const std::string& folder) { // NOLINT(readability-convert-member-functions-to-static) #ifdef _WIN32 system_folder_ = std::string(getenv("APPDATA")) + "/" + folder; #elif __APPLE__ struct passwd* pw = getpwuid(getuid()); const char* homedir = pw->pw_dir; system_folder_ = std::string(homedir) + "/Library/Application Support" + "/" + folder; #elif __linux__ struct passwd* pw = getpwuid(getuid()); const char* homedir = pw->pw_dir; system_folder_ = std::string(homedir) + "/.config/" + folder; { // Intenta crear ".config", per si no existeix std::string config_base_folder = std::string(homedir) + "/.config"; int ret = mkdir(config_base_folder.c_str(), S_IRWXU); if (ret == -1 && errno != EEXIST) { printf("ERROR CREATING CONFIG BASE FOLDER."); exit(EXIT_FAILURE); } } #endif struct stat st = {.st_dev = 0}; if (stat(system_folder_.c_str(), &st) == -1) { errno = 0; #ifdef _WIN32 int ret = mkdir(system_folder_.c_str()); #else int ret = mkdir(system_folder_.c_str(), S_IRWXU); #endif if (ret == -1) { switch (errno) { case EACCES: printf("the parent directory does not allow write"); exit(EXIT_FAILURE); case EEXIST: printf("pathname already exists"); exit(EXIT_FAILURE); case ENAMETOOLONG: printf("pathname is too long"); exit(EXIT_FAILURE); default: perror("mkdir"); exit(EXIT_FAILURE); } } } } // Carga la configuración de assets desde assets.yaml void Director::setFileList() { // NOLINT(readability-convert-member-functions-to-static) // Determinar el prefijo de ruta según la plataforma #ifdef MACOS_BUNDLE const std::string PREFIX = "/../Resources"; #else const std::string PREFIX; #endif // Construir ruta al archivo de configuración de assets std::string config_path = executable_path_ + PREFIX + "/config/assets.yaml"; // Cargar todos los assets desde el archivo de configuración // La verificación de existencia de archivos se realiza durante Resource::Cache::load() Resource::List::get()->loadFromFile(config_path, PREFIX, system_folder_); } // Destruye la escena activa void Director::destroyCurrentScene() { logo_.reset(); title_.reset(); game_.reset(); active_scene_ = SceneManager::Scene::QUIT; } // Crea la escena indicada void Director::createScene(SceneManager::Scene scene) { destroyCurrentScene(); switch (scene) { case SceneManager::Scene::LOGO: logo_ = std::make_unique(); break; case SceneManager::Scene::TITLE: title_ = std::make_unique(); break; case SceneManager::Scene::GAME: Audio::get()->stopMusic(); game_ = std::make_unique<Game>(Game::Mode::GAME); break; default: break; } active_scene_ = scene; } // Una iteración del bucle principal (callback model) auto Director::iterate() -> SDL_AppResult { // Resolver RESTART_CURRENT antes de comprobar escena if (SceneManager::current == SceneManager::Scene::RESTART_CURRENT) { SceneManager::current = SceneManager::scene_before_restart; } // Salir si se ha pedido QUIT if (SceneManager::current == SceneManager::Scene::QUIT) { EventBuffer::events.clear(); return SDL_APP_SUCCESS; } // Si la escena solicitada no coincide con la activa, crear la nueva if (SceneManager::current != active_scene_) { createScene(SceneManager::current); } // Ejecutar un frame de la escena activa switch (active_scene_) { case SceneManager::Scene::LOGO: logo_->update(); logo_->render(); break; case SceneManager::Scene::TITLE: title_->update(); title_->render(); break; case SceneManager::Scene::GAME: game_->update(); game_->render(); break; default: break; } // Limpiar el buffer de eventos tras cada frame EventBuffer::events.clear(); return SDL_APP_CONTINUE; }