diff --git a/dll/settings_parser.cpp b/dll/settings_parser.cpp index 1485887d..ca9da485 100644 --- a/dll/settings_parser.cpp +++ b/dll/settings_parser.cpp @@ -616,47 +616,38 @@ static std::set parse_supported_languages(class Local_Storage *loca return supported_languages; } -// DLC.txt +// app::dlcs static void parse_dlc(class Settings *settings_client, class Settings *settings_server) { - std::string dlc_config_path = Local_Storage::get_game_settings_path() + "DLC.txt"; - std::ifstream input( utf8_decode(dlc_config_path) ); - if (input.is_open()) { - common_helpers::consume_bom(input); - settings_client->unlockAllDLC(false); - settings_server->unlockAllDLC(false); - PRINT_DEBUG("Locking all DLC"); + constexpr static const char unlock_all_key[] = "unlock_all"; - for( std::string line; std::getline( input, line ); ) { - if (!line.empty() && line.front() == '#') { - continue; - } - if (!line.empty() && line.back() == '\n') { - line.pop_back(); - } - - if (!line.empty() && line.back() == '\r') { - line.pop_back(); - } - - std::size_t deliminator = line.find("="); - if (deliminator != 0 && deliminator != std::string::npos && deliminator != line.size()) { - AppId_t appid = stol(line.substr(0, deliminator)); - std::string name = line.substr(deliminator + 1); - bool available = true; - - if (appid) { - PRINT_DEBUG("Adding DLC: %u|%s| %u", appid, name.c_str(), available); - settings_client->addDLC(appid, name, available); - settings_server->addDLC(appid, name, available); - } - } - } - } else { - //unlock all DLC - PRINT_DEBUG("Unlocking all DLC"); + bool unlock_all = ini.GetBoolValue("app::dlcs", unlock_all_key, true); + if (unlock_all) { + PRINT_DEBUG("unlocking all DLCs"); settings_client->unlockAllDLC(true); settings_server->unlockAllDLC(true); + } else { + PRINT_DEBUG("locking all DLCs"); + settings_client->unlockAllDLC(false); + settings_server->unlockAllDLC(false); + } + + std::list dlcs_keys{}; + if (!ini.GetAllKeys("app::dlcs", dlcs_keys) || dlcs_keys.empty()) return; + + // remove the unlock all key so we can iterate through the DLCs + dlcs_keys.remove_if([](const CSimpleIniA::Entry &item){ + return common_helpers::str_cmp_insensitive(item.pItem, unlock_all_key); + }); + + for (const auto &dlc_key : dlcs_keys) { + AppId_t appid = (AppId_t)std::stoul(dlc_key.pItem); + if (!appid) continue; + + auto name = ini.GetValue("app::dlcs", dlc_key.pItem, "unknown DLC"); + PRINT_DEBUG("adding DLC: [%u] = '%s'", appid, name); + settings_client->addDLC(appid, name, true); + settings_server->addDLC(appid, name, true); } } diff --git a/post_build/README.release.md b/post_build/README.release.md index 26c07ef2..6e936cb0 100644 --- a/post_build/README.release.md +++ b/post_build/README.release.md @@ -56,22 +56,6 @@ Check the example file in `steam_settings.EXAMPLE\configs.EXAMPLE.ini`. --- -## DLC: -By default the emulator will try to unlock all DLCs (by returning `true` when the game calls the `BIsDlcInstalled()` function). -If the game uses the other function you will need to provide a list of DLCs to the emulator. - -To do this first create a `steam_settings` folder right beside the emulator. -In this folder, put a `DLC.txt` file. (path will be `\steam_settings\DLC.txt`) - -If the DLC file is present, the emulator will only unlock the DLCs in that file. If the file is empty all DLCs will be locked. - -The contents of this file are: -`appid=DLC name` - -See the `steam_settings.EXAMPLE` folder for an example. - ---- - ## Languages: You can include a `steam_settings\supported_languages.txt` file with a list of languages that the game supports. diff --git a/post_build/steam_settings.EXAMPLE/configs.app.EXAMPLE.ini b/post_build/steam_settings.EXAMPLE/configs.app.EXAMPLE.ini index 155c3ca8..8b97fcba 100644 --- a/post_build/steam_settings.EXAMPLE/configs.app.EXAMPLE.ini +++ b/post_build/steam_settings.EXAMPLE/configs.app.EXAMPLE.ini @@ -37,6 +37,16 @@ inventory=STEAMINVENTORY_INTERFACE_V001 video=STEAMVIDEO_INTERFACE_V001 masterserver_updater=SteamMasterServerUpdater001 +[app::dlcs] +# should the emu report all DLCs as unlocked +# some games check for "hidden" DLCs, hence this should be set to 1 in that case +# but other games detect emus by querying for a fake/bad DLC, hence this should be set to 0 in that case +# default=1 +unlock_all=0 +# format: ID=name +1234=DLCNAME +56789=This is another example DLC name + [app::paths] 556760=../DLCRoot0 1234=./folder_where_steam_api_is diff --git a/tools/generate_emu_config/generate_emu_config.py b/tools/generate_emu_config/generate_emu_config.py index aa24e4c7..6257bc21 100644 --- a/tools/generate_emu_config/generate_emu_config.py +++ b/tools/generate_emu_config/generate_emu_config.py @@ -317,7 +317,7 @@ EXTRA_FEATURES_CONVENIENT = { }, 'configs.overlay.ini': { 'overlay::general': { - 'enable_experimental_overlay': (1, 'xxx USE AT YOUR OWN RISK xxx, enable the experimental overlay'), + 'enable_experimental_overlay': (1, 'XXX USE AT YOUR OWN RISK XXX, enable the experimental overlay, might cause crashes or other problems'), 'disable_warning_any': (1, 'disable any warning in the overlay'), }, } @@ -565,11 +565,12 @@ def write_ini_file(base_path: str, out_ini: dict): for file in out_ini.items(): with open(os.path.join(base_path, file[0]), 'wt', encoding='utf-8') as f: for item in file[1].items(): - f.write('[' + item[0] + ']\n') # section + f.write('[' + str(item[0]) + ']\n') # section for kv in item[1].items(): - f.write('# ' + kv[1][1] + '\n') # comment - f.write(kv[0] + '=' + str(kv[1][0]) + '\n') # key/value pair - f.write('\n') # key/value pair + if kv[1][1]: # comment + f.write('# ' + str(kv[1][1]) + '\n') + f.write(str(kv[0]) + '=' + str(kv[1][0]) + '\n') # key/value pair + f.write('\n') def main(): USERNAME = "" @@ -832,6 +833,8 @@ def main(): } } }) + # write the data as soon as possible in case a later step caused an exception + write_ini_file(emu_settings_dir, out_config_app_ini) dlc_config_list : list[tuple[int, str]] = [] dlc_list, depot_app_list, all_depots = get_dlc(game_info) @@ -850,13 +853,26 @@ def main(): dlc_config_list.append((dlc, dlc_name)) - # we create the DLC fle nonetheless, empty file makes the emu lock DLCs, otherwise everything is allowed + # we set unlock_all=0 nonetheless, to make the emu lock DLCs, otherwise everything is allowed # some games use that as a detection mechanism - with open(os.path.join(emu_settings_dir, "DLC.txt"), 'wt', encoding="utf-8") as f: - if dlc_config_list: - for x in dlc_config_list: - f.write(f"{x[0]}={x[1]}\n") - + merge_dict(out_config_app_ini, { + 'configs.app.ini': { + 'app::dlcs': { + 'unlock_all': (0, 'should the emu report all DLCs as unlocked, default=1'), + } + } + }) + for x in dlc_config_list: + merge_dict(out_config_app_ini, { + 'configs.app.ini': { + 'app::dlcs': { + x[0]: (x[1], ''), + } + } + }) + # write the data as soon as possible in case a later step caused an exception + write_ini_file(emu_settings_dir, out_config_app_ini) + if all_depots: with open(os.path.join(emu_settings_dir, "depots.txt"), 'wt', encoding="utf-8") as f: for game_depot in all_depots: @@ -945,16 +961,12 @@ def main(): logo, logo_small) - out_config_main_ini = {} if DISABLE_EXTRA: - merge_dict(out_config_main_ini, EXTRA_FEATURES_DISABLE) + merge_dict(out_config_app_ini, EXTRA_FEATURES_DISABLE) if CONVENIENT_EXTRA: - merge_dict(out_config_main_ini, EXTRA_FEATURES_CONVENIENT) + merge_dict(out_config_app_ini, EXTRA_FEATURES_CONVENIENT) - if out_config_main_ini: - write_ini_file(emu_settings_dir, out_config_main_ini) - if out_config_app_ini: write_ini_file(emu_settings_dir, out_config_app_ini) diff --git a/tools/migrate_gse/README.md b/tools/migrate_gse/README.md new file mode 100644 index 00000000..64b77157 --- /dev/null +++ b/tools/migrate_gse/README.md @@ -0,0 +1,52 @@ +## Save & Settings folder migration tool +This tool allows you to migrate your `steam_settings` folder, or your global `settings` folder from the old format, to the new `.ini` oriented format. + +## Where is the global settings folder? +On Windows this folder is located at `%appdata%\Goldberg SteamEmu Saves\settings\` +On Linux this folder is located at: + * if env var `XDG_DATA_HOME` is defined: + `$XDG_DATA_HOME/Goldberg SteamEmu Saves/settings/` + * Otherwise, if env var `HOME` is defined: + `$HOME/.local/share/Goldberg SteamEmu Saves/settings/` + +## How to migrate the global settings folder +Simply open the terminal/cmd in the folder of the tool and run it **without** any arguments. +* On Windows: + ```shell + migrate_gse.exe + ``` +* On Linux: + ```shell + chmod 777 migrate_gse + ./migrate_gse + ``` +The tool will generate a new folder in the current directory called `steam_settings`, copy the **content inside** this folder (a bunch of `.ini` files) and paste them here: + * On Windows: `%appdata%\GSE Saves\settings\` + * On Linux: + * if env var `XDG_DATA_HOME` is defined: + `$XDG_DATA_HOME/GSE Saves/settings/` + * Otherwise, if env var `HOME` is defined: + `$HOME/.local/share/GSE Saves/settings/` + +Notice the new name of the global saves folder `GSE Saves`, not to be confused with the old one `Goldberg SteamEmu Saves` + + +## How to migrate a local `steam_settings` folder you already have +Open the terminal/cmd in the folder of the tool and run it with only one argument, which is the path to the target `steam_settings` folder. +* On Windows: + ```shell + migrate_gse.exe "D:\my games\some game\steam_settings" + ``` +* On Linux (notice how the tilde character is outside the quotes to allow it to expand): + ```shell + chmod 777 migrate_gse + ./migrate_gse ~/"some game/steam_settings" + ``` +The tool will generate a new folder in the current directory called `steam_settings`, copy the **content inside** this folder (a bunch of `.ini` files) and paste them inside the target/old `steam_settings` folder + + +## General notes +* In all cases, the tool will not copy the achievements images, overlay fonts/sounds, and all the extra stuff, it will just generate the corresponding `.ini` files + +* Some configuration files are still using the same old format, that includes all the `.json` files, depots.txt, subscribed_groups_xxx.txt, etc... + So don't just remove everything from the old `steam_settings` folder diff --git a/tools/migrate_gse/main.py b/tools/migrate_gse/main.py index 83fda890..87425e4b 100644 --- a/tools/migrate_gse/main.py +++ b/tools/migrate_gse/main.py @@ -29,12 +29,12 @@ def write_ini_file(base_path: str, out_ini: dict): for file in out_ini.items(): with open(os.path.join(base_path, file[0]), 'wt', encoding='utf-8') as f: for item in file[1].items(): - f.write('[' + item[0] + ']\n') # section + f.write('[' + str(item[0]) + ']\n') # section for kv in item[1].items(): - if kv[1][1]: - f.write('# ' + kv[1][1] + '\n') # comment - f.write(kv[0] + '=' + str(kv[1][0]) + '\n') # key/value pair - f.write('\n') # key/value pair + if kv[1][1]: # comment + f.write('# ' + str(kv[1][1]) + '\n') + f.write(str(kv[0]) + '=' + str(kv[1][0]) + '\n') # key/value pair + f.write('\n') itf_patts = [ ( r'SteamClient\d+', "client" ), @@ -99,28 +99,22 @@ def main(): global_settings = os.path.join(home_env, 'Goldberg SteamEmu Saves', 'settings') if not global_settings or not os.path.isdir(global_settings): - print('failed to detect settings folder', file=sys.stderr) + print('failed to detect folder', file=sys.stderr) help() sys.exit(1) + print(f'searching inside the folder: "{global_settings}"') + out_dict_ini = {} + # oh no, they're too many! for file in glob.glob('*.*', root_dir=global_settings): file = file.lower() - if file == 'force_account_name.txt': + if file == 'force_account_name.txt' or file == 'account_name.txt': with open(os.path.join(global_settings, file), "r", encoding='utf-8') as fr: merge_dict(out_dict_ini, { 'configs.user.ini': { 'user::general': { - 'account_name': (fr.readline(), 'user account name'), - }, - } - }) - elif file == 'account_name.txt': - with open(os.path.join(global_settings, file), "r", encoding='utf-8') as fr: - merge_dict(out_dict_ini, { - 'configs.user.ini': { - 'user::general': { - 'account_name': (fr.readline(), 'user account name'), + 'account_name': (fr.readline().strip('\n').strip('\r'), 'user account name'), }, } }) @@ -129,11 +123,11 @@ def main(): merge_dict(out_dict_ini, { 'configs.app.ini': { 'app::general': { - 'branch_name': (fr.readline(), 'the name of the beta branch'), + 'branch_name': (fr.readline().strip(), 'the name of the beta branch'), }, } }) - elif file == 'force_language.txt': + elif file == 'force_language.txt' or file == 'language.txt': with open(os.path.join(global_settings, file), "r", encoding='utf-8') as fr: merge_dict(out_dict_ini, { 'configs.user.ini': { @@ -142,16 +136,7 @@ def main(): }, } }) - elif file == 'language.txt': - with open(os.path.join(global_settings, file), "r", encoding='utf-8') as fr: - merge_dict(out_dict_ini, { - 'configs.user.ini': { - 'user::general': { - 'language': (fr.readline().strip(), 'the language reported to the app/game, https://partner.steamgames.com/doc/store/localization/languages'), - }, - } - }) - elif file == 'force_listen_port.txt': + elif file == 'force_listen_port.txt' or file == 'listen_port.txt': with open(os.path.join(global_settings, file), "r", encoding='utf-8') as fr: merge_dict(out_dict_ini, { 'configs.main.ini': { @@ -160,25 +145,7 @@ def main(): }, } }) - elif file == 'listen_port.txt': - with open(os.path.join(global_settings, file), "r", encoding='utf-8') as fr: - merge_dict(out_dict_ini, { - 'configs.main.ini': { - 'main::connectivity': { - 'listen_port': (fr.readline().strip(), 'change the UDP/TCP port the emulator listens on'), - }, - } - }) - elif file == 'force_steamid.txt': - with open(os.path.join(global_settings, file), "r", encoding='utf-8') as fr: - merge_dict(out_dict_ini, { - 'configs.user.ini': { - 'user::general': { - 'account_steamid': (fr.readline().strip(), 'Steam64 format'), - }, - } - }) - elif file == 'user_steam_id.txt': + elif file == 'force_steamid.txt' or file == 'user_steam_id.txt': with open(os.path.join(global_settings, file), "r", encoding='utf-8') as fr: merge_dict(out_dict_ini, { 'configs.user.ini': { @@ -272,17 +239,20 @@ def main(): }, } }) - elif file == 'enable_experimental_overlay.txt': + elif file == 'enable_experimental_overlay.txt' or file == 'disable_overlay.txt': + enable_ovl = 0 + if file == 'enable_experimental_overlay.txt': + enable_ovl = 1 merge_dict(out_dict_ini, { 'configs.overlay.ini': { 'overlay::general': { - 'enable_experimental_overlay': (1, 'XXX USE AT YOUR OWN RISK XXX, enable the experimental overlay, might cause crashes'), + 'enable_experimental_overlay': (enable_ovl, 'XXX USE AT YOUR OWN RISK XXX, enable the experimental overlay, might cause crashes'), }, } }) elif file == 'app_paths.txt': with open(os.path.join(global_settings, file), "r", encoding='utf-8') as fr: - app_lines = [lll for lll in fr.readlines() if lll.strip()] + app_lines = [lll.strip('\n').strip('\r') for lll in fr.readlines() if lll.strip() and lll.strip()[0] != '#'] for app_lll in app_lines: [apppid, appppath] = app_lll.split('=', 1) merge_dict(out_dict_ini, { @@ -292,6 +262,25 @@ def main(): }, } }) + elif file == 'dlc.txt': + with open(os.path.join(global_settings, file), "r", encoding='utf-8') as fr: + dlc_lines = [lll.strip('\n').strip('\r') for lll in fr.readlines() if lll.strip() and lll.strip()[0] != '#'] + merge_dict(out_dict_ini, { + 'configs.app.ini': { + 'app::dlcs': { + 'unlock_all': (0, 'should the emu report all DLCs as unlocked, default=1'), + }, + } + }) + for dlc_lll in dlc_lines: + [dlcid, dlcname] = dlc_lll.split('=', 1) + merge_dict(out_dict_ini, { + 'configs.app.ini': { + 'app::dlcs': { + dlcid.strip(): (dlcname, ''), + }, + } + }) elif file == 'achievements_bypass.txt': merge_dict(out_dict_ini, { 'configs.main.ini': { @@ -305,7 +294,7 @@ def main(): merge_dict(out_dict_ini, { 'configs.main.ini': { 'main::general': { - 'crash_printer_location': (fr.readline(), 'this is intended to debug some annoying scenarios, and best used with the debug build'), + 'crash_printer_location': (fr.readline().strip('\n').strip('\r'), 'this is intended to debug some annoying scenarios, and best used with the debug build'), }, } }) @@ -467,6 +456,7 @@ def main(): os.makedirs('steam_settings') write_ini_file('steam_settings', out_dict_ini) + print(f'new settings written inside: "{os.path.join(os.path.curdir, "steam_settings")}"') if __name__ == "__main__":