33 wchar_t buf[MAX_PATH]{};
34 GetModuleFileNameW(
nullptr, buf, MAX_PATH);
35 return std::filesystem::path(buf).parent_path();
37 return std::filesystem::current_path();
57static bool downloadFile(
const std::string& url,
const std::filesystem::path& dest,
58 const std::string& label,
AppState& app,
59 bool replaceSeparators =
false)
61 LOG_INFO <<
"Downloading " << label <<
"...";
67 LOG_ERROR <<
"Failed to download " << label <<
": " << resp.error;
68 setLoadStatus(
"Failed to download " + label +
": " + resp.error, app);
72 std::string content = resp.body;
73 if (replaceSeparators)
74 std::replace(content.begin(), content.end(),
' ',
';');
76 std::ofstream file(dest, std::ios::binary);
79 LOG_ERROR <<
"Failed to write " << label <<
" to " << dest.string();
84 file.write(content.data(), content.size());
86 LOG_INFO << label <<
" saved to " << dest.string();
92 namespace fs = std::filesystem;
95 const fs::path listfilePath = appDir /
"listfile.csv";
96 const fs::path keysPath = appDir /
"extraEncryptionKeys.csv";
99 const bool listfileMissing = !fs::exists(listfilePath, ec) || fs::file_size(listfilePath, ec) == 0;
100 const bool keysMissing = !fs::exists(keysPath, ec) || fs::file_size(keysPath, ec) == 0;
104 if (!
downloadFile(
"https://github.com/wowdev/wow-listfile/releases/latest/download/community-listfile.csv",
105 listfilePath,
"listfile.csv", app))
111 if (!
downloadFile(
"https://raw.githubusercontent.com/wowdev/TACTKeys/master/WoW.txt",
112 keysPath,
"extraEncryptionKeys.csv", app,
true))
117 const fs::path dbdDir = appDir /
"games" /
"wow" /
"dbdefs";
118 fs::create_directories(dbdDir, ec);
127 LOG_INFO <<
"Initializing Databases...";
130 namespace fs = std::filesystem;
132 const fs::path cachePath = appDir /
"wowdb.sqlite";
133 const fs::path versionPath = appDir /
"wowdb.version";
136 bool cacheValid =
false;
142 fs::exists(cachePath, ec) && fs::file_size(cachePath, ec) > 0 &&
143 fs::exists(versionPath, ec))
145 std::ifstream vf(versionPath);
146 std::string cachedVersion;
147 if (std::getline(vf, cachedVersion) && cachedVersion == currentVersion)
150 LOG_INFO <<
"Database cache is valid for version " << currentVersion;
156 LOG_INFO <<
"Database cache miss — will rebuild from DB2 files.";
157 fs::remove(cachePath, ec);
158 fs::remove(versionPath, ec);
167 const fs::path dbdDir = appDir /
"games" /
"wow" /
"dbdefs";
170 "https://raw.githubusercontent.com/wowdev/WoWDBDefs/refs/heads/master/definitions/%s.dbd");
173 "https://raw.githubusercontent.com/wowdev/WoWDBDefs/refs/heads/master/manifest.json");
176 LOG_INFO <<
"Attempting on-demand DBD-based database init from" << dbdDir.string();
177 if (!
GAMEDATABASE.initFromDBD(dbdDir.string(), currentVersion))
180 LOG_ERROR <<
"Database initialization failed!";
182 fs::remove(cachePath, ec);
183 fs::remove(versionPath, ec);
187 if (enableDbCache && !cacheValid)
189 std::ofstream vf(versionPath, std::ios::trunc);
190 vf << currentVersion;
191 LOG_INFO <<
"Database cache written for version " << currentVersion;
194 LOG_INFO <<
"Database initialization succeeded.";
197 LOG_INFO <<
"initDatabase: CharTexture::initRegions...";
201 LOG_INFO <<
"initDatabase: RaceInfos::init...";
207 LOG_INFO <<
"initDatabase: skipping Creature table (disabled).";
210 LOG_INFO <<
"initDatabase: skipping Item/ItemSparse loading (disabled).";
212 LOG_INFO <<
"Finished initiating database files.";
224 LOG_ERROR <<
"Could not load WoW Data folder (error "
235 const std::string baseConfigFolder =
"games/wow/";
236 LOG_INFO <<
"Using config folder: " << baseConfigFolder;
241 GAMEDIRECTORY.setProgressCallback([&app](
int current,
int total) {
243 app.
loading.
loadProgress = 0.10f + 0.40f *
static_cast<float>(current) /
static_cast<float>(total);
260 setLoadStatus(
"World of Warcraft loaded successfully.", app);
305 LOG_INFO <<
"World of Warcraft loaded successfully. Version: "
322 namespace fs = std::filesystem;
324 if (path.empty() || !fs::is_directory(path))
326 setLoadStatus(
"Please set a valid WoW Data folder path in Options > Settings.", app);
331 if (path.back() !=
'/' && path.back() !=
'\\')
336 if (lower.find(
"data\\") == std::string::npos && lower.find(
"data/") == std::string::npos)
348 LOG_ERROR <<
"No locale found in WoW folder.";
static void initRegions()
static void init()
Initialise the global race info table from the database.
Describes a detected game installation (locale, version, product).
static Game & instance()
Access the singleton instance (Meyers singleton).
void init(std::unique_ptr< core::GameFolder > folder, std::unique_ptr< core::GameDatabase > db)
Initialise with the given folder and database backends.
void setConfigFolder(const std::string &folder)
void markDirty()
Mark the file tree as dirty so it will be rebuilt on the next draw().
std::filesystem::path getApplicationDirPath()
Return the directory containing the running executable.
void setLoadStatus(const std::string &s, AppState &app)
Thread-safe load-status setters / getters (lock app.loadStatusMutex).
static void initDatabase(AppState &app)
static bool downloadFile(const std::string &url, const std::filesystem::path &dest, const std::string &label, AppState &app, bool replaceSeparators=false)
void pollAsyncLoad(AppState &app)
static void loadWoW(const core::GameConfig &config, AppState &app)
static bool checkAndDownloadSupportFiles(AppState &app)
void launchLoadThread(const core::GameConfig &config, AppState &app)
Spawn the background loading thread for the given config.
std::string getLoadStatus(AppState &app)
void loadWoWThreadFunc(core::GameConfig config, AppState &app)
void beginLoadWoW(AppState &app)
Response Get(const std::string &url, const ProgressCallback &progress=nullptr)
Perform a synchronous HTTP(S) GET request.
std::string toLower(const std::string &s)
void save() const
Persist to Config.ini and save the ImGui layout to disk.
Top-level aggregate of all mutable application state.
std::atomic< bool > loadThreadDone
std::atomic< float > loadProgress
std::atomic< bool > initDB
std::atomic< bool > loadThreadSuccess
std::mutex loadStatusMutex
std::vector< core::GameConfig > pendingConfigs