diff --git a/src/citra/citra.cpp b/src/citra/citra.cpp index 29ce5f898..12a9c55d6 100644 --- a/src/citra/citra.cpp +++ b/src/citra/citra.cpp @@ -115,6 +115,18 @@ static void OnMessageReceived(const Network::ChatEntry& msg) { std::cout << std::endl << msg.nickname << ": " << msg.message << std::endl << std::endl; } +static void InitializeLogging() { + Log::Filter log_filter(Log::Level::Debug); + log_filter.ParseFilterString(Settings::values.log_filter); + Log::SetGlobalFilter(log_filter); + + Log::AddBackend(std::make_unique()); + + const std::string& log_dir = FileUtil::GetUserPath(FileUtil::UserPath::LogDir); + FileUtil::CreateFullPath(log_dir); + Log::AddBackend(std::make_unique(log_dir + LOG_FILE)); +} + /// Application entry point int main(int argc, char** argv) { Config config; @@ -124,14 +136,7 @@ int main(int argc, char** argv) { std::string movie_record; std::string movie_play; - Log::Filter log_filter; - log_filter.ParseFilterString(Settings::values.log_filter); - Log::SetGlobalFilter(log_filter); - - Log::AddBackend(std::make_unique()); - FileUtil::CreateFullPath(FileUtil::GetUserPath(D_LOGS_IDX)); - Log::AddBackend( - std::make_unique(FileUtil::GetUserPath(D_LOGS_IDX) + LOG_FILE)); + InitializeLogging(); char* endarg; #ifdef _WIN32 diff --git a/src/citra/config.cpp b/src/citra/config.cpp index 88ce9463f..d182fce1a 100644 --- a/src/citra/config.cpp +++ b/src/citra/config.cpp @@ -20,7 +20,7 @@ Config::Config() { // TODO: Don't hardcode the path; let the frontend decide where to put the config files. - sdl2_config_loc = FileUtil::GetUserPath(D_CONFIG_IDX) + "sdl2-config.ini"; + sdl2_config_loc = FileUtil::GetUserPath(FileUtil::UserPath::ConfigDir) + "sdl2-config.ini"; sdl2_config = std::make_unique(sdl2_config_loc); Reload(); diff --git a/src/citra_qt/configuration/config.cpp b/src/citra_qt/configuration/config.cpp index 46b5028ef..fb3268d16 100644 --- a/src/citra_qt/configuration/config.cpp +++ b/src/citra_qt/configuration/config.cpp @@ -14,7 +14,7 @@ Config::Config() { // TODO: Don't hardcode the path; let the frontend decide where to put the config files. - qt_config_loc = FileUtil::GetUserPath(D_CONFIG_IDX) + "qt-config.ini"; + qt_config_loc = FileUtil::GetUserPath(FileUtil::UserPath::ConfigDir) + "qt-config.ini"; FileUtil::CreateFullPath(qt_config_loc); qt_config = new QSettings(QString::fromStdString(qt_config_loc), QSettings::IniFormat); diff --git a/src/citra_qt/configuration/configure_debug.cpp b/src/citra_qt/configuration/configure_debug.cpp index 7d879d1e4..99fbf142c 100644 --- a/src/citra_qt/configuration/configure_debug.cpp +++ b/src/citra_qt/configuration/configure_debug.cpp @@ -19,7 +19,7 @@ ConfigureDebug::ConfigureDebug(QWidget* parent) : QWidget(parent), ui(new Ui::Co ui->setupUi(this); this->setConfiguration(); connect(ui->open_log_button, &QPushButton::pressed, []() { - QString path = QString::fromStdString(FileUtil::GetUserPath(D_LOGS_IDX)); + QString path = QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::LogDir)); QDesktopServices::openUrl(QUrl::fromLocalFile(path)); }); ui->toggle_cpu_jit->setEnabled(!Core::System::GetInstance().IsPoweredOn()); diff --git a/src/citra_qt/game_list.cpp b/src/citra_qt/game_list.cpp index 04e2991db..3b7cdc0fc 100644 --- a/src/citra_qt/game_list.cpp +++ b/src/citra_qt/game_list.cpp @@ -715,17 +715,19 @@ void GameListWorker::run() { stop_processing = false; for (UISettings::GameDir& game_dir : game_dirs) { if (game_dir.path == "INSTALLED") { - QString path = QString::fromStdString(FileUtil::GetUserPath(D_SDMC_IDX)) + - "Nintendo " - "3DS/00000000000000000000000000000000/" - "00000000000000000000000000000000/title/00040000"; + QString path = + QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir)) + + "Nintendo " + "3DS/00000000000000000000000000000000/" + "00000000000000000000000000000000/title/00040000"; watch_list.append(path); GameListDir* game_list_dir = new GameListDir(game_dir, GameListItemType::InstalledDir); emit DirEntryReady({game_list_dir}); AddFstEntriesToGameList(path.toStdString(), 2, game_list_dir); } else if (game_dir.path == "SYSTEM") { - QString path = QString::fromStdString(FileUtil::GetUserPath(D_NAND_IDX)) + - "00000000000000000000000000000000/title/00040010"; + QString path = + QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir)) + + "00000000000000000000000000000000/title/00040010"; watch_list.append(path); GameListDir* game_list_dir = new GameListDir(game_dir, GameListItemType::SystemDir); emit DirEntryReady({game_list_dir}); diff --git a/src/citra_qt/game_list_p.h b/src/citra_qt/game_list_p.h index 925757ea8..9fcfe1742 100644 --- a/src/citra_qt/game_list_p.h +++ b/src/citra_qt/game_list_p.h @@ -172,13 +172,13 @@ public: QString second_name = QString::fromStdString(filename + extension); static QRegExp installed_pattern( QString::fromStdString( - FileUtil::GetUserPath(D_SDMC_IDX) + + FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir) + "Nintendo " "3DS/00000000000000000000000000000000/00000000000000000000000000000000/" "title/0004000(0|e)/[0-9a-f]{8}/content/") .replace("\\", "\\\\")); static QRegExp system_pattern( - QString::fromStdString(FileUtil::GetUserPath(D_NAND_IDX) + + QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) + "00000000000000000000000000000000/" "title/00040010/[0-9a-f]{8}/content/") .replace("\\", "\\\\")); diff --git a/src/citra_qt/main.cpp b/src/citra_qt/main.cpp index 43cf839ae..a13378eb7 100644 --- a/src/citra_qt/main.cpp +++ b/src/citra_qt/main.cpp @@ -102,13 +102,18 @@ void GMainWindow::ShowTelemetryCallout() { const int GMainWindow::max_recent_files_item; -GMainWindow::GMainWindow() : config(new Config()), emu_thread(nullptr) { +static void InitializeLogging() { Log::Filter log_filter; log_filter.ParseFilterString(Settings::values.log_filter); Log::SetGlobalFilter(log_filter); - FileUtil::CreateFullPath(FileUtil::GetUserPath(D_LOGS_IDX)); - Log::AddBackend( - std::make_unique(FileUtil::GetUserPath(D_LOGS_IDX) + LOG_FILE)); + + const std::string& log_dir = FileUtil::GetUserPath(FileUtil::UserPath::LogDir); + FileUtil::CreateFullPath(log_dir); + Log::AddBackend(std::make_unique(log_dir + LOG_FILE)); +} + +GMainWindow::GMainWindow() : config(new Config()), emu_thread(nullptr) { + InitializeLogging(); Debugger::ToggleConsole(); Settings::LogSettings(); @@ -880,7 +885,7 @@ void GMainWindow::OnGameListOpenFolder(u64 program_id, GameListOpenTarget target switch (target) { case GameListOpenTarget::SAVE_DATA: { open_target = "Save Data"; - std::string sdmc_dir = FileUtil::GetUserPath(D_SDMC_IDX); + std::string sdmc_dir = FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir); path = FileSys::ArchiveSource_SDSaveData::GetSaveDataPathFor(sdmc_dir, program_id); break; } @@ -931,13 +936,13 @@ void GMainWindow::OnGameListOpenDirectory(QString directory) { QString path; if (directory == "INSTALLED") { path = - QString::fromStdString(FileUtil::GetUserPath(D_SDMC_IDX).c_str() + + QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir).c_str() + std::string("Nintendo " "3DS/00000000000000000000000000000000/" "00000000000000000000000000000000/title/00040000")); } else if (directory == "SYSTEM") { path = - QString::fromStdString(FileUtil::GetUserPath(D_NAND_IDX).c_str() + + QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir).c_str() + std::string("00000000000000000000000000000000/title/00040010")); } else { path = directory; diff --git a/src/common/common_paths.h b/src/common/common_paths.h index f6d9ea303..9b4457d0d 100644 --- a/src/common/common_paths.h +++ b/src/common/common_paths.h @@ -30,7 +30,7 @@ #define USA_DIR "USA" #define JAP_DIR "JAP" -// Subdirs in the User dir returned by GetUserPath(D_USER_IDX) +// Subdirs in the User dir returned by GetUserPath(UserPath::UserDir) #define CONFIG_DIR "config" #define CACHE_DIR "cache" #define SDMC_DIR "sdmc" @@ -39,10 +39,10 @@ #define LOG_DIR "log" // Filenames -// Files in the directory returned by GetUserPath(D_LOGS_IDX) +// Files in the directory returned by GetUserPath(UserPath::LogDir) #define LOG_FILE "citra_log.txt" -// Files in the directory returned by GetUserPath(D_CONFIG_IDX) +// Files in the directory returned by GetUserPath(UserPath::ConfigDir) #define EMU_CONFIG "emu.ini" #define DEBUGGER_CONFIG "debugger.ini" #define LOGGER_CONFIG "logger.ini" diff --git a/src/common/file_util.cpp b/src/common/file_util.cpp index 34cc3136a..40ea885b8 100644 --- a/src/common/file_util.cpp +++ b/src/common/file_util.cpp @@ -4,6 +4,7 @@ #include #include +#include #include "common/assert.h" #include "common/common_funcs.h" #include "common/common_paths.h" @@ -670,67 +671,68 @@ std::string GetSysDirectory() { // Returns a string with a Citra data dir or file in the user's home // directory. To be used in "multi-user" mode (that is, installed). -const std::string& GetUserPath(const unsigned int DirIDX, const std::string& newPath) { - static std::string paths[NUM_PATH_INDICES]; +const std::string& GetUserPath(UserPath path, const std::string& new_path) { + static std::unordered_map paths; + auto& user_path = paths[UserPath::UserDir]; // Set up all paths and files on the first run - if (paths[D_USER_IDX].empty()) { + if (user_path.empty()) { #ifdef _WIN32 - paths[D_USER_IDX] = GetExeDirectory() + DIR_SEP USERDATA_DIR DIR_SEP; - if (!FileUtil::IsDirectory(paths[D_USER_IDX])) { - paths[D_USER_IDX] = AppDataRoamingDirectory() + DIR_SEP EMU_DATA_DIR DIR_SEP; + user_path = GetExeDirectory() + DIR_SEP USERDATA_DIR DIR_SEP; + if (!FileUtil::IsDirectory(user_path)) { + user_path = AppDataRoamingDirectory() + DIR_SEP EMU_DATA_DIR DIR_SEP; } else { LOG_INFO(Common_Filesystem, "Using the local user directory"); } - paths[D_CONFIG_IDX] = paths[D_USER_IDX] + CONFIG_DIR DIR_SEP; - paths[D_CACHE_IDX] = paths[D_USER_IDX] + CACHE_DIR DIR_SEP; + paths.emplace(UserPath::ConfigDir, user_path + CONFIG_DIR DIR_SEP); + paths.emplace(UserPath::CacheDir, user_path + CACHE_DIR DIR_SEP); #else if (FileUtil::Exists(ROOT_DIR DIR_SEP USERDATA_DIR)) { - paths[D_USER_IDX] = ROOT_DIR DIR_SEP USERDATA_DIR DIR_SEP; - paths[D_CONFIG_IDX] = paths[D_USER_IDX] + CONFIG_DIR DIR_SEP; - paths[D_CACHE_IDX] = paths[D_USER_IDX] + CACHE_DIR DIR_SEP; + user_path = ROOT_DIR DIR_SEP USERDATA_DIR DIR_SEP; + paths.emplace(UserPath::ConfigDir, user_path + CONFIG_DIR DIR_SEP); + paths.emplace(UserPath::CacheDir, user_path + CACHE_DIR DIR_SEP); } else { std::string data_dir = GetUserDirectory("XDG_DATA_HOME"); std::string config_dir = GetUserDirectory("XDG_CONFIG_HOME"); std::string cache_dir = GetUserDirectory("XDG_CACHE_HOME"); - paths[D_USER_IDX] = data_dir + DIR_SEP EMU_DATA_DIR DIR_SEP; - paths[D_CONFIG_IDX] = config_dir + DIR_SEP EMU_DATA_DIR DIR_SEP; - paths[D_CACHE_IDX] = cache_dir + DIR_SEP EMU_DATA_DIR DIR_SEP; + user_path = data_dir + DIR_SEP EMU_DATA_DIR DIR_SEP; + paths.emplace(UserPath::ConfigDir, config_dir + DIR_SEP EMU_DATA_DIR DIR_SEP); + paths.emplace(UserPath::CacheDir, cache_dir + DIR_SEP EMU_DATA_DIR DIR_SEP); } #endif - paths[D_SDMC_IDX] = paths[D_USER_IDX] + SDMC_DIR DIR_SEP; - paths[D_NAND_IDX] = paths[D_USER_IDX] + NAND_DIR DIR_SEP; - paths[D_SYSDATA_IDX] = paths[D_USER_IDX] + SYSDATA_DIR DIR_SEP; + paths.emplace(UserPath::SDMCDir, user_path + SDMC_DIR DIR_SEP); + paths.emplace(UserPath::NANDDir, user_path + NAND_DIR DIR_SEP); + paths.emplace(UserPath::SysDataDir, user_path + SYSDATA_DIR DIR_SEP); // TODO: Put the logs in a better location for each OS - paths[D_LOGS_IDX] = paths[D_USER_IDX] + LOG_DIR DIR_SEP; + paths.emplace(UserPath::LogDir, user_path + LOG_DIR DIR_SEP); } - if (!newPath.empty()) { - if (!FileUtil::IsDirectory(newPath)) { - LOG_ERROR(Common_Filesystem, "Invalid path specified {}", newPath); - return paths[DirIDX]; + if (!new_path.empty()) { + if (!FileUtil::IsDirectory(new_path)) { + LOG_ERROR(Common_Filesystem, "Invalid path specified {}", new_path); + return paths[path]; } else { - paths[DirIDX] = newPath; + paths[path] = new_path; } - switch (DirIDX) { - case D_ROOT_IDX: - paths[D_USER_IDX] = paths[D_ROOT_IDX] + DIR_SEP; + switch (path) { + case UserPath::RootDir: + user_path = paths[UserPath::RootDir] + DIR_SEP; break; - case D_USER_IDX: - paths[D_USER_IDX] = paths[D_ROOT_IDX] + DIR_SEP; - paths[D_CONFIG_IDX] = paths[D_USER_IDX] + CONFIG_DIR DIR_SEP; - paths[D_CACHE_IDX] = paths[D_USER_IDX] + CACHE_DIR DIR_SEP; - paths[D_SDMC_IDX] = paths[D_USER_IDX] + SDMC_DIR DIR_SEP; - paths[D_NAND_IDX] = paths[D_USER_IDX] + NAND_DIR DIR_SEP; + case UserPath::UserDir: + user_path = paths[UserPath::RootDir] + DIR_SEP; + paths[UserPath::ConfigDir] = user_path + CONFIG_DIR DIR_SEP; + paths[UserPath::CacheDir] = user_path + CACHE_DIR DIR_SEP; + paths[UserPath::SDMCDir] = user_path + SDMC_DIR DIR_SEP; + paths[UserPath::NANDDir] = user_path + NAND_DIR DIR_SEP; break; } } - return paths[DirIDX]; + return paths[path]; } size_t WriteStringToFile(bool text_file, const std::string& str, const char* filename) { diff --git a/src/common/file_util.h b/src/common/file_util.h index 4b04edb3e..79e956fde 100644 --- a/src/common/file_util.h +++ b/src/common/file_util.h @@ -17,21 +17,20 @@ #include "common/string_util.h" #endif -// User directory indices for GetUserPath -enum { - D_USER_IDX, - D_ROOT_IDX, - D_CONFIG_IDX, - D_CACHE_IDX, - D_SDMC_IDX, - D_NAND_IDX, - D_SYSDATA_IDX, - D_LOGS_IDX, - NUM_PATH_INDICES -}; - namespace FileUtil { +// User paths for GetUserPath +enum class UserPath { + CacheDir, + ConfigDir, + LogDir, + NANDDir, + RootDir, + SDMCDir, + SysDataDir, + UserDir, +}; + // FileSystem tree node/ struct FSTEntry { bool isDirectory; @@ -124,7 +123,7 @@ bool SetCurrentDir(const std::string& directory); // Returns a pointer to a string with a Citra data dir in the user's home // directory. To be used in "multi-user" mode (that is, installed). -const std::string& GetUserPath(const unsigned int DirIDX, const std::string& newPath = ""); +const std::string& GetUserPath(UserPath path, const std::string& new_path = ""); // Returns the path to where the sys file are std::string GetSysDirectory(); diff --git a/src/core/hle/service/am/am.cpp b/src/core/hle/service/am/am.cpp index ba11090ea..1fe963adb 100644 --- a/src/core/hle/service/am/am.cpp +++ b/src/core/hle/service/am/am.cpp @@ -440,11 +440,13 @@ std::string GetTitlePath(Service::FS::MediaType media_type, u64 tid) { std::string GetMediaTitlePath(Service::FS::MediaType media_type) { if (media_type == Service::FS::MediaType::NAND) - return fmt::format("{}{}/title/", FileUtil::GetUserPath(D_NAND_IDX), SYSTEM_ID); + return fmt::format("{}{}/title/", FileUtil::GetUserPath(FileUtil::UserPath::NANDDir), + SYSTEM_ID); if (media_type == Service::FS::MediaType::SDMC) - return fmt::format("{}Nintendo 3DS/{}/{}/title/", FileUtil::GetUserPath(D_SDMC_IDX), - SYSTEM_ID, SDCARD_ID); + return fmt::format("{}Nintendo 3DS/{}/{}/title/", + FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir), SYSTEM_ID, + SDCARD_ID); if (media_type == Service::FS::MediaType::GameCard) { // TODO(shinyquagsire23): get current app parent folder if TID matches? diff --git a/src/core/hle/service/apt/apt.cpp b/src/core/hle/service/apt/apt.cpp index 55b1a448f..8aa39bc5e 100644 --- a/src/core/hle/service/apt/apt.cpp +++ b/src/core/hle/service/apt/apt.cpp @@ -171,7 +171,7 @@ bool Module::LoadLegacySharedFont() { // generated by the APT:U service. The best way to get is by dumping it from RAM. We've provided // a homebrew app to do this: https://github.com/citra-emu/3dsutils. Put the resulting file // "shared_font.bin" in the Citra "sysdata" directory. - std::string filepath = FileUtil::GetUserPath(D_SYSDATA_IDX) + SHARED_FONT; + std::string filepath = FileUtil::GetUserPath(FileUtil::UserPath::SysDataDir) + SHARED_FONT; FileUtil::CreateFullPath(filepath); // Create path if not already created FileUtil::IOFile file(filepath, "rb"); diff --git a/src/core/hle/service/fs/archive.cpp b/src/core/hle/service/fs/archive.cpp index 470970942..a6ef1022e 100644 --- a/src/core/hle/service/fs/archive.cpp +++ b/src/core/hle/service/fs/archive.cpp @@ -571,9 +571,9 @@ ResultCode DeleteExtSaveData(MediaType media_type, u32 high, u32 low) { std::string media_type_directory; if (media_type == MediaType::NAND) { - media_type_directory = FileUtil::GetUserPath(D_NAND_IDX); + media_type_directory = FileUtil::GetUserPath(FileUtil::UserPath::NANDDir); } else if (media_type == MediaType::SDMC) { - media_type_directory = FileUtil::GetUserPath(D_SDMC_IDX); + media_type_directory = FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir); } else { LOG_ERROR(Service_FS, "Unsupported media type {}", static_cast(media_type)); return ResultCode(-1); // TODO(Subv): Find the right error code @@ -592,7 +592,7 @@ ResultCode DeleteSystemSaveData(u32 high, u32 low) { // Construct the binary path to the archive first FileSys::Path path = FileSys::ConstructSystemSaveDataBinaryPath(high, low); - std::string nand_directory = FileUtil::GetUserPath(D_NAND_IDX); + std::string nand_directory = FileUtil::GetUserPath(FileUtil::UserPath::NANDDir); std::string base_path = FileSys::GetSystemSaveDataContainerPath(nand_directory); std::string systemsavedata_path = FileSys::GetSystemSaveDataPath(base_path, path); if (!FileUtil::DeleteDirRecursively(systemsavedata_path)) @@ -604,7 +604,7 @@ ResultCode CreateSystemSaveData(u32 high, u32 low) { // Construct the binary path to the archive first FileSys::Path path = FileSys::ConstructSystemSaveDataBinaryPath(high, low); - std::string nand_directory = FileUtil::GetUserPath(D_NAND_IDX); + std::string nand_directory = FileUtil::GetUserPath(FileUtil::UserPath::NANDDir); std::string base_path = FileSys::GetSystemSaveDataContainerPath(nand_directory); std::string systemsavedata_path = FileSys::GetSystemSaveDataPath(base_path, path); if (!FileUtil::CreateFullPath(systemsavedata_path)) @@ -616,8 +616,8 @@ void RegisterArchiveTypes() { // TODO(Subv): Add the other archive types (see here for the known types: // http://3dbrew.org/wiki/FS:OpenArchive#Archive_idcodes). - std::string sdmc_directory = FileUtil::GetUserPath(D_SDMC_IDX); - std::string nand_directory = FileUtil::GetUserPath(D_NAND_IDX); + std::string sdmc_directory = FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir); + std::string nand_directory = FileUtil::GetUserPath(FileUtil::UserPath::NANDDir); auto sdmc_factory = std::make_unique(sdmc_directory); if (sdmc_factory->Initialize()) RegisterArchiveType(std::move(sdmc_factory), ArchiveIdCode::SDMC); diff --git a/src/core/hw/aes/key.cpp b/src/core/hw/aes/key.cpp index 8d20ee781..cd1decbb0 100644 --- a/src/core/hw/aes/key.cpp +++ b/src/core/hw/aes/key.cpp @@ -70,7 +70,7 @@ AESKey HexToKey(const std::string& hex) { } void LoadPresetKeys() { - const std::string filepath = FileUtil::GetUserPath(D_SYSDATA_IDX) + AES_KEYS; + const std::string filepath = FileUtil::GetUserPath(FileUtil::UserPath::SysDataDir) + AES_KEYS; FileUtil::CreateFullPath(filepath); // Create path if not already created std::ifstream file; OpenFStream(file, filepath, std::ios_base::in); diff --git a/src/core/telemetry_session.cpp b/src/core/telemetry_session.cpp index 04ab300f8..c4f4d8e51 100644 --- a/src/core/telemetry_session.cpp +++ b/src/core/telemetry_session.cpp @@ -45,7 +45,8 @@ static u64 GenerateTelemetryId() { u64 GetTelemetryId() { u64 telemetry_id{}; - const std::string filename{FileUtil::GetUserPath(D_CONFIG_IDX) + "telemetry_id"}; + const std::string filename{FileUtil::GetUserPath(FileUtil::UserPath::ConfigDir) + + "telemetry_id"}; if (FileUtil::Exists(filename)) { FileUtil::IOFile file(filename, "rb"); @@ -69,7 +70,8 @@ u64 GetTelemetryId() { u64 RegenerateTelemetryId() { const u64 new_telemetry_id{GenerateTelemetryId()}; - const std::string filename{FileUtil::GetUserPath(D_CONFIG_IDX) + "telemetry_id"}; + const std::string filename{FileUtil::GetUserPath(FileUtil::UserPath::ConfigDir) + + "telemetry_id"}; FileUtil::IOFile file(filename, "wb"); if (!file.IsOpen()) {