#include "loader.h" #include #include #include #include #include #include #ifdef _WIN32 #include "util/path_.h" #include #include #endif #include "log.h" #include "sdl.h" #include "imgui_.h" #include "snapshots.h" #include "socket.h" #include "util/math_.h" using namespace anm2ed::types; using namespace anm2ed::util; namespace anm2ed { constexpr auto WINDOW_4K_SIZE = glm::ivec2(3840, 2160); constexpr auto UI_SCALE_4K_DEFAULT = 1.5f; constexpr auto WINDOW_DEFAULT_SIZE_MULTIPLIER = 0.90f; constexpr auto SOCKET_ADDRESS = "127.0.0.1"; constexpr auto SOCKET_PORT = 11414; #ifdef _WIN32 constexpr int SOCKET_ERROR_ADDRESS_IN_USE = WSAEADDRINUSE; #else constexpr int SOCKET_ERROR_ADDRESS_IN_USE = EADDRINUSE; #endif namespace { #ifdef _WIN32 void windows_wer_localdumps_configure() { #ifndef DEBUG return; #endif auto prefDir = sdl::preferences_directory_get(); if (prefDir.empty()) return; std::error_code ec{}; auto dumpDir = prefDir / "crash"; std::filesystem::create_directories(dumpDir, ec); if (ec) { logger.warning(std::format("Failed to create dump directory {}: {}", path::to_utf8(dumpDir), ec.message())); return; } wchar_t modulePath[MAX_PATH]{}; auto charsWritten = GetModuleFileNameW(nullptr, modulePath, MAX_PATH); if (charsWritten == 0 || charsWritten >= MAX_PATH) { logger.warning(std::format("Failed to get module filename for WER LocalDumps (error {}).", GetLastError())); return; } auto exeName = std::filesystem::path(modulePath).filename().wstring(); if (exeName.empty()) { logger.warning("Failed to determine executable name for WER LocalDumps."); return; } auto subkey = std::wstring(L"Software\\Microsoft\\Windows Error Reporting\\LocalDumps\\") + exeName; HKEY key{}; auto status = RegCreateKeyExW(HKEY_CURRENT_USER, subkey.c_str(), 0, nullptr, 0, KEY_SET_VALUE, nullptr, &key, nullptr); if (status != ERROR_SUCCESS) { logger.warning(std::format("Failed to create/open WER LocalDumps key (status {}).", (int)status)); return; } auto closeKey = [&]() { if (key) RegCloseKey(key); key = nullptr; }; auto dumpDirW = dumpDir.wstring(); const DWORD dumpType = 2; // full dump const DWORD dumpCount = 10; status = RegSetValueExW(key, L"DumpFolder", 0, REG_EXPAND_SZ, (const BYTE*)dumpDirW.c_str(), (DWORD)((dumpDirW.size() + 1) * sizeof(wchar_t))); if (status != ERROR_SUCCESS) { logger.warning(std::format("Failed to set WER DumpFolder (status {}).", (int)status)); closeKey(); return; } status = RegSetValueExW(key, L"DumpType", 0, REG_DWORD, (const BYTE*)&dumpType, sizeof(dumpType)); if (status != ERROR_SUCCESS) logger.warning(std::format("Failed to set WER DumpType (status {}).", (int)status)); status = RegSetValueExW(key, L"DumpCount", 0, REG_DWORD, (const BYTE*)&dumpCount, sizeof(dumpCount)); if (status != ERROR_SUCCESS) logger.warning(std::format("Failed to set WER DumpCount (status {}).", (int)status)); closeKey(); logger.info(std::format("Crash dumps enabled: {}", path::to_utf8(dumpDir))); } #endif bool socket_paths_send(Socket& socket, const std::vector& paths) { uint32_t count = htonl(static_cast(paths.size())); if (!socket.send(&count, sizeof(count))) return false; for (const auto& path : paths) { uint32_t length = htonl(static_cast(path.size())); if (!socket.send(&length, sizeof(length))) return false; if (!path.empty() && !socket.send(path.data(), path.size())) return false; } return true; } std::vector socket_paths_receive(Socket& socket) { uint32_t count{}; if (!socket.receive(&count, sizeof(count))) return {}; count = ntohl(count); std::vector paths; paths.reserve(count); for (uint32_t i = 0; i < count; ++i) { uint32_t length{}; if (!socket.receive(&length, sizeof(length))) return {}; length = ntohl(length); std::string path(length, '\0'); if (length > 0 && !socket.receive(path.data(), length)) return {}; paths.emplace_back(std::move(path)); } return paths; } } std::filesystem::path Loader::settings_path() { return sdl::preferences_directory_get() / "settings.ini"; } Loader::Loader(int argc, const char** argv) { for (int i = 1; i < argc; i++) arguments.emplace_back(argv[i]); Socket testSocket; if (!testSocket.open(SERVER)) logger.warning(std::format("Failed to open socket; single instancing will not work.")); if (testSocket.bind({SOCKET_ADDRESS, SOCKET_PORT})) { socket = std::move(testSocket); if (!socket.listen()) logger.warning("Could not listen on socket; single instancing disabled."); else { isSocketThread = true; logger.info(std::format("Opened socket at {}:{}", SOCKET_ADDRESS, SOCKET_PORT)); } } else { if (testSocket.last_error() != SOCKET_ERROR_ADDRESS_IN_USE) { logger.fatal(std::format("Failed to bind single-instance socket (error {}).", testSocket.last_error())); isError = true; return; } logger.info(std::format("Existing instance of program exists; passing arguments...")); Socket clientSocket; if (!clientSocket.open(CLIENT)) logger.warning("Could not open client socket to forward arguments."); else if (!clientSocket.connect({SOCKET_ADDRESS, SOCKET_PORT})) logger.warning("Could not connect to existing instance."); else if (!socket_paths_send(clientSocket, arguments)) logger.warning("Failed to transfer arguments to existing instance."); else logger.info("Sent arguments to existing instance. Exiting."); isError = true; return; } settings = Settings(settings_path()); SnapshotStack::max_size_set(settings.fileSnapshotStackSize); localize.language = (Language)settings.language; if (!SDL_Init(SDL_INIT_VIDEO)) { logger.fatal(std::format("Could not initialize SDL! {}", SDL_GetError())); isError = true; return; } logger.info("Initialized SDL"); #ifdef _WIN32 windows_wer_localdumps_configure(); #endif auto windowProperties = SDL_CreateProperties(); if (windowProperties == 0) { logger.fatal(std::format("Could not initialize window properties! {}", SDL_GetError())); isError = true; return; } if (settings.isDefault) { if (auto display = SDL_GetPrimaryDisplay(); display != 0) { if (auto mode = SDL_GetCurrentDisplayMode(display)) { if (mode->w >= WINDOW_4K_SIZE.x || mode->h >= WINDOW_4K_SIZE.y) settings.uiScale = UI_SCALE_4K_DEFAULT; settings.windowSize.x = mode->w * WINDOW_DEFAULT_SIZE_MULTIPLIER; settings.windowSize.y = mode->h * WINDOW_DEFAULT_SIZE_MULTIPLIER; } else logger.warning(std::format("Failed to query primary display mode: {}", SDL_GetError())); } else logger.warning("Failed to detect primary display for UI scaling."); } if (!MIX_Init()) logger.warning(std::format("Could not initialize SDL_mixer! {}", SDL_GetError())); else logger.info("Initialized SDL_mixer"); SDL_SetStringProperty(windowProperties, SDL_PROP_WINDOW_CREATE_TITLE_STRING, "Anm2Ed"); SDL_SetNumberProperty(windowProperties, SDL_PROP_WINDOW_CREATE_WIDTH_NUMBER, settings.windowSize.x); SDL_SetNumberProperty(windowProperties, SDL_PROP_WINDOW_CREATE_HEIGHT_NUMBER, settings.windowSize.y); SDL_SetNumberProperty(windowProperties, SDL_PROP_WINDOW_CREATE_X_NUMBER, settings.isDefault ? SDL_WINDOWPOS_CENTERED : settings.windowPosition.x); SDL_SetNumberProperty(windowProperties, SDL_PROP_WINDOW_CREATE_Y_NUMBER, settings.isDefault ? SDL_WINDOWPOS_CENTERED : settings.windowPosition.y); SDL_SetBooleanProperty(windowProperties, SDL_PROP_WINDOW_CREATE_RESIZABLE_BOOLEAN, true); SDL_SetBooleanProperty(windowProperties, SDL_PROP_WINDOW_CREATE_OPENGL_BOOLEAN, true); SDL_SetBooleanProperty(windowProperties, SDL_PROP_WINDOW_CREATE_HIGH_PIXEL_DENSITY_BOOLEAN, true); window = SDL_CreateWindowWithProperties(windowProperties); if (!window) { logger.fatal(std::format("Could not initialize window! {}", SDL_GetError())); isError = true; return; } SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3); glContext = SDL_GL_CreateContext(window); if (!glContext) { logger.fatal(std::format("Could not initialize OpenGL context! {}", SDL_GetError())); isError = true; return; } if (!gladLoadGLLoader((GLADloadproc)(SDL_GL_GetProcAddress))) { logger.fatal(std::format("Could not initialize OpenGL!")); isError = true; return; } logger.info(std::format("Initialized OpenGL {}", (const char*)glGetString(GL_VERSION))); glEnable(GL_BLEND); glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA); glLineWidth(2.0f); glDisable(GL_DEPTH_TEST); glDisable(GL_LINE_SMOOTH); math::random_seed_set(); IMGUI_CHECKVERSION(); if (!ImGui::CreateContext()) { logger.fatal("Could not initialize Dear ImGui!"); isError = true; return; } logger.info("Initialized Dear ImGui"); imgui::theme_set((theme::Type)settings.theme); ImGui_ImplSDL3_InitForOpenGL(window, glContext); ImGui_ImplOpenGL3_Init("#version 330"); ImGuiIO& io = ImGui::GetIO(); io.IniFilename = nullptr; io.ConfigFlags |= ImGuiConfigFlags_DockingEnable; io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; io.KeyRepeatDelay = settings.keyboardRepeatDelay; io.KeyRepeatRate = settings.keyboardRepeatRate; ImGui::GetStyle().FontScaleMain = settings.uiScale; io.ConfigWindowsMoveFromTitleBarOnly = true; if (auto imguiData = Settings::imgui_data_load(settings_path()); !imguiData.empty()) ImGui::LoadIniSettingsFromMemory(imguiData.c_str(), imguiData.size()); if (isSocketThread) { isSocketRunning = true; socketThread = std::thread( [this]() { while (isSocketRunning) { auto client = socket.accept(); if (!client.is_valid()) { if (!isSocketRunning) break; continue; } SDL_FlashWindow(window, SDL_FLASH_UNTIL_FOCUSED); auto paths = socket_paths_receive(client); for (auto& path : paths) { if (path.empty()) continue; SDL_Event event{}; event.type = SDL_EVENT_USER; event.drop.data = SDL_strdup(path.c_str()); event.drop.windowID = window ? SDL_GetWindowID(window) : 0; SDL_PushEvent(&event); } } }); } } Loader::~Loader() { if (isSocketThread) { isSocketRunning = false; if (socket.is_valid()) { Socket wakeSocket; if (wakeSocket.open(CLIENT) && wakeSocket.connect({SOCKET_ADDRESS, SOCKET_PORT})) socket_paths_send(wakeSocket, {}); } socket.close(); if (socketThread.joinable()) socketThread.join(); } if (ImGui::GetCurrentContext()) { settings.save(settings_path(), ImGui::SaveIniSettingsToMemory(nullptr)); ImGui_ImplSDL3_Shutdown(); ImGui_ImplOpenGL3_Shutdown(); ImGui::DestroyContext(); } MIX_Quit(); if (SDL_WasInit(0)) { SDL_GL_DestroyContext(glContext); SDL_DestroyWindow(window); SDL_Quit(); } } }