2026-02-02 12:24:50 -08:00
|
|
|
#include "ui/auth_screen.hpp"
|
2026-02-05 15:09:16 -08:00
|
|
|
#include "auth/crypto.hpp"
|
2026-02-05 15:44:42 -08:00
|
|
|
#include "core/application.hpp"
|
2026-02-05 12:25:00 -08:00
|
|
|
#include "core/logger.hpp"
|
2026-02-05 15:44:42 -08:00
|
|
|
#include "rendering/renderer.hpp"
|
|
|
|
|
#include "pipeline/asset_manager.hpp"
|
|
|
|
|
#include "audio/music_manager.hpp"
|
2026-02-12 22:56:36 -08:00
|
|
|
#include "game/expansion_profile.hpp"
|
2026-02-02 12:24:50 -08:00
|
|
|
#include <imgui.h>
|
2026-02-05 15:47:21 -08:00
|
|
|
#include <filesystem>
|
2026-02-02 12:24:50 -08:00
|
|
|
#include <sstream>
|
2026-02-05 12:25:00 -08:00
|
|
|
#include <fstream>
|
|
|
|
|
#include <cstdlib>
|
2026-02-05 15:09:16 -08:00
|
|
|
#include <cstring>
|
2026-02-05 12:25:00 -08:00
|
|
|
#include <filesystem>
|
2026-02-05 15:09:16 -08:00
|
|
|
#include <algorithm>
|
|
|
|
|
#include <iomanip>
|
2026-02-11 16:17:37 -08:00
|
|
|
#include <array>
|
|
|
|
|
#include <random>
|
2026-02-02 12:24:50 -08:00
|
|
|
|
|
|
|
|
namespace wowee { namespace ui {
|
|
|
|
|
|
2026-02-05 15:09:16 -08:00
|
|
|
static std::string hexEncode(const std::vector<uint8_t>& data) {
|
|
|
|
|
std::ostringstream ss;
|
|
|
|
|
for (uint8_t b : data)
|
|
|
|
|
ss << std::hex << std::setfill('0') << std::setw(2) << (int)b;
|
|
|
|
|
return ss.str();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static std::vector<uint8_t> hexDecode(const std::string& hex) {
|
|
|
|
|
std::vector<uint8_t> bytes;
|
|
|
|
|
for (size_t i = 0; i + 1 < hex.size(); i += 2) {
|
|
|
|
|
uint8_t b = static_cast<uint8_t>(std::stoul(hex.substr(i, 2), nullptr, 16));
|
|
|
|
|
bytes.push_back(b);
|
|
|
|
|
}
|
|
|
|
|
return bytes;
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-02 12:24:50 -08:00
|
|
|
AuthScreen::AuthScreen() {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void AuthScreen::render(auth::AuthHandler& authHandler) {
|
2026-02-05 12:25:00 -08:00
|
|
|
// Load saved login info on first render
|
|
|
|
|
if (!loginInfoLoaded) {
|
|
|
|
|
loadLoginInfo();
|
|
|
|
|
loginInfoLoaded = true;
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-05 15:34:29 -08:00
|
|
|
if (!videoInitAttempted) {
|
|
|
|
|
videoInitAttempted = true;
|
2026-02-05 16:26:45 -08:00
|
|
|
std::string videoPath = "assets/startscreen.mp4";
|
|
|
|
|
if (!std::filesystem::exists(videoPath)) {
|
|
|
|
|
videoPath = (std::filesystem::current_path() / "assets/startscreen.mp4").string();
|
|
|
|
|
}
|
|
|
|
|
backgroundVideo.open(videoPath);
|
2026-02-05 15:34:29 -08:00
|
|
|
}
|
|
|
|
|
backgroundVideo.update(ImGui::GetIO().DeltaTime);
|
|
|
|
|
if (backgroundVideo.isReady()) {
|
|
|
|
|
ImVec2 screen = ImGui::GetIO().DisplaySize;
|
|
|
|
|
float screenW = screen.x;
|
|
|
|
|
float screenH = screen.y;
|
|
|
|
|
float videoW = static_cast<float>(backgroundVideo.getWidth());
|
|
|
|
|
float videoH = static_cast<float>(backgroundVideo.getHeight());
|
|
|
|
|
if (videoW > 0.0f && videoH > 0.0f) {
|
|
|
|
|
float screenAspect = screenW / screenH;
|
|
|
|
|
float videoAspect = videoW / videoH;
|
|
|
|
|
ImVec2 uv0(0.0f, 0.0f);
|
|
|
|
|
ImVec2 uv1(1.0f, 1.0f);
|
|
|
|
|
if (videoAspect > screenAspect) {
|
|
|
|
|
float scale = screenAspect / videoAspect;
|
|
|
|
|
float crop = (1.0f - scale) * 0.5f;
|
|
|
|
|
uv0.x = crop;
|
|
|
|
|
uv1.x = 1.0f - crop;
|
|
|
|
|
} else if (videoAspect < screenAspect) {
|
|
|
|
|
float scale = videoAspect / screenAspect;
|
|
|
|
|
float crop = (1.0f - scale) * 0.5f;
|
|
|
|
|
uv0.y = crop;
|
|
|
|
|
uv1.y = 1.0f - crop;
|
|
|
|
|
}
|
|
|
|
|
ImDrawList* bg = ImGui::GetBackgroundDrawList();
|
|
|
|
|
bg->AddImage(static_cast<ImTextureID>(static_cast<uintptr_t>(backgroundVideo.getTextureId())),
|
|
|
|
|
ImVec2(0, 0), ImVec2(screenW, screenH), uv0, uv1);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-05 15:47:21 -08:00
|
|
|
auto& app = core::Application::getInstance();
|
|
|
|
|
auto* renderer = app.getRenderer();
|
2026-02-05 15:44:42 -08:00
|
|
|
if (!musicInitAttempted) {
|
|
|
|
|
musicInitAttempted = true;
|
|
|
|
|
auto* assets = app.getAssetManager();
|
|
|
|
|
if (renderer) {
|
|
|
|
|
auto* music = renderer->getMusicManager();
|
|
|
|
|
if (music && assets && assets->isInitialized() && !music->isInitialized()) {
|
|
|
|
|
music->initialize(assets);
|
|
|
|
|
}
|
2026-02-05 15:47:21 -08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (renderer) {
|
|
|
|
|
auto* music = renderer->getMusicManager();
|
|
|
|
|
if (music) {
|
|
|
|
|
music->update(ImGui::GetIO().DeltaTime);
|
|
|
|
|
if (!music->isPlaying()) {
|
2026-02-11 16:17:37 -08:00
|
|
|
static std::mt19937 rng(std::random_device{}());
|
2026-02-11 16:25:56 -08:00
|
|
|
static const std::array<const char*, 3> kLoginTracks = {
|
2026-02-11 16:17:37 -08:00
|
|
|
"Raise the Mug, Sound the Warcry.mp3",
|
2026-02-11 16:25:56 -08:00
|
|
|
"Wanderwewill.mp3",
|
|
|
|
|
"Gold on the Tide in Booty Bay.mp3"
|
2026-02-11 16:17:37 -08:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
std::vector<std::string> availableTracks;
|
|
|
|
|
availableTracks.reserve(kLoginTracks.size());
|
|
|
|
|
for (const char* track : kLoginTracks) {
|
|
|
|
|
std::filesystem::path localPath = std::filesystem::path("assets") / track;
|
|
|
|
|
if (std::filesystem::exists(localPath)) {
|
|
|
|
|
availableTracks.push_back(localPath.string());
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::filesystem::path cwdPath = std::filesystem::current_path() / "assets" / track;
|
|
|
|
|
if (std::filesystem::exists(cwdPath)) {
|
|
|
|
|
availableTracks.push_back(cwdPath.string());
|
2026-02-05 15:49:53 -08:00
|
|
|
}
|
2026-02-05 15:47:21 -08:00
|
|
|
}
|
2026-02-11 16:17:37 -08:00
|
|
|
|
|
|
|
|
if (!availableTracks.empty()) {
|
|
|
|
|
std::uniform_int_distribution<size_t> pick(0, availableTracks.size() - 1);
|
|
|
|
|
const std::string& path = availableTracks[pick(rng)];
|
|
|
|
|
music->playFilePath(path, true);
|
|
|
|
|
LOG_INFO("AuthScreen: Playing login intro track: ", path);
|
|
|
|
|
musicPlaying = music->isPlaying();
|
|
|
|
|
} else {
|
|
|
|
|
LOG_WARNING("AuthScreen: No login intro tracks found in assets/");
|
|
|
|
|
}
|
2026-02-05 15:44:42 -08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-02 12:24:50 -08:00
|
|
|
ImGui::SetNextWindowSize(ImVec2(500, 400), ImGuiCond_FirstUseEver);
|
2026-02-07 12:23:03 -08:00
|
|
|
ImGui::Begin("Authentication", nullptr, ImGuiWindowFlags_NoCollapse);
|
2026-02-02 12:24:50 -08:00
|
|
|
|
2026-02-07 12:23:03 -08:00
|
|
|
ImGui::Text("Connect to Server");
|
2026-02-02 12:24:50 -08:00
|
|
|
ImGui::Separator();
|
|
|
|
|
ImGui::Spacing();
|
|
|
|
|
|
|
|
|
|
// Server settings
|
|
|
|
|
ImGui::Text("Server Settings");
|
|
|
|
|
ImGui::InputText("Hostname", hostname, sizeof(hostname));
|
|
|
|
|
ImGui::InputInt("Port", &port);
|
|
|
|
|
if (port < 1) port = 1;
|
|
|
|
|
if (port > 65535) port = 65535;
|
|
|
|
|
|
2026-02-12 22:56:36 -08:00
|
|
|
// Expansion selector (populated from ExpansionRegistry)
|
|
|
|
|
auto* registry = core::Application::getInstance().getExpansionRegistry();
|
|
|
|
|
if (registry && !registry->getAllProfiles().empty()) {
|
|
|
|
|
auto& profiles = registry->getAllProfiles();
|
|
|
|
|
// Build combo items: "WotLK (3.3.5a)"
|
|
|
|
|
std::string preview;
|
|
|
|
|
if (expansionIndex >= 0 && expansionIndex < static_cast<int>(profiles.size())) {
|
|
|
|
|
preview = profiles[expansionIndex].shortName + " (" + profiles[expansionIndex].versionString() + ")";
|
|
|
|
|
}
|
|
|
|
|
if (ImGui::BeginCombo("Expansion", preview.c_str())) {
|
|
|
|
|
for (int i = 0; i < static_cast<int>(profiles.size()); ++i) {
|
|
|
|
|
std::string label = profiles[i].shortName + " (" + profiles[i].versionString() + ")";
|
|
|
|
|
bool selected = (expansionIndex == i);
|
|
|
|
|
if (ImGui::Selectable(label.c_str(), selected)) {
|
|
|
|
|
expansionIndex = i;
|
|
|
|
|
registry->setActive(profiles[i].id);
|
|
|
|
|
}
|
|
|
|
|
if (selected) ImGui::SetItemDefaultFocus();
|
|
|
|
|
}
|
|
|
|
|
ImGui::EndCombo();
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
ImGui::Text("Expansion: WotLK 3.3.5a (default)");
|
|
|
|
|
}
|
2026-02-07 12:23:03 -08:00
|
|
|
|
2026-02-02 12:24:50 -08:00
|
|
|
ImGui::Spacing();
|
|
|
|
|
ImGui::Separator();
|
|
|
|
|
ImGui::Spacing();
|
|
|
|
|
|
|
|
|
|
// Credentials
|
|
|
|
|
ImGui::Text("Credentials");
|
|
|
|
|
ImGui::InputText("Username", username, sizeof(username));
|
|
|
|
|
|
|
|
|
|
// Password with visibility toggle
|
|
|
|
|
ImGuiInputTextFlags passwordFlags = showPassword ? 0 : ImGuiInputTextFlags_Password;
|
|
|
|
|
ImGui::InputText("Password", password, sizeof(password), passwordFlags);
|
|
|
|
|
ImGui::SameLine();
|
|
|
|
|
if (ImGui::Checkbox("Show", &showPassword)) {
|
|
|
|
|
// Checkbox state changed
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ImGui::Spacing();
|
|
|
|
|
ImGui::Separator();
|
|
|
|
|
ImGui::Spacing();
|
|
|
|
|
|
|
|
|
|
// Connection status
|
|
|
|
|
if (!statusMessage.empty()) {
|
|
|
|
|
if (statusIsError) {
|
|
|
|
|
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.3f, 0.3f, 1.0f));
|
|
|
|
|
} else {
|
|
|
|
|
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.3f, 1.0f, 0.3f, 1.0f));
|
|
|
|
|
}
|
|
|
|
|
ImGui::TextWrapped("%s", statusMessage.c_str());
|
|
|
|
|
ImGui::PopStyleColor();
|
|
|
|
|
ImGui::Spacing();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Connect button
|
|
|
|
|
if (authenticating) {
|
2026-02-05 12:17:09 -08:00
|
|
|
authTimer += ImGui::GetIO().DeltaTime;
|
|
|
|
|
|
|
|
|
|
// Show progress with elapsed time
|
|
|
|
|
char progressBuf[128];
|
|
|
|
|
snprintf(progressBuf, sizeof(progressBuf), "Authenticating... (%.0fs)", authTimer);
|
|
|
|
|
ImGui::Text("%s", progressBuf);
|
2026-02-02 12:24:50 -08:00
|
|
|
|
|
|
|
|
// Check authentication status
|
|
|
|
|
auto state = authHandler.getState();
|
|
|
|
|
if (state == auth::AuthState::AUTHENTICATED) {
|
|
|
|
|
setStatus("Authentication successful!", false);
|
|
|
|
|
authenticating = false;
|
|
|
|
|
|
2026-02-05 15:09:16 -08:00
|
|
|
// Compute and save password hash if user typed a fresh password
|
|
|
|
|
if (!usingStoredHash) {
|
|
|
|
|
std::string upperUser = username;
|
|
|
|
|
std::string upperPass = password;
|
|
|
|
|
std::transform(upperUser.begin(), upperUser.end(), upperUser.begin(), ::toupper);
|
|
|
|
|
std::transform(upperPass.begin(), upperPass.end(), upperPass.begin(), ::toupper);
|
|
|
|
|
std::string combined = upperUser + ":" + upperPass;
|
|
|
|
|
auto hash = auth::Crypto::sha1(combined);
|
|
|
|
|
savedPasswordHash = hexEncode(hash);
|
|
|
|
|
}
|
|
|
|
|
saveLoginInfo();
|
|
|
|
|
|
2026-02-02 12:24:50 -08:00
|
|
|
// Call success callback
|
|
|
|
|
if (onSuccess) {
|
|
|
|
|
onSuccess();
|
|
|
|
|
}
|
|
|
|
|
} else if (state == auth::AuthState::FAILED) {
|
2026-02-05 12:17:09 -08:00
|
|
|
if (!failureReason.empty()) {
|
|
|
|
|
setStatus(failureReason, true);
|
|
|
|
|
} else {
|
|
|
|
|
setStatus("Authentication failed", true);
|
|
|
|
|
}
|
2026-02-02 12:24:50 -08:00
|
|
|
authenticating = false;
|
2026-02-05 12:17:09 -08:00
|
|
|
} else if (authTimer >= AUTH_TIMEOUT) {
|
|
|
|
|
setStatus("Connection timed out - server did not respond", true);
|
|
|
|
|
authenticating = false;
|
|
|
|
|
authHandler.disconnect();
|
2026-02-02 12:24:50 -08:00
|
|
|
}
|
|
|
|
|
} else {
|
2026-02-07 12:23:03 -08:00
|
|
|
if (ImGui::Button("Connect", ImVec2(160, 40))) {
|
2026-02-02 12:24:50 -08:00
|
|
|
attemptAuth(authHandler);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ImGui::SameLine();
|
2026-02-07 12:23:03 -08:00
|
|
|
if (ImGui::Button("Clear", ImVec2(160, 40))) {
|
2026-02-02 12:24:50 -08:00
|
|
|
statusMessage.clear();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ImGui::Spacing();
|
|
|
|
|
ImGui::Separator();
|
|
|
|
|
ImGui::Spacing();
|
|
|
|
|
|
|
|
|
|
// Info text
|
|
|
|
|
ImGui::TextWrapped("Enter your account credentials to connect to the authentication server.");
|
|
|
|
|
ImGui::TextWrapped("Default port is 3724.");
|
|
|
|
|
|
|
|
|
|
ImGui::End();
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-05 15:44:42 -08:00
|
|
|
void AuthScreen::stopLoginMusic() {
|
|
|
|
|
if (!musicPlaying) return;
|
|
|
|
|
auto& app = core::Application::getInstance();
|
|
|
|
|
auto* renderer = app.getRenderer();
|
|
|
|
|
if (!renderer) return;
|
|
|
|
|
auto* music = renderer->getMusicManager();
|
|
|
|
|
if (!music) return;
|
|
|
|
|
music->stopMusic(500.0f);
|
|
|
|
|
musicPlaying = false;
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-02 12:24:50 -08:00
|
|
|
void AuthScreen::attemptAuth(auth::AuthHandler& authHandler) {
|
|
|
|
|
// Validate inputs
|
|
|
|
|
if (strlen(username) == 0) {
|
|
|
|
|
setStatus("Username cannot be empty", true);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-05 15:09:16 -08:00
|
|
|
// Check if using stored hash (password field contains placeholder)
|
|
|
|
|
bool useHash = usingStoredHash && std::strcmp(password, PASSWORD_PLACEHOLDER) == 0;
|
|
|
|
|
|
|
|
|
|
if (!useHash && strlen(password) == 0) {
|
2026-02-02 12:24:50 -08:00
|
|
|
setStatus("Password cannot be empty", true);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (strlen(hostname) == 0) {
|
|
|
|
|
setStatus("Hostname cannot be empty", true);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Attempt connection
|
|
|
|
|
std::stringstream ss;
|
|
|
|
|
ss << "Connecting to " << hostname << ":" << port << "...";
|
|
|
|
|
setStatus(ss.str(), false);
|
|
|
|
|
|
2026-02-05 12:17:09 -08:00
|
|
|
// Wire up failure callback to capture specific error reason
|
|
|
|
|
failureReason.clear();
|
|
|
|
|
authHandler.setOnFailure([this](const std::string& reason) {
|
|
|
|
|
failureReason = reason;
|
|
|
|
|
});
|
|
|
|
|
|
2026-02-12 22:56:36 -08:00
|
|
|
// Configure client version from active expansion profile
|
|
|
|
|
auto* reg = core::Application::getInstance().getExpansionRegistry();
|
|
|
|
|
if (reg) {
|
|
|
|
|
auto* profile = reg->getActive();
|
|
|
|
|
if (profile) {
|
|
|
|
|
auth::ClientInfo info;
|
|
|
|
|
info.majorVersion = profile->majorVersion;
|
|
|
|
|
info.minorVersion = profile->minorVersion;
|
|
|
|
|
info.patchVersion = profile->patchVersion;
|
|
|
|
|
info.build = profile->build;
|
|
|
|
|
info.protocolVersion = profile->protocolVersion;
|
|
|
|
|
authHandler.setClientInfo(info);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-02 12:24:50 -08:00
|
|
|
if (authHandler.connect(hostname, static_cast<uint16_t>(port))) {
|
|
|
|
|
authenticating = true;
|
2026-02-05 12:17:09 -08:00
|
|
|
authTimer = 0.0f;
|
2026-02-02 12:24:50 -08:00
|
|
|
setStatus("Connected, authenticating...", false);
|
|
|
|
|
|
2026-02-05 12:25:00 -08:00
|
|
|
// Save login info for next session
|
|
|
|
|
saveLoginInfo();
|
|
|
|
|
|
2026-02-02 12:24:50 -08:00
|
|
|
// Send authentication credentials
|
2026-02-05 15:09:16 -08:00
|
|
|
if (useHash) {
|
|
|
|
|
auto hashBytes = hexDecode(savedPasswordHash);
|
|
|
|
|
authHandler.authenticateWithHash(username, hashBytes);
|
|
|
|
|
} else {
|
|
|
|
|
usingStoredHash = false;
|
|
|
|
|
authHandler.authenticate(username, password);
|
|
|
|
|
}
|
2026-02-02 12:24:50 -08:00
|
|
|
} else {
|
2026-02-05 12:17:09 -08:00
|
|
|
std::stringstream errSs;
|
|
|
|
|
errSs << "Failed to connect to " << hostname << ":" << port
|
|
|
|
|
<< " - check that the server is online and the address is correct";
|
|
|
|
|
setStatus(errSs.str(), true);
|
2026-02-02 12:24:50 -08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void AuthScreen::setStatus(const std::string& message, bool isError) {
|
|
|
|
|
statusMessage = message;
|
|
|
|
|
statusIsError = isError;
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-05 12:25:00 -08:00
|
|
|
std::string AuthScreen::getConfigPath() {
|
|
|
|
|
std::string dir;
|
|
|
|
|
#ifdef _WIN32
|
|
|
|
|
const char* appdata = std::getenv("APPDATA");
|
|
|
|
|
dir = appdata ? std::string(appdata) + "\\wowee" : ".";
|
|
|
|
|
#else
|
|
|
|
|
const char* home = std::getenv("HOME");
|
|
|
|
|
dir = home ? std::string(home) + "/.wowee" : ".";
|
|
|
|
|
#endif
|
|
|
|
|
return dir + "/login.cfg";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void AuthScreen::saveLoginInfo() {
|
|
|
|
|
std::string path = getConfigPath();
|
|
|
|
|
std::filesystem::path dir = std::filesystem::path(path).parent_path();
|
|
|
|
|
std::error_code ec;
|
|
|
|
|
std::filesystem::create_directories(dir, ec);
|
|
|
|
|
|
|
|
|
|
std::ofstream out(path);
|
|
|
|
|
if (!out.is_open()) {
|
|
|
|
|
LOG_WARNING("Could not save login info to ", path);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
out << "hostname=" << hostname << "\n";
|
|
|
|
|
out << "port=" << port << "\n";
|
|
|
|
|
out << "username=" << username << "\n";
|
2026-02-05 15:09:16 -08:00
|
|
|
if (!savedPasswordHash.empty()) {
|
|
|
|
|
out << "password_hash=" << savedPasswordHash << "\n";
|
|
|
|
|
}
|
2026-02-12 22:56:36 -08:00
|
|
|
// Save active expansion id
|
|
|
|
|
auto* expReg = core::Application::getInstance().getExpansionRegistry();
|
|
|
|
|
if (expReg && !expReg->getActiveId().empty()) {
|
|
|
|
|
out << "expansion=" << expReg->getActiveId() << "\n";
|
|
|
|
|
}
|
2026-02-05 12:25:00 -08:00
|
|
|
|
|
|
|
|
LOG_INFO("Login info saved to ", path);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void AuthScreen::loadLoginInfo() {
|
|
|
|
|
std::string path = getConfigPath();
|
|
|
|
|
std::ifstream in(path);
|
|
|
|
|
if (!in.is_open()) return;
|
|
|
|
|
|
|
|
|
|
std::string line;
|
|
|
|
|
while (std::getline(in, line)) {
|
|
|
|
|
size_t eq = line.find('=');
|
|
|
|
|
if (eq == std::string::npos) continue;
|
|
|
|
|
std::string key = line.substr(0, eq);
|
|
|
|
|
std::string val = line.substr(eq + 1);
|
|
|
|
|
|
|
|
|
|
if (key == "hostname" && !val.empty()) {
|
|
|
|
|
strncpy(hostname, val.c_str(), sizeof(hostname) - 1);
|
|
|
|
|
hostname[sizeof(hostname) - 1] = '\0';
|
|
|
|
|
} else if (key == "port") {
|
|
|
|
|
try { port = std::stoi(val); } catch (...) {}
|
|
|
|
|
} else if (key == "username" && !val.empty()) {
|
|
|
|
|
strncpy(username, val.c_str(), sizeof(username) - 1);
|
|
|
|
|
username[sizeof(username) - 1] = '\0';
|
2026-02-05 15:09:16 -08:00
|
|
|
} else if (key == "password_hash" && !val.empty()) {
|
|
|
|
|
savedPasswordHash = val;
|
2026-02-12 22:56:36 -08:00
|
|
|
} else if (key == "expansion" && !val.empty()) {
|
|
|
|
|
auto* expReg = core::Application::getInstance().getExpansionRegistry();
|
|
|
|
|
if (expReg && expReg->setActive(val)) {
|
|
|
|
|
// Find matching index
|
|
|
|
|
auto& profiles = expReg->getAllProfiles();
|
|
|
|
|
for (int i = 0; i < static_cast<int>(profiles.size()); ++i) {
|
|
|
|
|
if (profiles[i].id == val) { expansionIndex = i; break; }
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-02-05 12:25:00 -08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-05 15:09:16 -08:00
|
|
|
// If we have a saved hash, fill password with placeholder
|
|
|
|
|
if (!savedPasswordHash.empty()) {
|
|
|
|
|
strncpy(password, PASSWORD_PLACEHOLDER, sizeof(password) - 1);
|
|
|
|
|
password[sizeof(password) - 1] = '\0';
|
|
|
|
|
usingStoredHash = true;
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-05 12:25:00 -08:00
|
|
|
LOG_INFO("Login info loaded from ", path);
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-02 12:24:50 -08:00
|
|
|
}} // namespace wowee::ui
|