Files
anm2ed/src/loader.cpp
2026-01-11 17:15:28 -05:00

404 lines
12 KiB
C++

#include "loader.h"
#include <cerrno>
#include <cstdint>
#include <filesystem>
#include <imgui/backends/imgui_impl_opengl3.h>
#include <imgui/backends/imgui_impl_sdl3.h>
#include <SDL3_mixer/SDL_mixer.h>
#ifdef _WIN32
#include "util/path_.h"
#include <format>
#include <windows.h>
#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<std::string>& paths)
{
uint32_t count = htonl(static_cast<uint32_t>(paths.size()));
if (!socket.send(&count, sizeof(count))) return false;
for (const auto& path : paths)
{
uint32_t length = htonl(static_cast<uint32_t>(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<std::string> socket_paths_receive(Socket& socket)
{
uint32_t count{};
if (!socket.receive(&count, sizeof(count))) return {};
count = ntohl(count);
std::vector<std::string> 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();
}
}
}