mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-04-17 01:23:51 +00:00
Add expansion DBC CSVs, Turtle support, and server-specific login
This commit is contained in:
parent
7092844b5e
commit
f247d53309
139 changed files with 676758 additions and 91 deletions
|
|
@ -179,6 +179,9 @@ bool Application::initialize() {
|
|||
if (expansionRegistry_) {
|
||||
auto* profile = expansionRegistry_->getActive();
|
||||
if (profile && !profile->dataPath.empty()) {
|
||||
// Enable expansion-specific CSV DBC lookup (Data/expansions/<id>/db/*.csv).
|
||||
assetManager->setExpansionDataPath(profile->dataPath);
|
||||
|
||||
std::string expansionManifest = profile->dataPath + "/manifest.json";
|
||||
if (std::filesystem::exists(expansionManifest)) {
|
||||
assetPath = profile->dataPath;
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
#include <algorithm>
|
||||
#include <cstdlib>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <limits>
|
||||
|
||||
#include "stb_image.h"
|
||||
|
|
@ -241,6 +242,11 @@ BLPImage AssetManager::tryLoadPngOverride(const std::string& normalizedPath) con
|
|||
return image;
|
||||
}
|
||||
|
||||
void AssetManager::setExpansionDataPath(const std::string& path) {
|
||||
expansionDataPath_ = path;
|
||||
LOG_INFO("Expansion data path for CSV DBCs: ", expansionDataPath_);
|
||||
}
|
||||
|
||||
std::shared_ptr<DBCFile> AssetManager::loadDBC(const std::string& name) {
|
||||
if (!initialized) {
|
||||
LOG_ERROR("AssetManager not initialized");
|
||||
|
|
@ -255,17 +261,44 @@ std::shared_ptr<DBCFile> AssetManager::loadDBC(const std::string& name) {
|
|||
|
||||
LOG_DEBUG("Loading DBC: ", name);
|
||||
|
||||
std::string dbcPath = "DBFilesClient\\" + name;
|
||||
std::vector<uint8_t> dbcData;
|
||||
|
||||
std::vector<uint8_t> dbcData = readFile(dbcPath);
|
||||
// Try expansion-specific CSV first (e.g. Data/expansions/wotlk/db/Spell.csv)
|
||||
if (!expansionDataPath_.empty()) {
|
||||
// Derive CSV name from DBC name: "Spell.dbc" -> "Spell.csv"
|
||||
std::string baseName = name;
|
||||
auto dot = baseName.rfind('.');
|
||||
if (dot != std::string::npos) {
|
||||
baseName = baseName.substr(0, dot);
|
||||
}
|
||||
std::string csvPath = expansionDataPath_ + "/db/" + baseName + ".csv";
|
||||
if (std::filesystem::exists(csvPath)) {
|
||||
std::ifstream f(csvPath, std::ios::binary | std::ios::ate);
|
||||
if (f) {
|
||||
auto size = f.tellg();
|
||||
if (size > 0) {
|
||||
f.seekg(0);
|
||||
dbcData.resize(static_cast<size_t>(size));
|
||||
f.read(reinterpret_cast<char*>(dbcData.data()), size);
|
||||
LOG_DEBUG("Found CSV DBC: ", csvPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fall back to manifest (binary DBC from extracted MPQs)
|
||||
if (dbcData.empty()) {
|
||||
LOG_WARNING("DBC not found: ", dbcPath);
|
||||
return nullptr;
|
||||
std::string dbcPath = "DBFilesClient\\" + name;
|
||||
dbcData = readFile(dbcPath);
|
||||
if (dbcData.empty()) {
|
||||
LOG_WARNING("DBC not found: ", name);
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
auto dbc = std::make_shared<DBCFile>();
|
||||
if (!dbc->load(dbcData)) {
|
||||
LOG_ERROR("Failed to load DBC: ", dbcPath);
|
||||
LOG_ERROR("Failed to load DBC: ", name);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,14 +1,42 @@
|
|||
#include "pipeline/dbc_loader.hpp"
|
||||
#include "core/logger.hpp"
|
||||
#include <cctype>
|
||||
#include <cstring>
|
||||
#include <set>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
|
||||
namespace wowee {
|
||||
namespace pipeline {
|
||||
|
||||
namespace {
|
||||
std::string trimAscii(std::string s) {
|
||||
size_t b = 0;
|
||||
while (b < s.size() && std::isspace(static_cast<unsigned char>(s[b]))) {
|
||||
++b;
|
||||
}
|
||||
size_t e = s.size();
|
||||
while (e > b && std::isspace(static_cast<unsigned char>(s[e - 1]))) {
|
||||
--e;
|
||||
}
|
||||
return s.substr(b, e - b);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
DBCFile::DBCFile() = default;
|
||||
DBCFile::~DBCFile() = default;
|
||||
|
||||
bool DBCFile::load(const std::vector<uint8_t>& dbcData) {
|
||||
if (dbcData.empty()) {
|
||||
LOG_ERROR("DBC data is empty");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Detect CSV format: starts with '#'
|
||||
if (dbcData[0] == '#') {
|
||||
return loadCSV(dbcData);
|
||||
}
|
||||
|
||||
if (dbcData.size() < sizeof(DBCHeader)) {
|
||||
LOG_ERROR("DBC data too small for header");
|
||||
return false;
|
||||
|
|
@ -158,5 +186,140 @@ void DBCFile::buildIdCache() const {
|
|||
LOG_DEBUG("Built DBC ID cache with ", idToIndexCache.size(), " entries");
|
||||
}
|
||||
|
||||
bool DBCFile::loadCSV(const std::vector<uint8_t>& csvData) {
|
||||
std::string text(reinterpret_cast<const char*>(csvData.data()), csvData.size());
|
||||
std::istringstream stream(text);
|
||||
std::string line;
|
||||
|
||||
// --- Parse metadata line: # fields=N strings=I,J,K ---
|
||||
if (!std::getline(stream, line) || line.empty() || line[0] != '#') {
|
||||
LOG_ERROR("CSV DBC: missing metadata line");
|
||||
return false;
|
||||
}
|
||||
|
||||
fieldCount = 0;
|
||||
std::set<uint32_t> stringCols;
|
||||
|
||||
// Parse "fields=N"
|
||||
auto fieldsPos = line.find("fields=");
|
||||
if (fieldsPos != std::string::npos) {
|
||||
try {
|
||||
fieldCount = static_cast<uint32_t>(std::stoul(line.substr(fieldsPos + 7)));
|
||||
} catch (...) {
|
||||
fieldCount = 0;
|
||||
}
|
||||
}
|
||||
if (fieldCount == 0) {
|
||||
LOG_ERROR("CSV DBC: invalid field count");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Parse "strings=I,J,K"
|
||||
auto stringsPos = line.find("strings=");
|
||||
if (stringsPos != std::string::npos) {
|
||||
std::istringstream ss(line.substr(stringsPos + 8));
|
||||
std::string tok;
|
||||
while (std::getline(ss, tok, ',')) {
|
||||
tok = trimAscii(tok);
|
||||
if (!tok.empty()) {
|
||||
try {
|
||||
stringCols.insert(static_cast<uint32_t>(std::stoul(tok)));
|
||||
} catch (...) {
|
||||
LOG_WARNING("CSV DBC: invalid string column index token: '", tok, "'");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
recordSize = fieldCount * 4;
|
||||
|
||||
// --- Build string block with initial null byte ---
|
||||
stringBlock.clear();
|
||||
stringBlock.push_back(0); // offset 0 = empty string
|
||||
|
||||
// --- Parse data rows ---
|
||||
struct RowData {
|
||||
std::vector<uint32_t> fields;
|
||||
};
|
||||
std::vector<RowData> rows;
|
||||
|
||||
while (std::getline(stream, line)) {
|
||||
if (line.empty()) continue;
|
||||
|
||||
RowData row;
|
||||
row.fields.resize(fieldCount, 0);
|
||||
|
||||
uint32_t col = 0;
|
||||
size_t pos = 0;
|
||||
|
||||
while (col < fieldCount && pos < line.size()) {
|
||||
if (stringCols.count(col) && pos < line.size() && line[pos] == '"') {
|
||||
// Quoted string field
|
||||
pos++; // skip opening quote
|
||||
std::string str;
|
||||
while (pos < line.size()) {
|
||||
if (line[pos] == '"') {
|
||||
if (pos + 1 < line.size() && line[pos + 1] == '"') {
|
||||
str += '"'; // escaped quote
|
||||
pos += 2;
|
||||
} else {
|
||||
pos++; // closing quote
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
str += line[pos++];
|
||||
}
|
||||
}
|
||||
// Skip comma after closing quote
|
||||
if (pos < line.size() && line[pos] == ',') pos++;
|
||||
|
||||
// Store string in string block
|
||||
if (str.empty()) {
|
||||
row.fields[col] = 0; // points to empty string at offset 0
|
||||
} else {
|
||||
uint32_t offset = static_cast<uint32_t>(stringBlock.size());
|
||||
stringBlock.insert(stringBlock.end(), str.begin(), str.end());
|
||||
stringBlock.push_back(0); // null terminator
|
||||
row.fields[col] = offset;
|
||||
}
|
||||
} else {
|
||||
// Numeric field — read until comma or end of line
|
||||
size_t end = line.find(',', pos);
|
||||
if (end == std::string::npos) end = line.size();
|
||||
std::string tok = line.substr(pos, end - pos);
|
||||
if (!tok.empty()) {
|
||||
row.fields[col] = static_cast<uint32_t>(std::stoul(tok));
|
||||
}
|
||||
pos = (end < line.size()) ? end + 1 : line.size();
|
||||
}
|
||||
col++;
|
||||
}
|
||||
|
||||
rows.push_back(std::move(row));
|
||||
}
|
||||
|
||||
// --- Build record data (binary layout identical to WDBC) ---
|
||||
recordCount = static_cast<uint32_t>(rows.size());
|
||||
stringBlockSize = static_cast<uint32_t>(stringBlock.size());
|
||||
|
||||
recordData.resize(static_cast<size_t>(recordCount) * recordSize);
|
||||
for (uint32_t i = 0; i < recordCount; ++i) {
|
||||
uint8_t* dst = recordData.data() + static_cast<size_t>(i) * recordSize;
|
||||
for (uint32_t f = 0; f < fieldCount; ++f) {
|
||||
uint32_t val = rows[i].fields[f];
|
||||
std::memcpy(dst + f * 4, &val, 4);
|
||||
}
|
||||
}
|
||||
|
||||
loaded = true;
|
||||
idCacheBuilt = false;
|
||||
idToIndexCache.clear();
|
||||
|
||||
LOG_DEBUG("Loaded CSV DBC: ", recordCount, " records, ",
|
||||
fieldCount, " fields, ", stringCols.size(), " string cols, ",
|
||||
stringBlockSize, " string bytes");
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace pipeline
|
||||
} // namespace wowee
|
||||
|
|
|
|||
|
|
@ -12,14 +12,25 @@
|
|||
#include <fstream>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <cstdio>
|
||||
#include <filesystem>
|
||||
#include <algorithm>
|
||||
#include <iomanip>
|
||||
#include <array>
|
||||
#include <random>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace wowee { namespace ui {
|
||||
|
||||
static std::string trimAscii(std::string s) {
|
||||
auto isSpace = [](unsigned char c) { return c == ' ' || c == '\t' || c == '\r' || c == '\n'; };
|
||||
size_t b = 0;
|
||||
while (b < s.size() && isSpace(static_cast<unsigned char>(s[b]))) ++b;
|
||||
size_t e = s.size();
|
||||
while (e > b && isSpace(static_cast<unsigned char>(s[e - 1]))) --e;
|
||||
return s.substr(b, e - b);
|
||||
}
|
||||
|
||||
static std::string hexEncode(const std::vector<uint8_t>& data) {
|
||||
std::ostringstream ss;
|
||||
for (uint8_t b : data)
|
||||
|
|
@ -39,6 +50,107 @@ static std::vector<uint8_t> hexDecode(const std::string& hex) {
|
|||
AuthScreen::AuthScreen() {
|
||||
}
|
||||
|
||||
std::string AuthScreen::makeServerKey(const std::string& host, int port) {
|
||||
std::ostringstream ss;
|
||||
ss << host << ":" << port;
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
std::string AuthScreen::currentExpansionId() const {
|
||||
auto* reg = core::Application::getInstance().getExpansionRegistry();
|
||||
if (reg && reg->getActive()) {
|
||||
return reg->getActive()->id;
|
||||
}
|
||||
return "wotlk";
|
||||
}
|
||||
|
||||
void AuthScreen::selectServerProfile(int index) {
|
||||
if (index < 0 || index >= static_cast<int>(servers_.size())) {
|
||||
selectedServerIndex_ = -1;
|
||||
return;
|
||||
}
|
||||
|
||||
selectedServerIndex_ = index;
|
||||
const auto& s = servers_[index];
|
||||
|
||||
std::snprintf(hostname, sizeof(hostname), "%s", s.hostname.c_str());
|
||||
hostname[sizeof(hostname) - 1] = '\0';
|
||||
port = s.port;
|
||||
|
||||
std::snprintf(username, sizeof(username), "%s", s.username.c_str());
|
||||
username[sizeof(username) - 1] = '\0';
|
||||
|
||||
savedPasswordHash = s.passwordHash;
|
||||
usingStoredHash = !savedPasswordHash.empty();
|
||||
if (usingStoredHash) {
|
||||
std::snprintf(password, sizeof(password), "%s", PASSWORD_PLACEHOLDER);
|
||||
password[sizeof(password) - 1] = '\0';
|
||||
} else {
|
||||
password[0] = '\0';
|
||||
}
|
||||
|
||||
if (!s.expansionId.empty()) {
|
||||
auto* expReg = core::Application::getInstance().getExpansionRegistry();
|
||||
if (expReg && expReg->setActive(s.expansionId)) {
|
||||
auto& profiles = expReg->getAllProfiles();
|
||||
for (int i = 0; i < static_cast<int>(profiles.size()); ++i) {
|
||||
if (profiles[i].id == s.expansionId) { expansionIndex = i; break; }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AuthScreen::upsertCurrentServerProfile(bool includePasswordHash) {
|
||||
const std::string hostStr = hostname;
|
||||
if (hostStr.empty() || port <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const std::string key = makeServerKey(hostStr, port);
|
||||
int foundIndex = -1;
|
||||
for (int i = 0; i < static_cast<int>(servers_.size()); ++i) {
|
||||
if (makeServerKey(servers_[i].hostname, servers_[i].port) == key) {
|
||||
foundIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ServerProfile s;
|
||||
s.hostname = hostStr;
|
||||
s.port = port;
|
||||
s.username = username;
|
||||
s.expansionId = currentExpansionId();
|
||||
if (includePasswordHash && !savedPasswordHash.empty()) {
|
||||
s.passwordHash = savedPasswordHash;
|
||||
} else if (foundIndex >= 0) {
|
||||
// Preserve existing stored hash if we aren't updating it.
|
||||
s.passwordHash = servers_[foundIndex].passwordHash;
|
||||
}
|
||||
|
||||
if (foundIndex >= 0) {
|
||||
servers_[foundIndex] = std::move(s);
|
||||
selectedServerIndex_ = foundIndex;
|
||||
} else {
|
||||
servers_.push_back(std::move(s));
|
||||
selectedServerIndex_ = static_cast<int>(servers_.size()) - 1;
|
||||
}
|
||||
|
||||
// Keep deterministic ordering (and stable combo ordering) across runs.
|
||||
std::sort(servers_.begin(), servers_.end(),
|
||||
[](const ServerProfile& a, const ServerProfile& b) {
|
||||
if (a.hostname != b.hostname) return a.hostname < b.hostname;
|
||||
return a.port < b.port;
|
||||
});
|
||||
|
||||
// Fix up index after sort.
|
||||
for (int i = 0; i < static_cast<int>(servers_.size()); ++i) {
|
||||
if (makeServerKey(servers_[i].hostname, servers_[i].port) == key) {
|
||||
selectedServerIndex_ = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AuthScreen::render(auth::AuthHandler& authHandler) {
|
||||
// Load saved login info on first render
|
||||
if (!loginInfoLoaded) {
|
||||
|
|
@ -144,8 +256,42 @@ void AuthScreen::render(auth::AuthHandler& authHandler) {
|
|||
|
||||
// Server settings
|
||||
ImGui::Text("Server Settings");
|
||||
ImGui::InputText("Hostname", hostname, sizeof(hostname));
|
||||
ImGui::InputInt("Port", &port);
|
||||
{
|
||||
std::string preview;
|
||||
if (selectedServerIndex_ >= 0 && selectedServerIndex_ < static_cast<int>(servers_.size())) {
|
||||
preview = makeServerKey(servers_[selectedServerIndex_].hostname, servers_[selectedServerIndex_].port);
|
||||
} else {
|
||||
preview = makeServerKey(hostname, port) + " (custom)";
|
||||
}
|
||||
|
||||
if (ImGui::BeginCombo("Server", preview.c_str())) {
|
||||
bool customSelected = (selectedServerIndex_ < 0);
|
||||
if (ImGui::Selectable("Custom...", customSelected)) {
|
||||
selectedServerIndex_ = -1;
|
||||
}
|
||||
if (customSelected) ImGui::SetItemDefaultFocus();
|
||||
|
||||
ImGui::Separator();
|
||||
for (int i = 0; i < static_cast<int>(servers_.size()); ++i) {
|
||||
std::string label = makeServerKey(servers_[i].hostname, servers_[i].port);
|
||||
if (!servers_[i].username.empty()) {
|
||||
label += " (" + servers_[i].username + ")";
|
||||
}
|
||||
bool selected = (selectedServerIndex_ == i);
|
||||
if (ImGui::Selectable(label.c_str(), selected)) {
|
||||
selectServerProfile(i);
|
||||
}
|
||||
if (selected) ImGui::SetItemDefaultFocus();
|
||||
}
|
||||
ImGui::EndCombo();
|
||||
}
|
||||
}
|
||||
|
||||
bool hostChanged = ImGui::InputText("Hostname", hostname, sizeof(hostname));
|
||||
bool portChanged = ImGui::InputInt("Port", &port);
|
||||
if (hostChanged || portChanged) {
|
||||
selectedServerIndex_ = -1;
|
||||
}
|
||||
if (port < 1) port = 1;
|
||||
if (port > 65535) port = 65535;
|
||||
|
||||
|
|
@ -231,7 +377,7 @@ void AuthScreen::render(auth::AuthHandler& authHandler) {
|
|||
auto hash = auth::Crypto::sha1(combined);
|
||||
savedPasswordHash = hexEncode(hash);
|
||||
}
|
||||
saveLoginInfo();
|
||||
saveLoginInfo(true);
|
||||
|
||||
// Call success callback
|
||||
if (onSuccess) {
|
||||
|
|
@ -334,7 +480,7 @@ void AuthScreen::attemptAuth(auth::AuthHandler& authHandler) {
|
|||
setStatus("Connected, authenticating...", false);
|
||||
|
||||
// Save login info for next session
|
||||
saveLoginInfo();
|
||||
saveLoginInfo(false);
|
||||
|
||||
// Send authentication credentials
|
||||
if (useHash) {
|
||||
|
|
@ -369,7 +515,9 @@ std::string AuthScreen::getConfigPath() {
|
|||
return dir + "/login.cfg";
|
||||
}
|
||||
|
||||
void AuthScreen::saveLoginInfo() {
|
||||
void AuthScreen::saveLoginInfo(bool includePasswordHash) {
|
||||
upsertCurrentServerProfile(includePasswordHash);
|
||||
|
||||
std::string path = getConfigPath();
|
||||
std::filesystem::path dir = std::filesystem::path(path).parent_path();
|
||||
std::error_code ec;
|
||||
|
|
@ -381,16 +529,18 @@ void AuthScreen::saveLoginInfo() {
|
|||
return;
|
||||
}
|
||||
|
||||
out << "hostname=" << hostname << "\n";
|
||||
out << "port=" << port << "\n";
|
||||
out << "username=" << username << "\n";
|
||||
if (!savedPasswordHash.empty()) {
|
||||
out << "password_hash=" << savedPasswordHash << "\n";
|
||||
}
|
||||
// Save active expansion id
|
||||
auto* expReg = core::Application::getInstance().getExpansionRegistry();
|
||||
if (expReg && !expReg->getActiveId().empty()) {
|
||||
out << "expansion=" << expReg->getActiveId() << "\n";
|
||||
out << "version=2\n";
|
||||
out << "active=" << makeServerKey(hostname, port) << "\n";
|
||||
|
||||
for (const auto& s : servers_) {
|
||||
out << "\n[server " << makeServerKey(s.hostname, s.port) << "]\n";
|
||||
out << "username=" << s.username << "\n";
|
||||
if (!s.passwordHash.empty()) {
|
||||
out << "password_hash=" << s.passwordHash << "\n";
|
||||
}
|
||||
if (!s.expansionId.empty()) {
|
||||
out << "expansion=" << s.expansionId << "\n";
|
||||
}
|
||||
}
|
||||
|
||||
LOG_INFO("Login info saved to ", path);
|
||||
|
|
@ -401,40 +551,130 @@ void AuthScreen::loadLoginInfo() {
|
|||
std::ifstream in(path);
|
||||
if (!in.is_open()) return;
|
||||
|
||||
std::string file((std::istreambuf_iterator<char>(in)), std::istreambuf_iterator<char>());
|
||||
|
||||
// If this looks like the old flat format, migrate it into a single server entry.
|
||||
if (file.find("[server ") == std::string::npos) {
|
||||
std::unordered_map<std::string, std::string> kv;
|
||||
std::istringstream ss(file);
|
||||
std::string line;
|
||||
while (std::getline(ss, line)) {
|
||||
line = trimAscii(line);
|
||||
if (line.empty() || line[0] == '#') continue;
|
||||
size_t eq = line.find('=');
|
||||
if (eq == std::string::npos) continue;
|
||||
kv[trimAscii(line.substr(0, eq))] = trimAscii(line.substr(eq + 1));
|
||||
}
|
||||
|
||||
std::string host = kv["hostname"];
|
||||
int p = 3724;
|
||||
try { if (!kv["port"].empty()) p = std::stoi(kv["port"]); } catch (...) {}
|
||||
if (!host.empty()) {
|
||||
ServerProfile s;
|
||||
s.hostname = host;
|
||||
s.port = p;
|
||||
s.username = kv["username"];
|
||||
s.passwordHash = kv["password_hash"];
|
||||
s.expansionId = kv["expansion"];
|
||||
servers_.push_back(std::move(s));
|
||||
selectServerProfile(0);
|
||||
}
|
||||
|
||||
LOG_INFO("Login info loaded from ", path, " (migrated v1 -> v2)");
|
||||
return;
|
||||
}
|
||||
|
||||
servers_.clear();
|
||||
selectedServerIndex_ = -1;
|
||||
|
||||
std::string activeKey;
|
||||
ServerProfile current;
|
||||
bool inServer = false;
|
||||
|
||||
auto flushServer = [&]() {
|
||||
if (!inServer) return;
|
||||
if (!current.hostname.empty() && current.port > 0) {
|
||||
servers_.push_back(current);
|
||||
}
|
||||
current = ServerProfile{};
|
||||
inServer = false;
|
||||
};
|
||||
|
||||
std::istringstream ss(file);
|
||||
std::string line;
|
||||
while (std::getline(in, line)) {
|
||||
while (std::getline(ss, line)) {
|
||||
line = trimAscii(line);
|
||||
if (line.empty() || line[0] == '#') continue;
|
||||
|
||||
if (line.front() == '[' && line.back() == ']') {
|
||||
flushServer();
|
||||
std::string inside = line.substr(1, line.size() - 2);
|
||||
inside = trimAscii(inside);
|
||||
const std::string prefix = "server ";
|
||||
if (inside.rfind(prefix, 0) == 0) {
|
||||
std::string key = trimAscii(inside.substr(prefix.size()));
|
||||
// Parse host:port (split on last ':', allow [ipv6]:port).
|
||||
std::string hostPart = key;
|
||||
int portPart = 3724;
|
||||
if (!key.empty() && key.front() == '[') {
|
||||
auto rb = key.find(']');
|
||||
if (rb != std::string::npos) {
|
||||
hostPart = key.substr(1, rb - 1);
|
||||
auto colon = key.find(':', rb);
|
||||
if (colon != std::string::npos) {
|
||||
try { portPart = std::stoi(key.substr(colon + 1)); } catch (...) {}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
auto colon = key.rfind(':');
|
||||
if (colon != std::string::npos) {
|
||||
hostPart = key.substr(0, colon);
|
||||
try { portPart = std::stoi(key.substr(colon + 1)); } catch (...) {}
|
||||
}
|
||||
}
|
||||
|
||||
current.hostname = hostPart;
|
||||
current.port = portPart;
|
||||
inServer = true;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
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);
|
||||
std::string key = trimAscii(line.substr(0, eq));
|
||||
std::string val = trimAscii(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';
|
||||
} else if (key == "password_hash" && !val.empty()) {
|
||||
savedPasswordHash = val;
|
||||
} 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; }
|
||||
if (!inServer) {
|
||||
if (key == "active") activeKey = val;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (key == "username") current.username = val;
|
||||
else if (key == "password_hash") current.passwordHash = val;
|
||||
else if (key == "expansion") current.expansionId = val;
|
||||
}
|
||||
flushServer();
|
||||
|
||||
if (!servers_.empty()) {
|
||||
std::sort(servers_.begin(), servers_.end(),
|
||||
[](const ServerProfile& a, const ServerProfile& b) {
|
||||
if (a.hostname != b.hostname) return a.hostname < b.hostname;
|
||||
return a.port < b.port;
|
||||
});
|
||||
|
||||
if (!activeKey.empty()) {
|
||||
for (int i = 0; i < static_cast<int>(servers_.size()); ++i) {
|
||||
if (makeServerKey(servers_[i].hostname, servers_[i].port) == activeKey) {
|
||||
selectServerProfile(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
if (selectedServerIndex_ < 0) {
|
||||
selectServerProfile(0);
|
||||
}
|
||||
}
|
||||
|
||||
LOG_INFO("Login info loaded from ", path);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue