diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index b5979e01..de5fa3f5 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -56,7 +56,7 @@ build_steamos: build_windows: stage: build - image: fedora + image: fedora:29 script: - dnf -y install wine wget p7zip sed dos2unix unzip @@ -76,6 +76,7 @@ build_windows: - DLL_FILES="$(ls ImGui/*.cpp | tr "\n" " ")"; sed "s|ImGui/\*.cpp|$DLL_FILES|g" -i *.bat - DLL_FILES="$(ls ImGui/impls/*.cpp | tr "\n" " ")"; sed "s|ImGui/impls/\*.cpp|$DLL_FILES|g" -i *.bat - DLL_FILES="$(ls dll/*.proto | tr "\n" " " | sed "s/.proto/.pb.cc/g")"; sed "s|dll/\*.cc|$DLL_FILES|g" -i *.bat + - DLL_FILES="$(ls steamclient_loader/*.cpp | tr "\n" " ")"; sed "s|steamclient_loader/\*.cpp|$DLL_FILES|g" -i *.bat - export WINEDEBUG=-all - wine cmd /c build_win_debug_experimental.bat - wine cmd /c build_win_release.bat @@ -111,13 +112,10 @@ build_cmake_linux: build_cmake_windows: stage: build - image: fedora + image: fedora:29 before_script: - - dnf update -y - - dnf install 'dnf-command(config-manager)' -y - - dnf config-manager --add-repo https://dl.winehq.org/wine-builds/fedora/30/winehq.repo - - dnf install wget unzip p7zip winehq-devel samba-winbind-clients -y + - dnf -y install wine wget p7zip sed dos2unix - wget 'https://gitlab.com/Mr_Goldberg/goldberg_emulator/uploads/48db8f434a193aae872279dc4f5dde6a/sdk_standalone.7z' - 7za x sdk_standalone.7z -osdk_standalone - wget 'https://github.com/Kitware/CMake/releases/download/v3.15.0-rc1/cmake-3.15.0-rc1-win64-x64.zip' diff --git a/Readme_experimental_steamclient.txt b/Readme_experimental_steamclient.txt new file mode 100644 index 00000000..247ccd22 --- /dev/null +++ b/Readme_experimental_steamclient.txt @@ -0,0 +1,4 @@ +This is a build of the experimental version of my emu in steamclient mode with an included loader. See both the regular and experimental readmes for how to configure it. +Note that all emu config files should be put beside the steamclient dll. You do not need to put a steam_interfaces.txt file for the steamclient version of the emu. + +To use the loader, put both steamclient dlls and the loader in a folder and edit the config file. Make sure you put the right appid in the ini file. diff --git a/Readme_release.txt b/Readme_release.txt index a646f756..a2c12361 100644 --- a/Readme_release.txt +++ b/Readme_release.txt @@ -60,13 +60,17 @@ The steam appid can also be set using the SteamAppId or SteamGameId env variable Offline mode: Some games that connect to online servers might only work if the steam emu behaves like steam is in offline mode. If you need this create a offline.txt file in the steam_settings folder. +Disable networking: +If for some reason you want to disable all the networking functionality of the emu you can create a disable_networking.txt file in the steam_settings folder. This will of course break all the +networking functionality so games that use networking related functionality like lobbies or those that launch a server in the background will not work. + Custom Broadcast ips: If you want to set custom ips (or domains) which the emulator will send broadcast packets to, make a list of them, one on each line in: Goldberg SteamEmu Saves\settings\custom_broadcasts.txt If the custom ips/domains are specific for one game only you can put the custom_broadcasts.txt in the steam_settings\ folder. An example is provided in steam_settings.EXAMPLE\custom_broadcasts.EXAMPLE.txt -Items or Inventory: -Create a folder named steam_settings right beside steam_api.dll if there isn't one already. In that folder, create a file named items.json which will contain every item you want to have in your game. +Achievements, Items or Inventory: +Create a folder named steam_settings right beside steam_api.dll if there isn't one already. In that folder, create a file named items.json and/or achievements.json which will contain every item/achievement you want to have in your game. An example can be found in steam_settings.EXAMPLE that works with Killing Floor 2. The items.json syntax is simple, you SHOULD validate your .json file before trying to run your game or you won't have any item in your inventory. Just look for "online json validator" on your web brower to valide your file. You can use https://steamdb.info/ to list items and attributes they have and put them into your .json. diff --git a/build_env_x64.bat b/build_env_x64.bat old mode 100644 new mode 100755 diff --git a/build_env_x86.bat b/build_env_x86.bat old mode 100644 new mode 100755 diff --git a/build_linux.sh b/build_linux.sh old mode 100644 new mode 100755 diff --git a/build_set_protobuf_directories.bat b/build_set_protobuf_directories.bat old mode 100644 new mode 100755 diff --git a/build_steamos.sh b/build_steamos.sh old mode 100644 new mode 100755 diff --git a/build_win_debug_experimental.bat b/build_win_debug_experimental.bat old mode 100644 new mode 100755 diff --git a/build_win_find_interfaces.bat b/build_win_find_interfaces.bat old mode 100644 new mode 100755 diff --git a/build_win_lobby_connect.bat b/build_win_lobby_connect.bat old mode 100644 new mode 100755 diff --git a/build_win_release.bat b/build_win_release.bat old mode 100644 new mode 100755 index 73ba8fe8..56386cb0 --- a/build_win_release.bat +++ b/build_win_release.bat @@ -2,6 +2,7 @@ cd /d "%~dp0" del /Q /S release\* rmdir /S /Q release\experimental +rmdir /S /Q release\experimental_steamclient rmdir /S /Q release\lobby_connect rmdir /S /Q release mkdir release @@ -18,5 +19,6 @@ cl /LD /DNO_OVERLAY /DEMU_RELEASE_BUILD /DNDEBUG /I%PROTOBUF_X64_DIRECTORY%\incl copy Readme_release.txt release\Readme.txt xcopy /s files_example\* release\ call build_win_release_experimental.bat +call build_win_release_experimental_steamclient.bat call build_win_lobby_connect.bat call build_win_find_interfaces.bat diff --git a/build_win_release_experimental.bat b/build_win_release_experimental.bat old mode 100644 new mode 100755 diff --git a/build_win_release_experimental_steamclient.bat b/build_win_release_experimental_steamclient.bat new file mode 100644 index 00000000..1a1312e8 --- /dev/null +++ b/build_win_release_experimental_steamclient.bat @@ -0,0 +1,16 @@ +@echo off +cd /d "%~dp0" +mkdir release\experimental_steamclient +del /Q release\experimental_steamclient\* +call build_set_protobuf_directories.bat +"%PROTOC_X86_EXE%" -I.\dll\ --cpp_out=.\dll\ .\dll\net.proto +call build_env_x86.bat +cl dll/rtlgenrandom.c dll/rtlgenrandom.def +cl /LD /DEMU_RELEASE_BUILD /DEMU_EXPERIMENTAL_BUILD /DSTEAMCLIENT_DLL /DCONTROLLER_SUPPORT /DNDEBUG /I%PROTOBUF_X86_DIRECTORY%\include\ dll/*.cpp dll/*.cc detours/*.cpp controller/gamepad.c "%PROTOBUF_X86_LIBRARY%" Iphlpapi.lib Ws2_32.lib rtlgenrandom.lib Shell32.lib /EHsc /MP12 /Ox /link /debug:none /OUT:release\experimental_steamclient\steamclient.dll +"%PROTOC_X64_EXE%" -I.\dll\ --cpp_out=.\dll\ .\dll\net.proto +cl steamclient_loader/*.cpp advapi32.lib user32.lib /EHsc /MP12 /Ox /link /debug:none /OUT:release\experimental_steamclient\steamclient_loader.exe +copy steamclient_loader\ColdClientLoader.ini release\experimental_steamclient\ +call build_env_x64.bat +cl dll/rtlgenrandom.c dll/rtlgenrandom.def +cl /LD /DEMU_RELEASE_BUILD /DEMU_EXPERIMENTAL_BUILD /DSTEAMCLIENT_DLL /DCONTROLLER_SUPPORT /DNDEBUG /I%PROTOBUF_X64_DIRECTORY%\include\ dll/*.cpp dll/*.cc detours/*.cpp controller/gamepad.c "%PROTOBUF_X64_LIBRARY%" Iphlpapi.lib Ws2_32.lib rtlgenrandom.lib Shell32.lib /EHsc /MP12 /Ox /link /debug:none /OUT:release\experimental_steamclient\steamclient64.dll +copy Readme_experimental_steamclient.txt release\experimental_steamclient\Readme.txt diff --git a/dll/base.h b/dll/base.h index 9d5b5bbd..275b760c 100644 --- a/dll/base.h +++ b/dll/base.h @@ -132,8 +132,12 @@ struct Steam_Call_Result { return check_timedout(created, STEAM_CALLRESULT_TIMEOUT); } + bool call_completed() { + return (!reserved) && check_timedout(created, run_in); + } + bool can_execute() { - return (!reserved) && (!to_delete) && check_timedout(created, run_in); + return (!to_delete) && call_completed(); } bool has_cb() { @@ -191,14 +195,14 @@ public: bool exists(SteamAPICall_t api_call) { auto cr = std::find_if(callresults.begin(), callresults.end(), [api_call](struct Steam_Call_Result const& item) { return item.api_call == api_call; }); if (cr == callresults.end()) return false; - if (cr->reserved) return false; + if (!cr->call_completed()) return false; return true; } bool callback_result(SteamAPICall_t api_call, void *copy_to, unsigned int size) { auto cb_result = std::find_if(callresults.begin(), callresults.end(), [api_call](struct Steam_Call_Result const& item) { return item.api_call == api_call; }); if (cb_result != callresults.end()) { - if (cb_result->reserved) return false; + if (!cb_result->call_completed()) return false; if (cb_result->result.size() > size) return false; memcpy(copy_to, &(cb_result->result[0]), cb_result->result.size()); diff --git a/dll/item_db_loader.cpp b/dll/item_db_loader.cpp deleted file mode 100644 index ee591059..00000000 --- a/dll/item_db_loader.cpp +++ /dev/null @@ -1,62 +0,0 @@ -/* Copyright (C) 2019 Nemirtingas (Maxime P) - This file is part of the Goldberg Emulator - - The Goldberg Emulator is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 3 of the License, or (at your option) any later version. - - The Goldberg Emulator is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with the Goldberg Emulator; if not, see - . */ -#include "item_db_loader.h" - -#include -#include "../json/json.hpp" - -void read_items_db(std::string items_db, std::map> *items, std::atomic_bool *is_loadedb) -{ - std::ifstream items_file(items_db); - // If there is a file and we opened it - if( items_file ) - { - items_file.seekg(0, std::ios::end); - size_t size = items_file.tellg(); - std::string buffer(size, '\0'); - items_file.seekg(0); - // Read it entirely, if the .json file gets too big, - // I should look into this and split reads into smaller parts. - items_file.read(&buffer[0], size); - items_file.close(); - - try - { - std::map> tmp; - nlohmann::json json = nlohmann::json::parse(buffer); - - for (auto& i : json.items()) - { - SteamItemDef_t key = std::stoi((*i).key()); - nlohmann::json& value = (*i).value(); - for (auto& j : value.items()) - { - tmp[key][(*j).key()] = (*j).value(); - } - } - - items->swap(tmp); - } - catch (std::exception& e) - { - PRINT_DEBUG("Error while parsing json: %s\n", e.what()); - } - } - - PRINT_DEBUG("Loaded json. Loaded %u items.\n", items->size()); - *is_loadedb = true; -} \ No newline at end of file diff --git a/dll/item_db_loader.h b/dll/item_db_loader.h deleted file mode 100644 index 89c09129..00000000 --- a/dll/item_db_loader.h +++ /dev/null @@ -1,24 +0,0 @@ -/* Copyright (C) 2019 Nemirtingas (Maxime P) - This file is part of the Goldberg Emulator - - The Goldberg Emulator is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 3 of the License, or (at your option) any later version. - - The Goldberg Emulator is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with the Goldberg Emulator;*/ -#ifndef __ITEM_DB_LOADER_INCLUDED__ -#define __ITEM_DB_LOADER_INCLUDED__ - -#include "base.h" // For SteamItemDef_t - -#include -void read_items_db(std::string items_db, std::map> *items, std::atomic_bool *is_loaded); - -#endif//__ITEM_DB_LOADER_INCLUDED__ \ No newline at end of file diff --git a/dll/local_storage.cpp b/dll/local_storage.cpp index 9f843177..f1b3d45d 100644 --- a/dll/local_storage.cpp +++ b/dll/local_storage.cpp @@ -20,6 +20,7 @@ #include #include #include +#include struct File_Data { std::string name; @@ -393,7 +394,7 @@ std::string Local_Storage::get_program_path() std::string Local_Storage::get_game_settings_path() { - return get_program_path().append(GAME_SETTINGS_FOLDER).append(PATH_SEPARATOR); + return get_program_path().append(game_settings_folder).append(PATH_SEPARATOR); } #if defined(STEAM_WIN32) @@ -523,7 +524,7 @@ std::string Local_Storage::get_path(std::string folder) std::string Local_Storage::get_global_settings_path() { - return save_directory + SETTINGS_STORAGE_FOLDER + PATH_SEPARATOR; + return save_directory + settings_storage_folder + PATH_SEPARATOR; } std::vector Local_Storage::get_filenames_path(std::string path) diff --git a/dll/local_storage.h b/dll/local_storage.h index 72aa5e2b..394fae2f 100644 --- a/dll/local_storage.h +++ b/dll/local_storage.h @@ -21,18 +21,21 @@ #ifndef LOCAL_STORAGE_INCLUDE #define LOCAL_STORAGE_INCLUDE -#define SETTINGS_STORAGE_FOLDER "settings" -#define REMOTE_STORAGE_FOLDER "remote" -#define STATS_STORAGE_FOLDER "stats" -#define USER_DATA_FOLDER "local" - -#define GAME_SETTINGS_FOLDER "steam_settings" - #include +#include "../json/json.hpp" #define MAX_FILENAME_LENGTH 300 class Local_Storage { +public: + static constexpr auto inventory_storage_folder = "inventory"; + static constexpr auto settings_storage_folder = "settings"; + static constexpr auto remote_storage_folder = "remote"; + static constexpr auto stats_storage_folder = "stats"; + static constexpr auto user_data_storage = "local"; + static constexpr auto game_settings_folder = "steam_settings"; + +private: std::string save_directory; std::string appid; public: diff --git a/dll/network.cpp b/dll/network.cpp index e011827c..8a5e7ed0 100644 --- a/dll/network.cpp +++ b/dll/network.cpp @@ -711,18 +711,26 @@ bool Networking::handle_low_level_udp(Common_Message *msg, IP_PORT ip_port) #define NUM_TCP_WAITING 128 -Networking::Networking(CSteamID id, uint32 appid, uint16 port, std::set *custom_broadcasts) +Networking::Networking(CSteamID id, uint32 appid, uint16 port, std::set *custom_broadcasts, bool disable_sockets) { - run_at_startup(); tcp_port = udp_port = port; own_ip = 0x7F000001; alive = true; last_run = std::chrono::high_resolution_clock::now(); this->appid = appid; + + if (disable_sockets) { + enabled = false; + udp_socket = -1; + tcp_socket = -1; + return; + } + if (custom_broadcasts) { std::transform(custom_broadcasts->begin(), custom_broadcasts->end(), std::back_inserter(this->custom_broadcasts), [](uint32 ip) {return htonl(ip);}); } + run_at_startup(); sock_t sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); PRINT_DEBUG("UDP socket: %u\n", sock); if (is_socket_valid(sock) && set_socket_nonblocking(sock)) { @@ -1059,6 +1067,7 @@ void Networking::Run() void Networking::addListenId(CSteamID id) { + if (!enabled) return; auto i = std::find(ids.begin(), ids.end(), id); if (i != ids.end()) { return; @@ -1092,6 +1101,8 @@ bool Networking::sendToIPPort(Common_Message *msg, uint32 ip, uint16 port, bool bool Networking::sendTo(Common_Message *msg, bool reliable, Connection *conn) { + if (!enabled) return false; + bool ret = false; CSteamID dest_id((uint64)msg->dest_id()); if (std::find(ids.begin(), ids.end(), dest_id) != ids.end()) { diff --git a/dll/network.h b/dll/network.h index 324ed621..1d6119aa 100644 --- a/dll/network.h +++ b/dll/network.h @@ -125,7 +125,7 @@ public: //NOTE: for all functions ips/ports are passed/returned in host byte order //ex: 127.0.0.1 should be passed as 0x7F000001 static std::set resolve_ip(std::string dns); - Networking(CSteamID id, uint32 appid, uint16 port, std::set *custom_broadcasts); + Networking(CSteamID id, uint32 appid, uint16 port, std::set *custom_broadcasts, bool disable_sockets); void addListenId(CSteamID id); void setAppID(uint32 appid); void Run(); diff --git a/dll/settings.h b/dll/settings.h index a5efb5b7..cfb06b09 100644 --- a/dll/settings.h +++ b/dll/settings.h @@ -135,6 +135,9 @@ public: //controller struct Controller_Settings controller_settings; + + //networking + bool disable_networking = false; }; #endif diff --git a/dll/settings_parser.cpp b/dll/settings_parser.cpp index 42849d36..dc46f59d 100644 --- a/dll/settings_parser.cpp +++ b/dll/settings_parser.cpp @@ -217,7 +217,7 @@ uint32 create_localstorage_settings(Settings **settings_client_out, Settings **s uint64 steam_id = 0; bool generate_new = false; //try to load steam id from game specific settings folder first - if (local_storage->get_data(SETTINGS_STORAGE_FOLDER, "user_steam_id.txt", array_steam_id, sizeof(array_steam_id) - 1) > 0) { + if (local_storage->get_data(Local_Storage::settings_storage_folder, "user_steam_id.txt", array_steam_id, sizeof(array_steam_id) - 1) > 0) { user_id = CSteamID((uint64)std::atoll(array_steam_id)); if (!user_id.IsValid()) { generate_new = true; @@ -247,6 +247,7 @@ uint32 create_localstorage_settings(Settings **settings_client_out, Settings **s } bool steam_offline_mode = false; + bool disable_networking = false; { std::string steam_settings_path = Local_Storage::get_game_settings_path(); @@ -255,6 +256,8 @@ uint32 create_localstorage_settings(Settings **settings_client_out, Settings **s PRINT_DEBUG("steam settings path %s\n", p.c_str()); if (p == "offline.txt") { steam_offline_mode = true; + } else if (p == "disable_networking.txt") { + disable_networking = true; } } } @@ -265,6 +268,8 @@ uint32 create_localstorage_settings(Settings **settings_client_out, Settings **s settings_server->set_port(port); settings_client->custom_broadcasts = custom_broadcasts; settings_server->custom_broadcasts = custom_broadcasts; + settings_client->disable_networking = disable_networking; + settings_server->disable_networking = disable_networking; { std::string dlc_config_path = Local_Storage::get_game_settings_path() + "DLC.txt"; diff --git a/dll/steam_client.cpp b/dll/steam_client.cpp index 92d1e3a5..b5e0cae0 100644 --- a/dll/steam_client.cpp +++ b/dll/steam_client.cpp @@ -42,7 +42,6 @@ static void background_thread(Steam_Client *client) Steam_Client::Steam_Client() { uint32 appid = create_localstorage_settings(&settings_client, &settings_server, &local_storage); - std::string items_db_file_path = (Local_Storage::get_game_settings_path() + "items.json"); { std::ifstream chk_ovlay(Local_Storage::get_program_path() + PATH_SEPARATOR + "disable_overlay.txt", std::ios::in); @@ -79,7 +78,7 @@ Steam_Client::Steam_Client() steam_music = new Steam_Music(callbacks_client); steam_musicremote = new Steam_MusicRemote(); steam_HTMLsurface = new Steam_HTMLsurface(settings_client, network, callback_results_client, callbacks_client); - steam_inventory = new Steam_Inventory(settings_client, callback_results_client, callbacks_client, run_every_runcb, items_db_file_path); + steam_inventory = new Steam_Inventory(settings_client, callback_results_client, callbacks_client, run_every_runcb, local_storage); steam_video = new Steam_Video(); steam_parental = new Steam_Parental(); steam_networking_sockets = new Steam_Networking_Sockets(settings_client, network, callback_results_client, callbacks_client, run_every_runcb); @@ -97,7 +96,7 @@ Steam_Client::Steam_Client() steam_gameserverstats = new Steam_GameServerStats(settings_server, network, callback_results_server, callbacks_server); steam_gameserver_networking = new Steam_Networking(settings_server, network, callbacks_server, run_every_runcb); steam_gameserver_http = new Steam_HTTP(settings_server, network, callback_results_server, callbacks_server); - steam_gameserver_inventory = new Steam_Inventory(settings_server, callback_results_server, callbacks_server, run_every_runcb, items_db_file_path); + steam_gameserver_inventory = new Steam_Inventory(settings_server, callback_results_server, callbacks_server, run_every_runcb, local_storage); steam_gameserver_ugc = new Steam_UGC(settings_server, callback_results_server, callbacks_server); steam_gameserver_apps = new Steam_Apps(settings_server, callback_results_server); steam_gameserver_networking_sockets = new Steam_Networking_Sockets(settings_server, network, callback_results_server, callbacks_server, run_every_runcb); diff --git a/dll/steam_controller.h b/dll/steam_controller.h index 926013dd..1cd15e4c 100644 --- a/dll/steam_controller.h +++ b/dll/steam_controller.h @@ -682,7 +682,8 @@ int GetAnalogActionOrigins( InputHandle_t inputHandle, InputActionSetHandle_t ac break; } } - return 0; + + return count; } diff --git a/dll/steam_inventory.h b/dll/steam_inventory.h index 2a825111..3bd8f6e3 100644 --- a/dll/steam_inventory.h +++ b/dll/steam_inventory.h @@ -15,8 +15,8 @@ License along with the Goldberg Emulator; if not, see . */ -#include "item_db_loader.h" -#include +#include "base.h" // For SteamItemDef_t +#include "../json/json.hpp" struct Steam_Inventory_Requests { double timeout = 0.1; @@ -42,21 +42,23 @@ class Steam_Inventory : public ISteamInventory002, public ISteamInventory { +public: + static constexpr auto items_user_file = "items.json"; + static constexpr auto items_default_file = "default_items.json"; + +private: class Settings *settings; class SteamCallResults *callback_results; class SteamCallBacks *callbacks; class RunEveryRunCB *run_every_runcb; + class Local_Storage* local_storage; std::vector inventory_requests; - std::map> items; - // Like typedefs - using item_iterator = std::map>::iterator; - using attr_iterator = std::map::iterator; + nlohmann::json defined_items; + nlohmann::json user_items; - std::atomic_bool items_loaded; - std::string items_db_file; - std::once_flag load_items_flag; + bool inventory_loaded; bool call_definition_update; bool item_definitions_loaded; @@ -129,19 +131,7 @@ Steam_Inventory(class Settings *settings, class SteamCallResults *callback_resul call_definition_update(false), item_definitions_loaded(false) { - items_db_file = items_db_file_path; - PRINT_DEBUG("Items file path: %s\n", items_db_file.c_str()); - items_loaded = false; - - this->settings = settings; - this->callbacks = callbacks; - this->callback_results = callback_results; - this->run_every_runcb = run_every_runcb; this->run_every_runcb->add(&Steam_Inventory::run_every_runcb_cb, this); - - call_definition_update = false; - definition_update_called = false; - full_update_called = false; } ~Steam_Inventory() @@ -188,6 +178,7 @@ bool GetResultItems( SteamInventoryResult_t resultHandle, struct Steam_Inventory_Requests *request = get_inventory_result(resultHandle); if (!request) return false; if (!request->result_done()) return false; + if (!inventory_loaded) return false; if (pOutItemsArray != nullptr) { @@ -196,11 +187,18 @@ bool GetResultItems( SteamInventoryResult_t resultHandle, if (request->full_query) { // We end if we reached the end of items or the end of buffer - for( auto i = items.begin(); i != items.end() && max_items; ++i, --max_items ) + for( auto i = user_items.begin(); i != user_items.end() && max_items; ++i, --max_items ) { - pOutItemsArray->m_iDefinition = i->first; - pOutItemsArray->m_itemId = i->first; - pOutItemsArray->m_unQuantity = 1; + pOutItemsArray->m_iDefinition = std::stoi(i.key()); + pOutItemsArray->m_itemId = pOutItemsArray->m_iDefinition; + try + { + pOutItemsArray->m_unQuantity = i.value().get(); + } + catch (...) + { + pOutItemsArray->m_unQuantity = 0; + } pOutItemsArray->m_unFlags = k_ESteamItemNoTrade; ++pOutItemsArray; } @@ -602,15 +600,15 @@ bool GetItemDefinitionIDs( if (pItemDefIDs == nullptr) { - *punItemDefIDsArraySize = items.size(); + *punItemDefIDsArraySize = defined_items.size(); return true; } - if (*punItemDefIDsArraySize < items.size()) + if (*punItemDefIDsArraySize < defined_items.size()) return false; - for (auto& i : items) - *pItemDefIDs++ = i.first; + for (auto i = defined_items.begin(); i != defined_items.end(); ++i) + *pItemDefIDs++ = std::stoi(i.key()); return true; } @@ -631,25 +629,39 @@ bool GetItemDefinitionProperty( SteamItemDef_t iDefinition, const char *pchPrope PRINT_DEBUG("GetItemDefinitionProperty %i %s\n", iDefinition, pchPropertyName); std::lock_guard lock(global_mutex); - item_iterator item; - if ((item = items.find(iDefinition)) != items.end()) + auto item = defined_items.find(std::to_string(iDefinition)); + if (item != defined_items.end()) { - attr_iterator attr; if (pchPropertyName != nullptr) { // Should I check for punValueBufferSizeOut == nullptr ? // Try to get the property - if ((attr = item->second.find(pchPropertyName)) != items[iDefinition].end()) + auto attr = item.value().find(pchPropertyName); + if (attr != item.value().end()) { - std::string const& val = attr->second; + std::string val; + try + { + val = attr.value().get(); + } + catch (...) + { + pchPropertyName = ""; + *punValueBufferSizeOut = 0; + PRINT_DEBUG("Error, item: %d, attr: %s is not a string!", iDefinition, pchPropertyName); + return true; + } if (pchValueBuffer != nullptr) { // copy what we can strncpy(pchValueBuffer, val.c_str(), *punValueBufferSizeOut); + *punValueBufferSizeOut = std::min(static_cast(val.length() + 1), *punValueBufferSizeOut); + } + else + { + // Set punValueBufferSizeOut to the property size + *punValueBufferSizeOut = val.length() + 1; } - - // Set punValueBufferSizeOut to the property size - *punValueBufferSizeOut = std::min(static_cast(val.length() + 1), *punValueBufferSizeOut); if (pchValueBuffer != nullptr) { @@ -672,8 +684,8 @@ bool GetItemDefinitionProperty( SteamItemDef_t iDefinition, const char *pchPrope { // Should I check for punValueBufferSizeOut == nullptr ? *punValueBufferSizeOut = 0; - for (auto& i : item->second) - *punValueBufferSizeOut += i.first.length() + 1; // Size of key + comma, and the last is not a comma but null char + for (auto i = item.value().begin(); i != item.value().end(); ++i) + *punValueBufferSizeOut += i.key().length() + 1; // Size of key + comma, and the last is not a comma but null char } else { @@ -681,16 +693,16 @@ bool GetItemDefinitionProperty( SteamItemDef_t iDefinition, const char *pchPrope uint32_t len = *punValueBufferSizeOut-1; *punValueBufferSizeOut = 0; memset(pchValueBuffer, 0, len); - for( auto i = item->second.begin(); i != item->second.end() && len > 0; ++i ) + for( auto i = item.value().begin(); i != item.value().end() && len > 0; ++i ) { - strncat(pchValueBuffer, i->first.c_str(), len); + strncat(pchValueBuffer, i.key().c_str(), len); // Count how many chars we copied // Either the string length or the buffer size if its too small - uint32 x = std::min(len, static_cast(i->first.length())); + uint32 x = std::min(len, static_cast(i.key().length())); *punValueBufferSizeOut += x; len -= x; - if (len && std::distance(i, item->second.end()) != 1) // If this is not the last item, add a comma + if (len && std::distance(i, item.value().end()) != 1) // If this is not the last item, add a comma strncat(pchValueBuffer, ",", len--); // Always add 1, its a comma or the null terminator @@ -745,7 +757,10 @@ STEAM_CALL_RESULT( SteamInventoryRequestPricesResult_t ) SteamAPICall_t RequestPrices() { PRINT_DEBUG("RequestPrices\n"); - return 0; + SteamInventoryRequestPricesResult_t data; + data.m_result = k_EResultOK; + memcpy(data.m_rgchCurrency, "USD", 4); + return callback_results->addCallResult(data.k_iCallback, &data, sizeof(data), 0.2); } @@ -753,6 +768,7 @@ SteamAPICall_t RequestPrices() uint32 GetNumItemsWithPrices() { PRINT_DEBUG("GetNumItemsWithPrices\n"); + return 0; } bool GetItemsWithPrices( STEAM_ARRAY_COUNT(unArrayLength) STEAM_OUT_ARRAY_COUNT(pArrayItemDefs, Items with prices) SteamItemDef_t *pArrayItemDefs, @@ -761,6 +777,7 @@ bool GetItemsWithPrices( STEAM_ARRAY_COUNT(unArrayLength) STEAM_OUT_ARRAY_COUNT( uint32 unArrayLength ) { PRINT_DEBUG("GetItemsWithPrices\n"); + return false; } // Returns item definition ids and their prices in the user's local currency. @@ -836,7 +853,7 @@ void RunCallbacks() //only gets called once //also gets called when getting items SteamInventoryDefinitionUpdate_t data = {}; - callbacks->addCBResult(data.k_iCallback, &data, sizeof(data)); + callbacks->addCBResult(data.k_iCallback, &data, sizeof(data), 0.05); } call_definition_update = false; @@ -850,8 +867,7 @@ void RunCallbacks() if (inventory_loaded) { std::chrono::system_clock::time_point now = std::chrono::system_clock::now(); - - for (auto & r : inventory_requests) { + for (auto& r : inventory_requests) { if (!r.done && std::chrono::duration_cast>(now - r.time_created).count() > r.timeout) { if (r.full_query) { // SteamInventoryFullUpdate_t callbacks are triggered when GetAllItems diff --git a/dll/steam_matchmaking.h b/dll/steam_matchmaking.h index 64d40971..d89e9fa6 100644 --- a/dll/steam_matchmaking.h +++ b/dll/steam_matchmaking.h @@ -1165,16 +1165,21 @@ void RunCallbacks() } } } else { - int compare_to = stoi(value->second, 0, 0); - PRINT_DEBUG("Compare Values %i %i\n", compare_to, f.value_int); - if (f.eComparisonType == k_ELobbyComparisonEqual) { - if (compare_to == f.value_int) { - PRINT_DEBUG("Equal\n"); - //use = use; - } else { - PRINT_DEBUG("Not Equal\n"); - use = false; + try { + int compare_to = std::stoi(value->second, 0, 0); + PRINT_DEBUG("Compare Values %i %i\n", compare_to, f.value_int); + if (f.eComparisonType == k_ELobbyComparisonEqual) { + if (compare_to == f.value_int) { + PRINT_DEBUG("Equal\n"); + //use = use; + } else { + PRINT_DEBUG("Not Equal\n"); + use = false; + } } + } catch (...) { + //Same case as if the key is not in the lobby? + use = false; } //TODO: add more comparisons } diff --git a/dll/steam_networking_sockets.h b/dll/steam_networking_sockets.h index 818f2b9e..f72f244d 100644 --- a/dll/steam_networking_sockets.h +++ b/dll/steam_networking_sockets.h @@ -1045,6 +1045,7 @@ bool GetHostedDedicatedServerAddress001( SteamDatagramHostedAddress *pRouting ) /// directly share it with clients. virtual EResult GetHostedDedicatedServerAddress( SteamDatagramHostedAddress *pRouting ) { + PRINT_DEBUG("Steam_Networking_Sockets::GetHostedDedicatedServerAddress %p\n", pRouting); std::lock_guard lock(global_mutex); pRouting->SetDevAddress(network->getOwnIP(), 27054); return k_EResultOK; diff --git a/dll/steam_networking_utils.h b/dll/steam_networking_utils.h index 4a2a59e4..96aff6b4 100644 --- a/dll/steam_networking_utils.h +++ b/dll/steam_networking_utils.h @@ -28,6 +28,7 @@ public ISteamNetworkingUtils class RunEveryRunCB *run_every_runcb; std::chrono::time_point initialized_time = std::chrono::steady_clock::now(); FSteamNetworkingSocketsDebugOutput debug_function; + bool relay_initialized = false; public: static void steam_callback(void *object, Common_Message *msg) @@ -67,6 +68,7 @@ Steam_Networking_Utils(class Settings *settings, class Networking *network, clas bool InitializeRelayAccess() { PRINT_DEBUG("Steam_Networking_Utils::InitializeRelayAccess\n"); + relay_initialized = true; return true; } @@ -80,44 +82,71 @@ bool InitializeRelayAccess() /// more details, you can pass a non-NULL value. ESteamNetworkingAvailability GetRelayNetworkStatus( SteamRelayNetworkStatus_t *pDetails ) { - PRINT_DEBUG("Steam_Networking_Utils::GetRelayNetworkStatus\n"); + PRINT_DEBUG("Steam_Networking_Utils::GetRelayNetworkStatus %p\n", pDetails); + std::lock_guard lock(global_mutex); + + //TODO: check if this is how real steam returns it + SteamRelayNetworkStatus_t data = {}; + if (relay_initialized) { + data.m_eAvail = k_ESteamNetworkingAvailability_Current; + data.m_bPingMeasurementInProgress = 0; + data.m_eAvailAnyRelay = k_ESteamNetworkingAvailability_Current; + data.m_eAvailNetworkConfig = k_ESteamNetworkingAvailability_Current; + strcpy(data.m_debugMsg, "OK"); + } + + if (pDetails) { + *pDetails = data; + } + return k_ESteamNetworkingAvailability_Current; } float GetLocalPingLocation( SteamNetworkPingLocation_t &result ) { PRINT_DEBUG("Steam_Networking_Utils::GetLocalPingLocation\n"); + if (relay_initialized) { + result.m_data[2] = 123; + result.m_data[8] = 67; + return 2.0; + } + return -1; } int EstimatePingTimeBetweenTwoLocations( const SteamNetworkPingLocation_t &location1, const SteamNetworkPingLocation_t &location2 ) { PRINT_DEBUG("Steam_Networking_Utils::EstimatePingTimeBetweenTwoLocations\n"); - return k_nSteamNetworkingPing_Unknown ; + //return k_nSteamNetworkingPing_Unknown; + return 10; } int EstimatePingTimeFromLocalHost( const SteamNetworkPingLocation_t &remoteLocation ) { PRINT_DEBUG("Steam_Networking_Utils::EstimatePingTimeFromLocalHost\n"); + return 10; } void ConvertPingLocationToString( const SteamNetworkPingLocation_t &location, char *pszBuf, int cchBufSize ) { PRINT_DEBUG("Steam_Networking_Utils::ConvertPingLocationToString\n"); + strncpy(pszBuf, "fra=10+2", cchBufSize); } bool ParsePingLocationString( const char *pszString, SteamNetworkPingLocation_t &result ) { PRINT_DEBUG("Steam_Networking_Utils::ParsePingLocationString\n"); + return true; } bool CheckPingDataUpToDate( float flMaxAgeSeconds ) { PRINT_DEBUG("Steam_Networking_Utils::CheckPingDataUpToDate %f\n", flMaxAgeSeconds); + relay_initialized = true; return true; } @@ -125,6 +154,7 @@ bool CheckPingDataUpToDate( float flMaxAgeSeconds ) bool IsPingMeasurementInProgress() { PRINT_DEBUG("Steam_Networking_Utils::IsPingMeasurementInProgress\n"); + return false; } diff --git a/dll/steam_remote_storage.h b/dll/steam_remote_storage.h index d0c7646b..7709b93c 100644 --- a/dll/steam_remote_storage.h +++ b/dll/steam_remote_storage.h @@ -69,7 +69,7 @@ Steam_Remote_Storage(class Settings *settings, Local_Storage *local_storage, cla this->local_storage = local_storage; this->callback_results = callback_results; steam_cloud_enabled = true; - local_storage->update_save_filenames(REMOTE_STORAGE_FOLDER); + local_storage->update_save_filenames(Local_Storage::remote_storage_folder); } // NOTE @@ -83,8 +83,12 @@ Steam_Remote_Storage(class Settings *settings, Local_Storage *local_storage, cla bool FileWrite( const char *pchFile, const void *pvData, int32 cubData ) { PRINT_DEBUG("Steam_Remote_Storage::FileWrite %s %u\n", pchFile, cubData); + if (!pchFile || cubData <= 0 || cubData > k_unMaxCloudFileChunkSize) { + return false; + } + std::lock_guard lock(global_mutex); - int data_stored = local_storage->store_data(REMOTE_STORAGE_FOLDER, pchFile, (char* )pvData, cubData); + int data_stored = local_storage->store_data(Local_Storage::remote_storage_folder, pchFile, (char* )pvData, cubData); PRINT_DEBUG("Steam_Remote_Storage::Stored %i, %u\n", data_stored, data_stored == cubData); return data_stored == cubData; } @@ -93,7 +97,7 @@ int32 FileRead( const char *pchFile, void *pvData, int32 cubDataToRead ) { PRINT_DEBUG("Steam_Remote_Storage::FileRead %s %i\n", pchFile, cubDataToRead); std::lock_guard lock(global_mutex); - int read_data = local_storage->get_data(REMOTE_STORAGE_FOLDER, pchFile, (char* )pvData, cubDataToRead); + int read_data = local_storage->get_data(Local_Storage::remote_storage_folder, pchFile, (char* )pvData, cubDataToRead); if (read_data < 0) read_data = 0; PRINT_DEBUG("Read %i\n", read_data); return read_data; @@ -103,12 +107,16 @@ STEAM_CALL_RESULT( RemoteStorageFileWriteAsyncComplete_t ) SteamAPICall_t FileWriteAsync( const char *pchFile, const void *pvData, uint32 cubData ) { PRINT_DEBUG("Steam_Remote_Storage::FileWriteAsync\n"); - std::lock_guard lock(global_mutex); - bool success = local_storage->store_data(REMOTE_STORAGE_FOLDER, pchFile, (char* )pvData, cubData) == cubData; - RemoteStorageFileWriteAsyncComplete_t data; - data.m_eResult = k_EResultOK; + if (!pchFile || cubData > k_unMaxCloudFileChunkSize || cubData == 0) { + return k_uAPICallInvalid; + } - return callback_results->addCallResult(data.k_iCallback, &data, sizeof(data)); + std::lock_guard lock(global_mutex); + bool success = local_storage->store_data(Local_Storage::remote_storage_folder, pchFile, (char* )pvData, cubData) == cubData; + RemoteStorageFileWriteAsyncComplete_t data; + data.m_eResult = success ? k_EResultOK : k_EResultFail; + + return callback_results->addCallResult(data.k_iCallback, &data, sizeof(data), 0.01); } @@ -118,7 +126,7 @@ SteamAPICall_t FileReadAsync( const char *pchFile, uint32 nOffset, uint32 cubToR PRINT_DEBUG("Steam_Remote_Storage::FileReadAsync\n"); std::lock_guard lock(global_mutex); - unsigned int size = local_storage->file_size(REMOTE_STORAGE_FOLDER, pchFile); + unsigned int size = local_storage->file_size(Local_Storage::remote_storage_folder, pchFile); RemoteStorageFileReadAsyncComplete_t data; if (size <= nOffset) { @@ -153,7 +161,7 @@ bool FileReadAsyncComplete( SteamAPICall_t hReadCall, void *pvBuffer, uint32 cub return false; char *temp = new char[a_read->size]; - int read_data = local_storage->get_data(REMOTE_STORAGE_FOLDER, a_read->file_name, (char* )temp, a_read->size); + int read_data = local_storage->get_data(Local_Storage::remote_storage_folder, a_read->file_name, (char* )temp, a_read->size); if (read_data < a_read->to_read + a_read->offset) { delete[] temp; return false; @@ -175,7 +183,7 @@ bool FileForget( const char *pchFile ) bool FileDelete( const char *pchFile ) { PRINT_DEBUG("Steam_Remote_Storage::FileDelete\n"); - return local_storage->file_delete(REMOTE_STORAGE_FOLDER, pchFile); + return local_storage->file_delete(Local_Storage::remote_storage_folder, pchFile); } STEAM_CALL_RESULT( RemoteStorageFileShareResult_t ) @@ -184,7 +192,7 @@ SteamAPICall_t FileShare( const char *pchFile ) PRINT_DEBUG("Steam_Remote_Storage::FileShare\n"); std::lock_guard lock(global_mutex); RemoteStorageFileShareResult_t data = {}; - if (local_storage->file_exists(REMOTE_STORAGE_FOLDER, pchFile)) { + if (local_storage->file_exists(Local_Storage::remote_storage_folder, pchFile)) { data.m_eResult = k_EResultOK; data.m_hFile = generate_steam_api_call_id(); strncpy(data.m_rgchFilename, pchFile, sizeof(data.m_rgchFilename) - 1); @@ -237,7 +245,7 @@ bool FileWriteStreamClose( UGCFileWriteStreamHandle_t writeHandle ) if (stream_writes.end() == request) return false; - local_storage->store_data(REMOTE_STORAGE_FOLDER, request->file_name, request->file_data.data(), request->file_data.size()); + local_storage->store_data(Local_Storage::remote_storage_folder, request->file_name, request->file_data.data(), request->file_data.size()); stream_writes.erase(request); return true; } @@ -258,25 +266,25 @@ bool FileWriteStreamCancel( UGCFileWriteStreamHandle_t writeHandle ) bool FileExists( const char *pchFile ) { PRINT_DEBUG("Steam_Remote_Storage::FileExists %s\n", pchFile); - return local_storage->file_exists(REMOTE_STORAGE_FOLDER, pchFile); + return local_storage->file_exists(Local_Storage::remote_storage_folder, pchFile); } bool FilePersisted( const char *pchFile ) { PRINT_DEBUG("Steam_Remote_Storage::FilePersisted\n"); - return local_storage->file_exists(REMOTE_STORAGE_FOLDER, pchFile); + return local_storage->file_exists(Local_Storage::remote_storage_folder, pchFile); } int32 GetFileSize( const char *pchFile ) { PRINT_DEBUG("Steam_Remote_Storage::GetFileSize %s\n", pchFile); - return local_storage->file_size(REMOTE_STORAGE_FOLDER, pchFile); + return local_storage->file_size(Local_Storage::remote_storage_folder, pchFile); } int64 GetFileTimestamp( const char *pchFile ) { PRINT_DEBUG("Steam_Remote_Storage::GetFileTimestamp\n"); - return local_storage->file_timestamp(REMOTE_STORAGE_FOLDER, pchFile); + return local_storage->file_timestamp(Local_Storage::remote_storage_folder, pchFile); } ERemoteStoragePlatform GetSyncPlatforms( const char *pchFile ) @@ -290,7 +298,7 @@ ERemoteStoragePlatform GetSyncPlatforms( const char *pchFile ) int32 GetFileCount() { PRINT_DEBUG("Steam_Remote_Storage::GetFileCount\n"); - int32 num = local_storage->count_files(REMOTE_STORAGE_FOLDER); + int32 num = local_storage->count_files(Local_Storage::remote_storage_folder); PRINT_DEBUG("Steam_Remote_Storage::File count: %i\n", num); return num; } @@ -299,7 +307,7 @@ const char *GetFileNameAndSize( int iFile, int32 *pnFileSizeInBytes ) { PRINT_DEBUG("Steam_Remote_Storage::GetFileNameAndSize %i\n", iFile); static char output_filename[MAX_FILENAME_LENGTH]; - if (local_storage->iterate_file(REMOTE_STORAGE_FOLDER, iFile, output_filename, pnFileSizeInBytes)) { + if (local_storage->iterate_file(Local_Storage::remote_storage_folder, iFile, output_filename, pnFileSizeInBytes)) { PRINT_DEBUG("Steam_Remote_Storage::Name: |%s|, size: %i\n", output_filename, pnFileSizeInBytes ? *pnFileSizeInBytes : 0); return output_filename; } else { @@ -372,7 +380,7 @@ SteamAPICall_t UGCDownload( UGCHandle_t hContent, uint32 unPriority ) data.m_eResult = k_EResultOK; data.m_hFile = hContent; data.m_nAppID = settings->get_local_game_id().AppID(); - data.m_nSizeInBytes = local_storage->file_size(REMOTE_STORAGE_FOLDER, shared_files[hContent]); + data.m_nSizeInBytes = local_storage->file_size(Local_Storage::remote_storage_folder, shared_files[hContent]); shared_files[hContent].copy(data.m_pchFileName, sizeof(data.m_pchFileName) - 1); data.m_ulSteamIDOwner = settings->get_local_steam_id().ConvertToUint64(); downloaded_files[hContent].file = shared_files[hContent]; @@ -426,7 +434,7 @@ int32 UGCRead( UGCHandle_t hContent, void *pvData, int32 cubDataToRead, uint32 c } Downloaded_File f = downloaded_files[hContent]; - int read_data = local_storage->get_data(REMOTE_STORAGE_FOLDER, f.file, (char* )pvData, cubDataToRead, cOffset); + int read_data = local_storage->get_data(Local_Storage::remote_storage_folder, f.file, (char* )pvData, cubDataToRead, cOffset); if (eAction == k_EUGCRead_Close || (eAction == k_EUGCRead_ContinueReadingUntilFinished && (read_data < cubDataToRead || (cOffset + cubDataToRead) >= f.total_size))) { downloaded_files.erase(hContent); diff --git a/dll/steam_user.h b/dll/steam_user.h index 93cc4285..35875eca 100644 --- a/dll/steam_user.h +++ b/dll/steam_user.h @@ -149,7 +149,7 @@ bool GetUserDataFolder( char *pchBuffer, int cubBuffer ) PRINT_DEBUG("GetUserDataFolder\n"); if (!cubBuffer) return false; - std::string user_data = local_storage->get_path(USER_DATA_FOLDER); + std::string user_data = local_storage->get_path(Local_Storage::user_data_storage); strncpy(pchBuffer, user_data.c_str(), cubBuffer - 1); pchBuffer[cubBuffer - 1] = 0; return true; diff --git a/dll/steam_user_stats.h b/dll/steam_user_stats.h index 291ab15d..974b8543 100644 --- a/dll/steam_user_stats.h +++ b/dll/steam_user_stats.h @@ -51,7 +51,7 @@ private: SteamCallResults* callback_results; class SteamCallBacks* callbacks; std::vector leaderboards; - + nlohmann::json defined_achievements; nlohmann::json user_achievements; diff --git a/files_example/steam_settings.EXAMPLE/achievements_EXAMPLE.json b/files_example/steam_settings.EXAMPLE/achievements_EXAMPLE.json new file mode 100644 index 00000000..b5788f64 --- /dev/null +++ b/files_example/steam_settings.EXAMPLE/achievements_EXAMPLE.json @@ -0,0 +1,2098 @@ +[ + { + "description": "Complete Burning Paris on Survival Normal difficulty", + "displayName": "Tower Tussle", + "hidden": "0", + "icon": "images/Achievement_0.jpg", + "icongray": "images/Achievement_0_gray.jpg", + "name": "Achievement_0" + }, + { + "description": "Complete Burning Paris on Survival Hard difficulty", + "displayName": "Seine Skirmish", + "hidden": "0", + "icon": "images/Achievement_1.jpg", + "icongray": "images/Achievement_1_gray.jpg", + "name": "Achievement_1" + }, + { + "description": "Beat Burning Paris on Suicidal", + "displayName": "Bastille Brawl", + "hidden": "0", + "icon": "images/Achievement_2.jpg", + "icongray": "images/Achievement_2_gray.jpg", + "name": "Achievement_2" + }, + { + "description": "Beat Burning Paris on Hell on Earth", + "displayName": "Arc Action", + "hidden": "0", + "icon": "images/Achievement_3.jpg", + "icongray": "images/Achievement_3_gray.jpg", + "name": "Achievement_3" + }, + { + "description": "Beat Outpost on Normal", + "displayName": "You Can't Fight In Here, This Is The Control Room", + "hidden": "0", + "icon": "images/Achievement_4.jpg", + "icongray": "images/Achievement_4_gray.jpg", + "name": "Achievement_4" + }, + { + "description": "Beat Outpost on Hard", + "displayName": "This Is What Happens When You Meet A Zed In The Alps", + "hidden": "0", + "icon": "images/Achievement_5.jpg", + "icongray": "images/Achievement_5_gray.jpg", + "name": "Achievement_5" + }, + { + "description": "Beat Outpost on Suicidal", + "displayName": "The Shield Doors Must Be Closed", + "hidden": "0", + "icon": "images/Achievement_6.jpg", + "icongray": "images/Achievement_6_gray.jpg", + "name": "Achievement_6" + }, + { + "description": "Beat Outpost on Hell on Earth", + "displayName": "Fear Is For The Zeds, My Little Lord", + "hidden": "0", + "icon": "images/Achievement_7.jpg", + "icongray": "images/Achievement_7_gray.jpg", + "name": "Achievement_7" + }, + { + "description": "Beat Biotics Lab on Normal", + "displayName": "Open For Testing", + "hidden": "0", + "icon": "images/Achievement_8.jpg", + "icongray": "images/Achievement_8_gray.jpg", + "name": "Achievement_8" + }, + { + "description": "Beat Biotics Lab on Hard", + "displayName": "Limited Contact", + "hidden": "0", + "icon": "images/Achievement_9.jpg", + "icongray": "images/Achievement_9_gray.jpg", + "name": "Achievement_9" + }, + { + "description": "Beat Biotics Lab on Suicidal", + "displayName": "Restricted Access", + "hidden": "0", + "icon": "images/Achievement_10.jpg", + "icongray": "images/Achievement_10_gray.jpg", + "name": "Achievement_10" + }, + { + "description": "Beat Biotics Lab on Hell on Earth", + "displayName": "Controlled Environment", + "hidden": "0", + "icon": "images/Achievement_11.jpg", + "icongray": "images/Achievement_11_gray.jpg", + "name": "Achievement_11" + }, + { + "description": "Beat Volter Manor on Normal", + "displayName": "Just Visiting", + "hidden": "0", + "icon": "images/Achievement_12.jpg", + "icongray": "images/Achievement_12_gray.jpg", + "name": "Achievement_12" + }, + { + "description": "Beat Volter Manor on Hard", + "displayName": "Mind Your Manor", + "hidden": "0", + "icon": "images/Achievement_13.jpg", + "icongray": "images/Achievement_13_gray.jpg", + "name": "Achievement_13" + }, + { + "description": "Beat Volter Manor on Suicidal", + "displayName": "Settling In", + "hidden": "0", + "icon": "images/Achievement_14.jpg", + "icongray": "images/Achievement_14_gray.jpg", + "name": "Achievement_14" + }, + { + "description": "Complete Volter Manor on Survival Hell On Earth difficulty", + "displayName": "Lord of the Manor", + "hidden": "0", + "icon": "images/Achievement_15.jpg", + "icongray": "images/Achievement_15_gray.jpg", + "name": "Achievement_15" + }, + { + "description": "Collect all the items on Burning Paris", + "displayName": "Paris Plunder", + "hidden": "0", + "icon": "images/Achievement_16.jpg", + "icongray": "images/Achievement_16_gray.jpg", + "name": "Achievement_16" + }, + { + "description": "Collect all the items on Outpost", + "displayName": "Outpost Offerings", + "hidden": "0", + "icon": "images/Achievement_17.jpg", + "icongray": "images/Achievement_17_gray.jpg", + "name": "Achievement_17" + }, + { + "description": "Collect all the items on Biotics Lab", + "displayName": "Biotics Bling", + "hidden": "0", + "icon": "images/Achievement_18.jpg", + "icongray": "images/Achievement_18_gray.jpg", + "name": "Achievement_18" + }, + { + "description": "Collect all the items on Volter Manor", + "displayName": "Manor Money", + "hidden": "0", + "icon": "images/Achievement_19.jpg", + "icongray": "images/Achievement_19_gray.jpg", + "name": "Achievement_19" + }, + { + "description": "Complete Evacuation Point on Survival Normal difficulty", + "displayName": "The Suite Life", + "hidden": "0", + "icon": "images/Achievement_20.jpg", + "icongray": "images/Achievement_20_gray.jpg", + "name": "Achievement_20" + }, + { + "description": "Complete Evacuation Point on Survival Hard difficulty", + "displayName": "Unsinkable II", + "hidden": "0", + "icon": "images/Achievement_21.jpg", + "icongray": "images/Achievement_21_gray.jpg", + "name": "Achievement_21" + }, + { + "description": "Complete Evacuation Point on Survival Suicidal difficulty", + "displayName": "Bow Movement", + "hidden": "0", + "icon": "images/Achievement_22.jpg", + "icongray": "images/Achievement_22_gray.jpg", + "name": "Achievement_22" + }, + { + "description": "Complete Evacuation Point on Survival Hell On Earth difficulty", + "displayName": "Seas The Day", + "hidden": "0", + "icon": "images/Achievement_23.jpg", + "icongray": "images/Achievement_23_gray.jpg", + "name": "Achievement_23" + }, + { + "description": "Complete Catacombs on Survival Normal difficulty", + "displayName": "A Light In The Darkness", + "hidden": "0", + "icon": "images/Achievement_24.jpg", + "icongray": "images/Achievement_24_gray.jpg", + "name": "Achievement_24" + }, + { + "description": "Complete Catacombs on Survival Hard difficulty", + "displayName": "This Is No Mine", + "hidden": "0", + "icon": "images/Achievement_25.jpg", + "icongray": "images/Achievement_25_gray.jpg", + "name": "Achievement_25" + }, + { + "description": "Complete Catacombs on Survival Suicidal difficulty", + "displayName": "This Is A Tomb, Theirs", + "hidden": "0", + "icon": "images/Achievement_26.jpg", + "icongray": "images/Achievement_26_gray.jpg", + "name": "Achievement_26" + }, + { + "description": "Complete Catacombs on Survival Hell On Earth difficulty", + "displayName": "They Shall Not Pass", + "hidden": "0", + "icon": "images/Achievement_27.jpg", + "icongray": "images/Achievement_27_gray.jpg", + "name": "Achievement_27" + }, + { + "description": "Collect all the items on Evacuation Point", + "displayName": "Point Paper", + "hidden": "0", + "icon": "images/Achievement_28.jpg", + "icongray": "images/Achievement_28_gray.jpg", + "name": "Achievement_28" + }, + { + "description": "Collect all the items on Catacombs", + "displayName": "Catacombs Cash", + "hidden": "0", + "icon": "images/Achievement_29.jpg", + "icongray": "images/Achievement_29_gray.jpg", + "name": "Achievement_29" + }, + { + "description": "Reach Level 5 Berserker", + "displayName": "Reach Level 5 Berserker", + "hidden": "0", + "icon": "images/Achievement_30.jpg", + "icongray": "images/Achievement_30_gray.jpg", + "name": "Achievement_30" + }, + { + "description": "Reach Level 10 Berserker", + "displayName": "Reach Level 10 Berserker", + "hidden": "0", + "icon": "images/Achievement_31.jpg", + "icongray": "images/Achievement_31_gray.jpg", + "name": "Achievement_31" + }, + { + "description": "Reach Level 15 Berserker", + "displayName": "Reach Level 15 Berserker", + "hidden": "0", + "icon": "images/Achievement_32.jpg", + "icongray": "images/Achievement_32_gray.jpg", + "name": "Achievement_32" + }, + { + "description": "Reach Level 20 Berserker", + "displayName": "Reach Level 20 Berserker", + "hidden": "0", + "icon": "images/Achievement_33.jpg", + "icongray": "images/Achievement_33_gray.jpg", + "name": "Achievement_33" + }, + { + "description": "Reach Level 25 Berserker", + "displayName": "Reach Level 25 Berserker", + "hidden": "0", + "icon": "images/Achievement_34.jpg", + "icongray": "images/Achievement_34_gray.jpg", + "name": "Achievement_34" + }, + { + "description": "Reach Level 5 Medic", + "displayName": "Reach Level 5 Medic", + "hidden": "0", + "icon": "images/Achievement_35.jpg", + "icongray": "images/Achievement_35_gray.jpg", + "name": "Achievement_35" + }, + { + "description": "Reach Level 10 Medic", + "displayName": "Reach Level 10 Medic", + "hidden": "0", + "icon": "images/Achievement_36.jpg", + "icongray": "images/Achievement_36_gray.jpg", + "name": "Achievement_36" + }, + { + "description": "Reach Level 15 Medic", + "displayName": "Reach Level 15 Medic", + "hidden": "0", + "icon": "images/Achievement_37.jpg", + "icongray": "images/Achievement_37_gray.jpg", + "name": "Achievement_37" + }, + { + "description": "Reach Level 20 Medic", + "displayName": "Reach Level 20 Medic", + "hidden": "0", + "icon": "images/Achievement_38.jpg", + "icongray": "images/Achievement_38_gray.jpg", + "name": "Achievement_38" + }, + { + "description": "Reach Level 25 Medic", + "displayName": "Reach Level 25 Medic", + "hidden": "0", + "icon": "images/Achievement_39.jpg", + "icongray": "images/Achievement_39_gray.jpg", + "name": "Achievement_39" + }, + { + "description": "Reach Level 5 Commando", + "displayName": "Reach Level 5 Commando", + "hidden": "0", + "icon": "images/Achievement_40.jpg", + "icongray": "images/Achievement_40_gray.jpg", + "name": "Achievement_40" + }, + { + "description": "Reach Level 10 Commando", + "displayName": "Reach Level 10 Commando", + "hidden": "0", + "icon": "images/Achievement_41.jpg", + "icongray": "images/Achievement_41_gray.jpg", + "name": "Achievement_41" + }, + { + "description": "Reach Level 15 Commando", + "displayName": "Reach Level 15 Commando", + "hidden": "0", + "icon": "images/Achievement_42.jpg", + "icongray": "images/Achievement_42_gray.jpg", + "name": "Achievement_42" + }, + { + "description": "Reach Level 20 Commando", + "displayName": "Reach Level 20 Commando", + "hidden": "0", + "icon": "images/Achievement_43.jpg", + "icongray": "images/Achievement_43_gray.jpg", + "name": "Achievement_43" + }, + { + "description": "Reach Level 25 Commando", + "displayName": "Reach Level 25 Commando", + "hidden": "0", + "icon": "images/Achievement_44.jpg", + "icongray": "images/Achievement_44_gray.jpg", + "name": "Achievement_44" + }, + { + "description": "Reach Level 5 Support", + "displayName": "Reach Level 5 Support", + "hidden": "0", + "icon": "images/Achievement_45.jpg", + "icongray": "images/Achievement_45_gray.jpg", + "name": "Achievement_45" + }, + { + "description": "Reach Level 10 Support", + "displayName": "Reach Level 10 Support", + "hidden": "0", + "icon": "images/Achievement_46.jpg", + "icongray": "images/Achievement_46_gray.jpg", + "name": "Achievement_46" + }, + { + "description": "Reach Level 15 Support", + "displayName": "Reach Level 15 Support", + "hidden": "0", + "icon": "images/Achievement_47.jpg", + "icongray": "images/Achievement_47_gray.jpg", + "name": "Achievement_47" + }, + { + "description": "Reach Level 20 Support", + "displayName": "Reach Level 20 Support", + "hidden": "0", + "icon": "images/Achievement_48.jpg", + "icongray": "images/Achievement_48_gray.jpg", + "name": "Achievement_48" + }, + { + "description": "Reach Level 25 Support", + "displayName": "Reach Level 25 Support", + "hidden": "0", + "icon": "images/Achievement_49.jpg", + "icongray": "images/Achievement_49_gray.jpg", + "name": "Achievement_49" + }, + { + "description": "Reach Level 5 Firebug", + "displayName": "Reach Level 5 Firebug", + "hidden": "0", + "icon": "images/Achievement_50.jpg", + "icongray": "images/Achievement_50_gray.jpg", + "name": "Achievement_50" + }, + { + "description": "Reach Level 10 Firebug", + "displayName": "Reach Level 10 Firebug", + "hidden": "0", + "icon": "images/Achievement_51.jpg", + "icongray": "images/Achievement_51_gray.jpg", + "name": "Achievement_51" + }, + { + "description": "Reach Level 15 Firebug", + "displayName": "Reach Level 15 Firebug", + "hidden": "0", + "icon": "images/Achievement_52.jpg", + "icongray": "images/Achievement_52_gray.jpg", + "name": "Achievement_52" + }, + { + "description": "Reach Level 20 Firebug", + "displayName": "Reach Level 20 Firebug", + "hidden": "0", + "icon": "images/Achievement_53.jpg", + "icongray": "images/Achievement_53_gray.jpg", + "name": "Achievement_53" + }, + { + "description": "Reach Level 25 Firebug", + "displayName": "Reach Level 25 Firebug", + "hidden": "0", + "icon": "images/Achievement_54.jpg", + "icongray": "images/Achievement_54_gray.jpg", + "name": "Achievement_54" + }, + { + "description": "Reach Level 5 Demolitions", + "displayName": "Reach Level 5 Demolitions", + "hidden": "0", + "icon": "images/Achievement_55.jpg", + "icongray": "images/Achievement_55_gray.jpg", + "name": "Achievement_55" + }, + { + "description": "Reach Level 10 Demolitions", + "displayName": "Reach Level 10 Demolitions", + "hidden": "0", + "icon": "images/Achievement_56.jpg", + "icongray": "images/Achievement_56_gray.jpg", + "name": "Achievement_56" + }, + { + "description": "Reach Level 15 Demolitions", + "displayName": "Reach Level 15 Demolitions", + "hidden": "0", + "icon": "images/Achievement_57.jpg", + "icongray": "images/Achievement_57_gray.jpg", + "name": "Achievement_57" + }, + { + "description": "Reach Level 20 Demolitions", + "displayName": "Reach Level 20 Demolitions", + "hidden": "0", + "icon": "images/Achievement_58.jpg", + "icongray": "images/Achievement_58_gray.jpg", + "name": "Achievement_58" + }, + { + "description": "Reach Level 25 Demolitions", + "displayName": "Reach Level 25 Demolitions", + "hidden": "0", + "icon": "images/Achievement_59.jpg", + "icongray": "images/Achievement_59_gray.jpg", + "name": "Achievement_59" + }, + { + "description": "Reach Level 5 Gunslinger", + "displayName": "Reach Level 5 Gunslinger", + "hidden": "0", + "icon": "images/Achievement_60.jpg", + "icongray": "images/Achievement_60_gray.jpg", + "name": "Achievement_60" + }, + { + "description": "Reach Level 10 Gunslinger", + "displayName": "Reach Level 10 Gunslinger", + "hidden": "0", + "icon": "images/Achievement_61.jpg", + "icongray": "images/Achievement_61_gray.jpg", + "name": "Achievement_61" + }, + { + "description": "Reach Level 15 Gunslinger", + "displayName": "Reach Level 15 Gunslinger", + "hidden": "0", + "icon": "images/Achievement_62.jpg", + "icongray": "images/Achievement_62_gray.jpg", + "name": "Achievement_62" + }, + { + "description": "Reach Level 20 Gunslinger", + "displayName": "Reach Level 20 Gunslinger", + "hidden": "0", + "icon": "images/Achievement_63.jpg", + "icongray": "images/Achievement_63_gray.jpg", + "name": "Achievement_63" + }, + { + "description": "Reach Level 25 Gunslinger", + "displayName": "Reach Level 25 Gunslinger", + "hidden": "0", + "icon": "images/Achievement_64.jpg", + "icongray": "images/Achievement_64_gray.jpg", + "name": "Achievement_64" + }, + { + "description": "Beat Any One Map as Berserker on Normal Difficulty", + "displayName": "Normal Berserker", + "hidden": "0", + "icon": "images/Achievement_65.jpg", + "icongray": "images/Achievement_65_gray.jpg", + "name": "Achievement_65" + }, + { + "description": "Beat Any One Map as Berserker on Hard Difficulty", + "displayName": "Hard Berserker", + "hidden": "0", + "icon": "images/Achievement_66.jpg", + "icongray": "images/Achievement_66_gray.jpg", + "name": "Achievement_66" + }, + { + "description": "Beat Any One Map as Berserker on Suicidal Difficulty", + "displayName": "Suicidal Berserker", + "hidden": "0", + "icon": "images/Achievement_67.jpg", + "icongray": "images/Achievement_67_gray.jpg", + "name": "Achievement_67" + }, + { + "description": "Beat Any One Map as Berserker on Hell on Earth Difficulty", + "displayName": "Hellish Berserker", + "hidden": "0", + "icon": "images/Achievement_68.jpg", + "icongray": "images/Achievement_68_gray.jpg", + "name": "Achievement_68" + }, + { + "description": "Beat Any One Map as Medic on Normal Difficulty", + "displayName": "Normal Medic", + "hidden": "0", + "icon": "images/Achievement_69.jpg", + "icongray": "images/Achievement_69_gray.jpg", + "name": "Achievement_69" + }, + { + "description": "Beat Any One Map as Medic on Hard Difficulty", + "displayName": "Hard Medic", + "hidden": "0", + "icon": "images/Achievement_70.jpg", + "icongray": "images/Achievement_70_gray.jpg", + "name": "Achievement_70" + }, + { + "description": "Beat Any One Map as Medic on Suicidal Difficulty", + "displayName": "Suicidal Medic", + "hidden": "0", + "icon": "images/Achievement_71.jpg", + "icongray": "images/Achievement_71_gray.jpg", + "name": "Achievement_71" + }, + { + "description": "Beat Any One Map as Medic on Hell on Earth Difficulty", + "displayName": "Hellish Medic", + "hidden": "0", + "icon": "images/Achievement_72.jpg", + "icongray": "images/Achievement_72_gray.jpg", + "name": "Achievement_72" + }, + { + "description": "Complete any map as a Commando on Survival Normal difficulty", + "displayName": "Normal Commando", + "hidden": "0", + "icon": "images/Achievement_73.jpg", + "icongray": "images/Achievement_73_gray.jpg", + "name": "Achievement_73" + }, + { + "description": "Complete any map as a Commando on Survival Hard difficulty", + "displayName": "Hard Commando", + "hidden": "0", + "icon": "images/Achievement_74.jpg", + "icongray": "images/Achievement_74_gray.jpg", + "name": "Achievement_74" + }, + { + "description": "Complete any map as a Commando on Survival Suicidal difficulty", + "displayName": "Suicidal Commando", + "hidden": "0", + "icon": "images/Achievement_75.jpg", + "icongray": "images/Achievement_75_gray.jpg", + "name": "Achievement_75" + }, + { + "description": "Complete any map as a Commando on Survival Hell On Earth difficulty", + "displayName": "Hellish Commando", + "hidden": "0", + "icon": "images/Achievement_76.jpg", + "icongray": "images/Achievement_76_gray.jpg", + "name": "Achievement_76" + }, + { + "description": "Complete any map as a Support on Survival Normal difficulty", + "displayName": "Normal Support", + "hidden": "0", + "icon": "images/Achievement_77.jpg", + "icongray": "images/Achievement_77_gray.jpg", + "name": "Achievement_77" + }, + { + "description": "Complete any map as a Support on Survival Hard difficulty", + "displayName": "Hard Support", + "hidden": "0", + "icon": "images/Achievement_78.jpg", + "icongray": "images/Achievement_78_gray.jpg", + "name": "Achievement_78" + }, + { + "description": "Complete any map as a Support on Survival Suicidal difficulty", + "displayName": "Suicidal Support", + "hidden": "0", + "icon": "images/Achievement_79.jpg", + "icongray": "images/Achievement_79_gray.jpg", + "name": "Achievement_79" + }, + { + "description": "Complete any map as a Support on Survival Hell On Earth difficulty", + "displayName": "Hellish Support", + "hidden": "0", + "icon": "images/Achievement_80.jpg", + "icongray": "images/Achievement_80_gray.jpg", + "name": "Achievement_80" + }, + { + "description": "Complete any map as a Firebug on Survival Normal difficulty", + "displayName": "Normal Firebug", + "hidden": "0", + "icon": "images/Achievement_81.jpg", + "icongray": "images/Achievement_81_gray.jpg", + "name": "Achievement_81" + }, + { + "description": "Complete any map as a Firebug on Survival Hard difficulty", + "displayName": "Hard Firebug", + "hidden": "0", + "icon": "images/Achievement_82.jpg", + "icongray": "images/Achievement_82_gray.jpg", + "name": "Achievement_82" + }, + { + "description": "Complete any map as a Firebug on Survival Suicidal difficulty", + "displayName": "Suicidal Firebug", + "hidden": "0", + "icon": "images/Achievement_83.jpg", + "icongray": "images/Achievement_83_gray.jpg", + "name": "Achievement_83" + }, + { + "description": "Complete any map as a Firebug on Survival Hell On Earth difficulty", + "displayName": "Hellish Firebug", + "hidden": "0", + "icon": "images/Achievement_84.jpg", + "icongray": "images/Achievement_84_gray.jpg", + "name": "Achievement_84" + }, + { + "description": "Complete any map as a Demolitionist on Survival Normal difficulty", + "displayName": "Normal Demolition", + "hidden": "0", + "icon": "images/Achievement_85.jpg", + "icongray": "images/Achievement_85_gray.jpg", + "name": "Achievement_85" + }, + { + "description": "Complete any map as a Demolitionist on Survival Hard difficulty", + "displayName": "Hard Demolition", + "hidden": "0", + "icon": "images/Achievement_86.jpg", + "icongray": "images/Achievement_86_gray.jpg", + "name": "Achievement_86" + }, + { + "description": "Complete any map as a Demolitionist on Survival Suicidal difficulty", + "displayName": "Suicidal Demolition", + "hidden": "0", + "icon": "images/Achievement_87.jpg", + "icongray": "images/Achievement_87_gray.jpg", + "name": "Achievement_87" + }, + { + "description": "Complete any map as a Demolitionist on Survival Hell On Earth difficulty", + "displayName": "Hellish Demolition", + "hidden": "0", + "icon": "images/Achievement_88.jpg", + "icongray": "images/Achievement_88_gray.jpg", + "name": "Achievement_88" + }, + { + "description": "Complete any map as a Gunslinger on Survival Normal difficulty", + "displayName": "Normal Gunslinger", + "hidden": "0", + "icon": "images/Achievement_89.jpg", + "icongray": "images/Achievement_89_gray.jpg", + "name": "Achievement_89" + }, + { + "description": "Complete any map as a Gunslinger on Survival Hard difficulty", + "displayName": "Hard Gunslinger", + "hidden": "0", + "icon": "images/Achievement_90.jpg", + "icongray": "images/Achievement_90_gray.jpg", + "name": "Achievement_90" + }, + { + "description": "Complete any map as a Gunslinger on Survival Suicidal difficulty", + "displayName": "Suicidal Gunslinger", + "hidden": "0", + "icon": "images/Achievement_91.jpg", + "icongray": "images/Achievement_91_gray.jpg", + "name": "Achievement_91" + }, + { + "description": "Complete any map as a Gunslinger on Survival Hell On Earth difficulty", + "displayName": "Hellish Gunslinger", + "hidden": "0", + "icon": "images/Achievement_92.jpg", + "icongray": "images/Achievement_92_gray.jpg", + "name": "Achievement_92" + }, + { + "description": "Reach Level 25 in all Perks", + "displayName": "Perked Up", + "hidden": "0", + "icon": "images/Achievement_93.jpg", + "icongray": "images/Achievement_93_gray.jpg", + "name": "Achievement_93" + }, + { + "description": "Complete Black Forest on Survival Normal difficulty", + "displayName": "Killer Korn", + "hidden": "0", + "icon": "images/Achievement_94.jpg", + "icongray": "images/Achievement_94_gray.jpg", + "name": "Achievement_94" + }, + { + "description": "Complete Black Forest on Survival Hard difficulty", + "displayName": "Lager Me Up", + "hidden": "0", + "icon": "images/Achievement_95.jpg", + "icongray": "images/Achievement_95_gray.jpg", + "name": "Achievement_95" + }, + { + "description": "Complete Black Forest on Survival Suicidal difficulty", + "displayName": "I'll Dopple Bock", + "hidden": "0", + "icon": "images/Achievement_96.jpg", + "icongray": "images/Achievement_96_gray.jpg", + "name": "Achievement_96" + }, + { + "description": "Complete Black Forest on Survival Hell On Earth difficulty", + "displayName": "Kein Bier Vor Vier", + "hidden": "0", + "icon": "images/Achievement_97.jpg", + "icongray": "images/Achievement_97_gray.jpg", + "name": "Achievement_97" + }, + { + "description": "Collect all the items on Black Forest", + "displayName": "Black Forest Babies", + "hidden": "0", + "icon": "images/Achievement_98.jpg", + "icongray": "images/Achievement_98_gray.jpg", + "name": "Achievement_98" + }, + { + "description": "Beat Farmhouse on Normal Difficulty", + "displayName": "Plow the Field", + "hidden": "0", + "icon": "images/Achievement_99.jpg", + "icongray": "images/Achievement_99_gray.jpg", + "name": "Achievement_99" + }, + { + "description": "Beat Farmhouse on Hard Difficulty", + "displayName": "Sow the Seed", + "hidden": "0", + "icon": "images/Achievement_100.jpg", + "icongray": "images/Achievement_100_gray.jpg", + "name": "Achievement_100" + }, + { + "description": "Beat Farmhouse on Suicidal Difficulty", + "displayName": "Water the Crops", + "hidden": "0", + "icon": "images/Achievement_101.jpg", + "icongray": "images/Achievement_101_gray.jpg", + "name": "Achievement_101" + }, + { + "description": "Beat Farmhouse on Hell on Earth Difficulty", + "displayName": "Reap what you Sow", + "hidden": "0", + "icon": "images/Achievement_102.jpg", + "icongray": "images/Achievement_102_gray.jpg", + "name": "Achievement_102" + }, + { + "description": "Collect all the items on Farmhouse", + "displayName": "Darkness Dolls", + "hidden": "0", + "icon": "images/Achievement_103.jpg", + "icongray": "images/Achievement_103_gray.jpg", + "name": "Achievement_103" + }, + { + "description": "Beat Prison on Normal", + "displayName": "Walked into the wrong room", + "hidden": "0", + "icon": "images/Achievement_104.jpg", + "icongray": "images/Achievement_104_gray.jpg", + "name": "Achievement_104" + }, + { + "description": "Beat Prison on Hard", + "displayName": "Mercenaries get paid", + "hidden": "0", + "icon": "images/Achievement_105.jpg", + "icongray": "images/Achievement_105_gray.jpg", + "name": "Achievement_105" + }, + { + "description": "Beat Prison on Suicidal", + "displayName": "Like what, kill him again?", + "hidden": "0", + "icon": "images/Achievement_106.jpg", + "icongray": "images/Achievement_106_gray.jpg", + "name": "Achievement_106" + }, + { + "description": "Beat Prison on Hell on Earth", + "displayName": "I was trained by the best. British Intel.", + "hidden": "0", + "icon": "images/Achievement_107.jpg", + "icongray": "images/Achievement_107_gray.jpg", + "name": "Achievement_107" + }, + { + "description": "Collect all the items on Prison", + "displayName": "Perilous Prison", + "hidden": "0", + "icon": "images/Achievement_108.jpg", + "icongray": "images/Achievement_108_gray.jpg", + "name": "Achievement_108" + }, + { + "description": "Complete the Training Floor", + "displayName": "School's Out Forever", + "hidden": "0", + "icon": "images/Achievement_109.jpg", + "icongray": "images/Achievement_109_gray.jpg", + "name": "Achievement_109" + }, + { + "description": "Reach Level 5 Sharpshooter", + "displayName": "Reach Level 5 Sharpshooter", + "hidden": "0", + "icon": "images/Achievement_110.jpg", + "icongray": "images/Achievement_110_gray.jpg", + "name": "Achievement_110" + }, + { + "description": "Reach Level 10 Sharpshooter", + "displayName": "Reach Level 10 Sharpshooter", + "hidden": "0", + "icon": "images/Achievement_111.jpg", + "icongray": "images/Achievement_111_gray.jpg", + "name": "Achievement_111" + }, + { + "description": "Reach Level 15 Sharpshooter", + "displayName": "Reach Level 15 Sharpshooter", + "hidden": "0", + "icon": "images/Achievement_112.jpg", + "icongray": "images/Achievement_112_gray.jpg", + "name": "Achievement_112" + }, + { + "description": "Reach Level 20 Sharpshooter", + "displayName": "Reach Level 20 Sharpshooter", + "hidden": "0", + "icon": "images/Achievement_113.jpg", + "icongray": "images/Achievement_113_gray.jpg", + "name": "Achievement_113" + }, + { + "description": "Reach Level 25 Sharpshooter", + "displayName": "Reach Level 25 Sharpshooter", + "hidden": "0", + "icon": "images/Achievement_114.jpg", + "icongray": "images/Achievement_114_gray.jpg", + "name": "Achievement_114" + }, + { + "description": "Beat Any One Map as Sharpshooter on Normal Difficulty", + "displayName": "Normal Sharpshooter", + "hidden": "0", + "icon": "images/Achievement_115.jpg", + "icongray": "images/Achievement_115_gray.jpg", + "name": "Achievement_115" + }, + { + "description": "Beat Any One Map as Sharpshooter on Hard Difficulty", + "displayName": "Hard Sharpshooter", + "hidden": "0", + "icon": "images/Achievement_116.jpg", + "icongray": "images/Achievement_116_gray.jpg", + "name": "Achievement_116" + }, + { + "description": "Complete any map as a Sharpshooter on Survival Suicidal difficulty", + "displayName": "Suicidal Sharpshooter", + "hidden": "0", + "icon": "images/Achievement_117.jpg", + "icongray": "images/Achievement_117_gray.jpg", + "name": "Achievement_117" + }, + { + "description": "Complete any map as a Sharpshooter on Survival Hell On Earth difficulty", + "displayName": "Hellish Sharpshooter", + "hidden": "0", + "icon": "images/Achievement_118.jpg", + "icongray": "images/Achievement_118_gray.jpg", + "name": "Achievement_118" + }, + { + "description": "Complete Containment Station on Survival Normal difficulty", + "displayName": "Never Got the Hang of Thursdays", + "hidden": "0", + "icon": "images/Achievement_119.jpg", + "icongray": "images/Achievement_119_gray.jpg", + "name": "Achievement_119" + }, + { + "description": "Complete Containment Station on Survival Hard difficulty", + "displayName": "Don't Panic", + "hidden": "0", + "icon": "images/Achievement_120.jpg", + "icongray": "images/Achievement_120_gray.jpg", + "name": "Achievement_120" + }, + { + "description": "Complete Containment Station on Survival Suicidal difficulty", + "displayName": "Give Up and Go Mad Now", + "hidden": "0", + "icon": "images/Achievement_121.jpg", + "icongray": "images/Achievement_121_gray.jpg", + "name": "Achievement_121" + }, + { + "description": "Complete Containment Station on Survival Hell On Earth difficulty", + "displayName": "So Long and Thanks for All the Zeds", + "hidden": "0", + "icon": "images/Achievement_122.jpg", + "icongray": "images/Achievement_122_gray.jpg", + "name": "Achievement_122" + }, + { + "description": "Collect All the Items on Containment Station", + "displayName": "Can't Be Contained", + "hidden": "0", + "icon": "images/Achievement_123.jpg", + "icongray": "images/Achievement_123_gray.jpg", + "name": "Achievement_123" + }, + { + "description": "Complete Hostile Grounds on Survival Normal difficulty", + "displayName": "Mind the Gap", + "hidden": "0", + "icon": "images/Achievement_124.jpg", + "icongray": "images/Achievement_124_gray.jpg", + "name": "Achievement_124" + }, + { + "description": "Complete Hostile Grounds on Survival Hard difficulty", + "displayName": "Can't Make an Omelette Without Killing a Few People", + "hidden": "0", + "icon": "images/Achievement_125.jpg", + "icongray": "images/Achievement_125_gray.jpg", + "name": "Achievement_125" + }, + { + "description": "Complete Hostile Grounds on Survival Suicidal difficulty", + "displayName": "There Is No I in Team, But There Is an I in Pie", + "hidden": "0", + "icon": "images/Achievement_126.jpg", + "icongray": "images/Achievement_126_gray.jpg", + "name": "Achievement_126" + }, + { + "description": "Complete Hostile Grounds on Survival Hell On Earth difficulty", + "displayName": "Who Died and Made You #*%$&@ King of the Zombies?", + "hidden": "0", + "icon": "images/Achievement_127.jpg", + "icongray": "images/Achievement_127_gray.jpg", + "name": "Achievement_127" + }, + { + "description": "Collect All the Items on Hostile Grounds", + "displayName": "You've Got Red on You", + "hidden": "0", + "icon": "images/Achievement_128.jpg", + "icongray": "images/Achievement_128_gray.jpg", + "name": "Achievement_128" + }, + { + "description": "Kill a Siren Before She Screams", + "displayName": "Dead Silence", + "hidden": "0", + "icon": "images/Achievement_129.jpg", + "icongray": "images/Achievement_129_gray.jpg", + "name": "Achievement_129" + }, + { + "description": "Kill the Patriarch Before He Has a Chance to Heal", + "displayName": "Quick on the Trigger", + "hidden": "0", + "icon": "images/Achievement_130.jpg", + "icongray": "images/Achievement_130_gray.jpg", + "name": "Achievement_130" + }, + { + "description": "Kill Your First Fleshpound", + "displayName": "It's Only a Flesh Wound", + "hidden": "0", + "icon": "images/Achievement_131.jpg", + "icongray": "images/Achievement_131_gray.jpg", + "name": "Achievement_131" + }, + { + "description": "Kill Your First Scrake", + "displayName": "Hack and Slash", + "hidden": "0", + "icon": "images/Achievement_132.jpg", + "icongray": "images/Achievement_132_gray.jpg", + "name": "Achievement_132" + }, + { + "description": "Kill Dr. Hans Volter For the First Time", + "displayName": "Die Volter", + "hidden": "0", + "icon": "images/Achievement_133.jpg", + "icongray": "images/Achievement_133_gray.jpg", + "name": "Achievement_133" + }, + { + "description": "Win Any Match on Hard Difficulty", + "displayName": "Win Hard", + "hidden": "0", + "icon": "images/Achievement_134.jpg", + "icongray": "images/Achievement_134_gray.jpg", + "name": "Achievement_134" + }, + { + "description": "Win Any Match on Suicidal Difficulty", + "displayName": "Win Suicidal", + "hidden": "0", + "icon": "images/Achievement_135.jpg", + "icongray": "images/Achievement_135_gray.jpg", + "name": "Achievement_135" + }, + { + "description": "Win Any Match on Hell on Earth Difficulty", + "displayName": "Win Hell on Earth", + "hidden": "0", + "icon": "images/Achievement_136.jpg", + "icongray": "images/Achievement_136_gray.jpg", + "name": "Achievement_136" + }, + { + "description": "Reach Level 5 on Any Perk", + "displayName": "Mr. Perky 5", + "hidden": "0", + "icon": "images/Achievement_137.jpg", + "icongray": "images/Achievement_137_gray.jpg", + "name": "Achievement_137" + }, + { + "description": "Reach Level 10 on Any Perk", + "displayName": "Mr. Perky 10", + "hidden": "0", + "icon": "images/Achievement_138.jpg", + "icongray": "images/Achievement_138_gray.jpg", + "name": "Achievement_138" + }, + { + "description": "Reach Level 15 on Any Perk", + "displayName": "Mr. Perky 15", + "hidden": "0", + "icon": "images/Achievement_139.jpg", + "icongray": "images/Achievement_139_gray.jpg", + "name": "Achievement_139" + }, + { + "description": "Reach Level 20 on Any Perk", + "displayName": "Mr. Perky 20", + "hidden": "0", + "icon": "images/Achievement_140.jpg", + "icongray": "images/Achievement_140_gray.jpg", + "name": "Achievement_140" + }, + { + "description": "Reach Level 25 on Any Perk", + "displayName": "Mr. Perky 25", + "hidden": "0", + "icon": "images/Achievement_141.jpg", + "icongray": "images/Achievement_141_gray.jpg", + "name": "Achievement_141" + }, + { + "description": "Win 1 Multiplayer Match", + "displayName": "Win 1", + "hidden": "0", + "icon": "images/Achievement_142.jpg", + "icongray": "images/Achievement_142_gray.jpg", + "name": "Achievement_142" + }, + { + "description": "Win 10 Multiplayer Matches", + "displayName": "Win 10", + "hidden": "0", + "icon": "images/Achievement_143.jpg", + "icongray": "images/Achievement_143_gray.jpg", + "name": "Achievement_143" + }, + { + "description": "Win 25 Multiplayer Matches", + "displayName": "Win 25", + "hidden": "0", + "icon": "images/Achievement_144.jpg", + "icongray": "images/Achievement_144_gray.jpg", + "name": "Achievement_144" + }, + { + "description": "Win a VS Survival Round Playing as the Zeds", + "displayName": "VS Zed Win", + "hidden": "0", + "icon": "images/Achievement_145.jpg", + "icongray": "images/Achievement_145_gray.jpg", + "name": "Achievement_145" + }, + { + "description": "Win a VS Survival Round Playing as the Humans", + "displayName": "VS Human Win", + "hidden": "0", + "icon": "images/Achievement_146.jpg", + "icongray": "images/Achievement_146_gray.jpg", + "name": "Achievement_146" + }, + { + "description": "Weld a Door to 100%", + "displayName": "Hold Out", + "hidden": "0", + "icon": "images/Achievement_147.jpg", + "icongray": "images/Achievement_147_gray.jpg", + "name": "Achievement_147" + }, + { + "description": "Heal a Teammate With the Syringe", + "displayName": "I Got Your Back", + "hidden": "0", + "icon": "images/Achievement_148.jpg", + "icongray": "images/Achievement_148_gray.jpg", + "name": "Achievement_148" + }, + { + "description": "Give 1000 Dosh to Another Player During a Multiplayer Match", + "displayName": "Benefactor", + "hidden": "0", + "icon": "images/Achievement_149.jpg", + "icongray": "images/Achievement_149_gray.jpg", + "name": "Achievement_149" + }, + { + "description": "Beat Infernal Realms on Normal", + "displayName": "Hell is Other People", + "hidden": "0", + "icon": "images/Achievement_150.jpg", + "icongray": "images/Achievement_150_gray.jpg", + "name": "Achievement_150" + }, + { + "description": "Complete Infernal Realms on Survival Hard difficulty", + "displayName": "If You Are Going Through Hell, Keep Going", + "hidden": "0", + "icon": "images/Achievement_151.jpg", + "icongray": "images/Achievement_151_gray.jpg", + "name": "Achievement_151" + }, + { + "description": "Complete Infernal Realms on Survival Suicidal difficulty", + "displayName": "Hell is Just a Frame of Mind", + "hidden": "0", + "icon": "images/Achievement_152.jpg", + "icongray": "images/Achievement_152_gray.jpg", + "name": "Achievement_152" + }, + { + "description": "Complete Infernal Realms on Survival Hell On Earth difficulty", + "displayName": "All Hope abandon, Ye Who Enter Here", + "hidden": "0", + "icon": "images/Achievement_153.jpg", + "icongray": "images/Achievement_153_gray.jpg", + "name": "Achievement_153" + }, + { + "description": "Collect all the Items in Infernal Realms", + "displayName": "Infernal Relics", + "hidden": "0", + "icon": "images/Achievement_154.jpg", + "icongray": "images/Achievement_154_gray.jpg", + "name": "Achievement_154" + }, + { + "description": "Reach Level 5 SWAT", + "displayName": "Reach Level 5 SWAT", + "hidden": "0", + "icon": "images/Achievement_155.jpg", + "icongray": "images/Achievement_155_gray.jpg", + "name": "Achievement_155" + }, + { + "description": "Reach Level 10 SWAT", + "displayName": "Reach Level 10 SWAT", + "hidden": "0", + "icon": "images/Achievement_156.jpg", + "icongray": "images/Achievement_156_gray.jpg", + "name": "Achievement_156" + }, + { + "description": "Reach Level 15 SWAT", + "displayName": "Reach Level 15 SWAT", + "hidden": "0", + "icon": "images/Achievement_157.jpg", + "icongray": "images/Achievement_157_gray.jpg", + "name": "Achievement_157" + }, + { + "description": "Reach Level 20 SWAT", + "displayName": "Reach Level 20 SWAT", + "hidden": "0", + "icon": "images/Achievement_158.jpg", + "icongray": "images/Achievement_158_gray.jpg", + "name": "Achievement_158" + }, + { + "description": "Reach Level 25 SWAT", + "displayName": "Reach Level 25 SWAT", + "hidden": "0", + "icon": "images/Achievement_159.jpg", + "icongray": "images/Achievement_159_gray.jpg", + "name": "Achievement_159" + }, + { + "description": "Complete any map as a SWAT on Survival Normal difficulty", + "displayName": "Normal SWAT", + "hidden": "0", + "icon": "images/Achievement_160.jpg", + "icongray": "images/Achievement_160_gray.jpg", + "name": "Achievement_160" + }, + { + "description": "Beat Any One Map as SWAT on Hard Difficulty", + "displayName": "Hard SWAT", + "hidden": "0", + "icon": "images/Achievement_161.jpg", + "icongray": "images/Achievement_161_gray.jpg", + "name": "Achievement_161" + }, + { + "description": "Beat Any One Map as SWAT on Suicidal Difficulty", + "displayName": "Suicidal SWAT", + "hidden": "0", + "icon": "images/Achievement_162.jpg", + "icongray": "images/Achievement_162_gray.jpg", + "name": "Achievement_162" + }, + { + "description": "Beat Any One Map as SWAT on Hell On Earth Difficulty", + "displayName": "Hellish SWAT", + "hidden": "0", + "icon": "images/Achievement_163.jpg", + "icongray": "images/Achievement_163_gray.jpg", + "name": "Achievement_163" + }, + { + "description": "Reach Level 5 Survivalist", + "displayName": "Reach Level 5 Survivalist", + "hidden": "0", + "icon": "images/Achievement_164.jpg", + "icongray": "images/Achievement_164_gray.jpg", + "name": "Achievement_164" + }, + { + "description": "Reach Level 10 Survivalist", + "displayName": "Reach Level 10 Survivalist", + "hidden": "0", + "icon": "images/Achievement_165.jpg", + "icongray": "images/Achievement_165_gray.jpg", + "name": "Achievement_165" + }, + { + "description": "Reach Level 15 Survivalist", + "displayName": "Reach Level 15 Survivalist", + "hidden": "0", + "icon": "images/Achievement_166.jpg", + "icongray": "images/Achievement_166_gray.jpg", + "name": "Achievement_166" + }, + { + "description": "Reach Level 20 Survivalist", + "displayName": "Reach Level 20 Survivalist", + "hidden": "0", + "icon": "images/Achievement_167.jpg", + "icongray": "images/Achievement_167_gray.jpg", + "name": "Achievement_167" + }, + { + "description": "Reach Level 25 Survivalist", + "displayName": "Reach Level 25 Survivalist", + "hidden": "0", + "icon": "images/Achievement_168.jpg", + "icongray": "images/Achievement_168_gray.jpg", + "name": "Achievement_168" + }, + { + "description": "Beat Any One Map as Survivalist on Normal Difficulty", + "displayName": "Normal Survivalist", + "hidden": "0", + "icon": "images/Achievement_169.jpg", + "icongray": "images/Achievement_169_gray.jpg", + "name": "Achievement_169" + }, + { + "description": "Complete any map as a Survivalist on Survival Hard difficulty", + "displayName": "Hard Survivalist ", + "hidden": "0", + "icon": "images/Achievement_170.jpg", + "icongray": "images/Achievement_170_gray.jpg", + "name": "Achievement_170" + }, + { + "description": "Complete any map as a Survivalist on Survival Suicidal difficulty", + "displayName": "Suicidal Survivalist", + "hidden": "0", + "icon": "images/Achievement_171.jpg", + "icongray": "images/Achievement_171_gray.jpg", + "name": "Achievement_171" + }, + { + "description": "Complete any map as a Survivalist on Survival Hell On Earth difficulty", + "displayName": "Hellish Survivalist", + "hidden": "0", + "icon": "images/Achievement_172.jpg", + "icongray": "images/Achievement_172_gray.jpg", + "name": "Achievement_172" + }, + { + "description": "Complete Zed Landing on Survival Normal difficulty", + "displayName": "Surfs Up", + "hidden": "0", + "icon": "images/Achievement_173.jpg", + "icongray": "images/Achievement_173_gray.jpg", + "name": "Achievement_173" + }, + { + "description": "Complete Zed Landing on Survival Hard difficulty", + "displayName": "Gnarly", + "hidden": "0", + "icon": "images/Achievement_174.jpg", + "icongray": "images/Achievement_174_gray.jpg", + "name": "Achievement_174" + }, + { + "description": "Complete Zed Landing on Survival Suicidal difficulty", + "displayName": "Close-out", + "hidden": "0", + "icon": "images/Achievement_175.jpg", + "icongray": "images/Achievement_175_gray.jpg", + "name": "Achievement_175" + }, + { + "description": "Complete Zed Landing on Survival Hell On Earth difficulty", + "displayName": "Blown Out", + "hidden": "0", + "icon": "images/Achievement_176.jpg", + "icongray": "images/Achievement_176_gray.jpg", + "name": "Achievement_176" + }, + { + "description": "Collect all the volley balls on Zed Landing", + "displayName": "ALAN!!!!!!", + "hidden": "0", + "icon": "images/Achievement_177.jpg", + "icongray": "images/Achievement_177_gray.jpg", + "name": "Achievement_177" + }, + { + "description": "Complete The Descent on Survival Normal difficulty", + "displayName": "How Bout Some Gas?", + "hidden": "0", + "icon": "images/Achievement_178.jpg", + "icongray": "images/Achievement_178_gray.jpg", + "name": "Achievement_178" + }, + { + "description": "Complete The Descent on Survival Hard difficulty", + "displayName": "But what if we added Gas?", + "hidden": "0", + "icon": "images/Achievement_179.jpg", + "icongray": "images/Achievement_179_gray.jpg", + "name": "Achievement_179" + }, + { + "description": "Complete The Descent on Survival Suicidal difficulty", + "displayName": "Let's Try Some Gas", + "hidden": "0", + "icon": "images/Achievement_180.jpg", + "icongray": "images/Achievement_180_gray.jpg", + "name": "Achievement_180" + }, + { + "description": "Complete The Descent on Survival Hell On Earth difficulty", + "displayName": "That's enough gas....", + "hidden": "0", + "icon": "images/Achievement_181.jpg", + "icongray": "images/Achievement_181_gray.jpg", + "name": "Achievement_181" + }, + { + "description": "Collect all the items on The Descent", + "displayName": "Hans Off the Merchandise", + "hidden": "0", + "icon": "images/Achievement_182.jpg", + "icongray": "images/Achievement_182_gray.jpg", + "name": "Achievement_182" + }, + { + "description": "Complete Nuked on Survival Normal difficulty", + "displayName": "The War Room", + "hidden": "0", + "icon": "images/Achievement_183.jpg", + "icongray": "images/Achievement_183_gray.jpg", + "name": "Achievement_183" + }, + { + "description": "Complete Nuked on Survival Hard difficulty", + "displayName": "The Mineshaft Gap", + "hidden": "0", + "icon": "images/Achievement_184.jpg", + "icongray": "images/Achievement_184_gray.jpg", + "name": "Achievement_184" + }, + { + "description": "Complete Nuked on Survival Suicidal difficulty", + "displayName": "Peace is Our Profession", + "hidden": "0", + "icon": "images/Achievement_185.jpg", + "icongray": "images/Achievement_185_gray.jpg", + "name": "Achievement_185" + }, + { + "description": "Complete Nuked on Survival Hell On Earth difficulty", + "displayName": "How I Learned to Love the Bomb", + "hidden": "0", + "icon": "images/Achievement_186.jpg", + "icongray": "images/Achievement_186_gray.jpg", + "name": "Achievement_186" + }, + { + "description": "Collect all the items on Nuked", + "displayName": "Davy Crockett", + "hidden": "0", + "icon": "images/Achievement_187.jpg", + "icongray": "images/Achievement_187_gray.jpg", + "name": "Achievement_187" + }, + { + "description": "Complete Tragic Kingdom on Survival Normal difficulty", + "displayName": "It's a Bloody World After All", + "hidden": "0", + "icon": "images/Achievement_188.jpg", + "icongray": "images/Achievement_188_gray.jpg", + "name": "Achievement_188" + }, + { + "description": "Complete Tragic Kingdom on Survival Hard difficulty", + "displayName": "The Goriest Place on Earth", + "hidden": "0", + "icon": "images/Achievement_189.jpg", + "icongray": "images/Achievement_189_gray.jpg", + "name": "Achievement_189" + }, + { + "description": "Complete Tragic Kingdom on Survival Suicidal difficulty", + "displayName": "The House the Zed Built", + "hidden": "0", + "icon": "images/Achievement_190.jpg", + "icongray": "images/Achievement_190_gray.jpg", + "name": "Achievement_190" + }, + { + "description": "Complete Tragic Kingdom on Survival Hell On Earth difficulty", + "displayName": "Where Nightmares Come True", + "hidden": "0", + "icon": "images/Achievement_191.jpg", + "icongray": "images/Achievement_191_gray.jpg", + "name": "Achievement_191" + }, + { + "description": "Collect all the items on The Tragic Kingdom", + "displayName": "The Wonderful World of Merchandising", + "hidden": "0", + "icon": "images/Achievement_192.jpg", + "icongray": "images/Achievement_192_gray.jpg", + "name": "Achievement_192" + }, + { + "description": "Beat Nightmare on Normal", + "displayName": "I Got a Rock", + "hidden": "0", + "icon": "images/Achievement_193.jpg", + "icongray": "images/Achievement_193_gray.jpg", + "name": "Achievement_193" + }, + { + "description": "Beat Nightmare on Hard", + "displayName": "Yuck, Candy Corn", + "hidden": "0", + "icon": "images/Achievement_194.jpg", + "icongray": "images/Achievement_194_gray.jpg", + "name": "Achievement_194" + }, + { + "description": "Beat Nightmare on Suicidal", + "displayName": "Fun Size? What is Fun Size?", + "hidden": "0", + "icon": "images/Achievement_195.jpg", + "icongray": "images/Achievement_195_gray.jpg", + "name": "Achievement_195" + }, + { + "description": "Beat Nightmare on Hell on Earth", + "displayName": "Victory! Full Size Candy Bar!", + "hidden": "0", + "icon": "images/Achievement_196.jpg", + "icongray": "images/Achievement_196_gray.jpg", + "name": "Achievement_196" + }, + { + "description": "Collect all the items (skulls) on Nightmare", + "displayName": "Letting the Demons Out", + "hidden": "0", + "icon": "images/Achievement_197.jpg", + "icongray": "images/Achievement_197_gray.jpg", + "name": "Achievement_197" + }, + { + "description": "Beat Krampus Lair on Normal", + "displayName": "Stocking Full of Coal", + "hidden": "0", + "icon": "images/Achievement_198.jpg", + "icongray": "images/Achievement_198_gray.jpg", + "name": "Achievement_198" + }, + { + "description": "Beat Krampus Lair on Hard", + "displayName": "Bundle of Switches", + "hidden": "0", + "icon": "images/Achievement_199.jpg", + "icongray": "images/Achievement_199_gray.jpg", + "name": "Achievement_199" + }, + { + "description": "Beat Krampus Lair on Suicidal", + "displayName": "A Whupping", + "hidden": "0", + "icon": "images/Achievement_200.jpg", + "icongray": "images/Achievement_200_gray.jpg", + "name": "Achievement_200" + }, + { + "description": "Complete Krampus Lair on Survival Hell On Earth difficulty", + "displayName": "Carried off to the Underworld", + "hidden": "0", + "icon": "images/Achievement_201.jpg", + "icongray": "images/Achievement_201_gray.jpg", + "name": "Achievement_201" + }, + { + "description": "Collect all the items (Snowglobes) on Krampus Lair", + "displayName": "A World Under Glass", + "hidden": "0", + "icon": "images/Achievement_202.jpg", + "icongray": "images/Achievement_202_gray.jpg", + "name": "Achievement_202" + }, + { + "description": "Complete DieSector wave 25 on Endless Normal difficulty", + "displayName": "Training Simulation", + "hidden": "0", + "icon": "images/Achievement_203.jpg", + "icongray": "images/Achievement_203_gray.jpg", + "name": "Achievement_203" + }, + { + "description": "Complete DieSector wave 25 on Endless Hard difficulty", + "displayName": "Test Trials", + "hidden": "0", + "icon": "images/Achievement_204.jpg", + "icongray": "images/Achievement_204_gray.jpg", + "name": "Achievement_204" + }, + { + "description": "Complete DieSector wave 25 on Endless Suicidal difficulty", + "displayName": "Code Dead", + "hidden": "0", + "icon": "images/Achievement_205.jpg", + "icongray": "images/Achievement_205_gray.jpg", + "name": "Achievement_205" + }, + { + "description": "Complete DieSector wave 25 on Endless Hell On Earth difficulty", + "displayName": "Fatal Exception", + "hidden": "0", + "icon": "images/Achievement_206.jpg", + "icongray": "images/Achievement_206_gray.jpg", + "name": "Achievement_206" + }, + { + "description": "Collect all the items (D.A.R. Bobbleheads) on DieSector", + "displayName": "It's aD.A.R.able", + "hidden": "0", + "icon": "images/Achievement_207.jpg", + "icongray": "images/Achievement_207_gray.jpg", + "name": "Achievement_207" + }, + { + "description": "Complete Power Core on Survival Normal difficulty", + "displayName": "A Spark to Light the Way", + "hidden": "0", + "icon": "images/Achievement_208.jpg", + "icongray": "images/Achievement_208_gray.jpg", + "name": "Achievement_208" + }, + { + "description": "Complete Power Core on Survival Hard difficulty", + "displayName": "Bolting through the Core", + "hidden": "0", + "icon": "images/Achievement_209.jpg", + "icongray": "images/Achievement_209_gray.jpg", + "name": "Achievement_209" + }, + { + "description": "Complete Power Core on Survival Suicidal difficulty", + "displayName": "High Voltage", + "hidden": "0", + "icon": "images/Achievement_210.jpg", + "icongray": "images/Achievement_210_gray.jpg", + "name": "Achievement_210" + }, + { + "description": "Complete Power Core on Survival Hell On Earth difficulty", + "displayName": "I Have the Power!", + "hidden": "0", + "icon": "images/Achievement_211.jpg", + "icongray": "images/Achievement_211_gray.jpg", + "name": "Achievement_211" + }, + { + "description": "Collect all the items (Batteries) on Power Core", + "displayName": "Surge Breaker", + "hidden": "0", + "icon": "images/Achievement_212.jpg", + "icongray": "images/Achievement_212_gray.jpg", + "name": "Achievement_212" + }, + { + "description": "Complete Airship on Survival Normal difficulty", + "displayName": "A Little Turbulence", + "hidden": "0", + "icon": "images/Achievement_213.jpg", + "icongray": "images/Achievement_213_gray.jpg", + "name": "Achievement_213" + }, + { + "description": "Complete Airship on Survival Hard difficulty", + "displayName": "Flying Unfriendly Skies", + "hidden": "0", + "icon": "images/Achievement_214.jpg", + "icongray": "images/Achievement_214_gray.jpg", + "name": "Achievement_214" + }, + { + "description": "Complete Airship on Survival Suicidal difficulty", + "displayName": "Soar, Gore, and More", + "hidden": "0", + "icon": "images/Achievement_215.jpg", + "icongray": "images/Achievement_215_gray.jpg", + "name": "Achievement_215" + }, + { + "description": "Complete Airship on Survival Hell On Earth difficulty", + "displayName": "Mile High Dead Club", + "hidden": "0", + "icon": "images/Achievement_216.jpg", + "icongray": "images/Achievement_216_gray.jpg", + "name": "Achievement_216" + }, + { + "description": "Collect all the items (Steam Cells) on Airship", + "displayName": "Powered by Steam", + "hidden": "0", + "icon": "images/Achievement_217.jpg", + "icongray": "images/Achievement_217_gray.jpg", + "name": "Achievement_217" + }, + { + "description": "Complete Lockdown on Survival Normal difficulty", + "displayName": "Station Stabilization", + "hidden": "0", + "icon": "images/Achievement_218.jpg", + "icongray": "images/Achievement_218_gray.jpg", + "name": "Achievement_218" + }, + { + "description": "Complete Lockdown on Survival Hard difficulty", + "displayName": "Fun Near the Sun", + "hidden": "0", + "icon": "images/Achievement_219.jpg", + "icongray": "images/Achievement_219_gray.jpg", + "name": "Achievement_219" + }, + { + "description": "Complete Lockdown on Survival Suicidal difficulty", + "displayName": "Space Race", + "hidden": "0", + "icon": "images/Achievement_220.jpg", + "icongray": "images/Achievement_220_gray.jpg", + "name": "Achievement_220" + }, + { + "description": "Complete Lockdown on Survival Hell On Earth difficulty", + "displayName": "Houston, We Don't Have a Problem", + "hidden": "0", + "icon": "images/Achievement_221.jpg", + "icongray": "images/Achievement_221_gray.jpg", + "name": "Achievement_221" + }, + { + "description": "Collect all the items (Batteries) on Lockdown", + "displayName": "Shocking Discovery!", + "hidden": "0", + "icon": "images/Achievement_222.jpg", + "icongray": "images/Achievement_222_gray.jpg", + "name": "Achievement_222" + }, + { + "description": "Complete Monster Ball on Survival Normal difficulty", + "displayName": "Castle Crashers", + "hidden": "0", + "icon": "images/Achievement_223.jpg", + "icongray": "images/Achievement_223_gray.jpg", + "name": "Achievement_223" + }, + { + "description": "Complete Monster Ball on Survival Hard difficulty", + "displayName": "Party Hard!", + "hidden": "0", + "icon": "images/Achievement_224.jpg", + "icongray": "images/Achievement_224_gray.jpg", + "name": "Achievement_224" + }, + { + "description": "Complete Monster Ball on Survival Suicidal difficulty", + "displayName": "Dance on the Gore Floor", + "hidden": "0", + "icon": "images/Achievement_225.jpg", + "icongray": "images/Achievement_225_gray.jpg", + "name": "Achievement_225" + }, + { + "description": "Complete Monster Ball on Survival Hell on Earth difficulty", + "displayName": "Rest In Pieces", + "hidden": "0", + "icon": "images/Achievement_226.jpg", + "icongray": "images/Achievement_226_gray.jpg", + "name": "Achievement_226" + }, + { + "description": "Destroy 10 Glowing Skulls in Monster Ball", + "displayName": "Spooky Scary Skeletons", + "hidden": "0", + "icon": "images/Achievement_227.jpg", + "icongray": "images/Achievement_227_gray.jpg", + "name": "Achievement_227" + }, + { + "description": "Unlock the Alchemist Room in Monster Ball", + "displayName": "Death's Door", + "hidden": "0", + "icon": "images/Achievement_228.jpg", + "icongray": "images/Achievement_228_gray.jpg", + "name": "Achievement_228" + }, + { + "description": "Complete Santa's Workshop on Survival Normal Difficulty", + "displayName": "Cookies and Milk", + "hidden": "0", + "icon": "images/Achievement_229.jpg", + "icongray": "images/Achievement_229_gray.jpg", + "name": "Achievement_229" + }, + { + "description": "Complete Santa's Workshop on Survival Hard Difficulty", + "displayName": "Slaying with Santa", + "hidden": "0", + "icon": "images/Achievement_230.jpg", + "icongray": "images/Achievement_230_gray.jpg", + "name": "Achievement_230" + }, + { + "description": "Complete Santa's Workshop on Survival Suicidal Difficulty", + "displayName": "A Deadly Carol", + "hidden": "0", + "icon": "images/Achievement_231.jpg", + "icongray": "images/Achievement_231_gray.jpg", + "name": "Achievement_231" + }, + { + "description": "Complete Santa's Workshop on Survival Hell On Earth Difficulty", + "displayName": "You're On The Badass List", + "hidden": "0", + "icon": "images/Achievement_232.jpg", + "icongray": "images/Achievement_232_gray.jpg", + "name": "Achievement_232" + }, + { + "description": "Destroy 10 Snow Globes in Santa's Workshop", + "displayName": "Yule Shoot Your Eye Out", + "hidden": "0", + "icon": "images/Achievement_233.jpg", + "icongray": "images/Achievement_233_gray.jpg", + "name": "Achievement_233" + }, + { + "description": "Complete Shopping Spree on Survival Normal Difficulty", + "displayName": "Cleanup On Aisle 3", + "hidden": "0", + "icon": "images/Achievement_234.jpg", + "icongray": "images/Achievement_234_gray.jpg", + "name": "Achievement_234" + }, + { + "description": "Complete Shopping Spree on Survival Hard Difficulty", + "displayName": "Shoot One Get Two Free", + "hidden": "0", + "icon": "images/Achievement_235.jpg", + "icongray": "images/Achievement_235_gray.jpg", + "name": "Achievement_235" + }, + { + "description": "Complete Shopping Spree on Survival Suicidal Difficulty", + "displayName": "Savings to Die For", + "hidden": "0", + "icon": "images/Achievement_236.jpg", + "icongray": "images/Achievement_236_gray.jpg", + "name": "Achievement_236" + }, + { + "description": "Complete Shopping Spree on Survival Hell On Earth Difficulty", + "displayName": "Red Friday", + "hidden": "0", + "icon": "images/Achievement_237.jpg", + "icongray": "images/Achievement_237_gray.jpg", + "name": "Achievement_237" + }, + { + "description": "Destroy 10 Dosh Necklaces in Shopping Spree", + "displayName": "A Special Deal", + "hidden": "0", + "icon": "images/Achievement_238.jpg", + "icongray": "images/Achievement_238_gray.jpg", + "name": "Achievement_238" + }, + { + "description": "Complete Spillway on Survival Normal Difficulty", + "displayName": "It's All Downstream From Here", + "hidden": "0", + "icon": "images/Achievement_239.jpg", + "icongray": "images/Achievement_239_gray.jpg", + "name": "Achievement_239" + }, + { + "description": "Complete Spillway on Survival Hard Difficulty", + "displayName": "Zeds Be Dam", + "hidden": "0", + "icon": "images/Achievement_240.jpg", + "icongray": "images/Achievement_240_gray.jpg", + "name": "Achievement_240" + }, + { + "description": "Complete Spillway on Survival Suicidal Difficulty", + "displayName": "Overflow Controlled", + "hidden": "0", + "icon": "images/Achievement_241.jpg", + "icongray": "images/Achievement_241_gray.jpg", + "name": "Achievement_241" + }, + { + "description": "Complete Spillway on Survival Hell On Earth Difficulty", + "displayName": "Dam You're Good!", + "hidden": "0", + "icon": "images/Achievement_242.jpg", + "icongray": "images/Achievement_242_gray.jpg", + "name": "Achievement_242" + }, + { + "description": "Destroy 10 Dosh Necklaces in Spillway", + "displayName": "Money Down the Drain", + "hidden": "0", + "icon": "images/Achievement_243.jpg", + "icongray": "images/Achievement_243_gray.jpg", + "name": "Achievement_243" + }, + { + "description": "Complete Steam Fortress on Objective Normal Difficulty", + "displayName": "Burning Out the Fuse", + "hidden": "0", + "icon": "images/Achievement_244.jpg", + "icongray": "images/Achievement_244_gray.jpg", + "name": "Achievement_244" + }, + { + "description": "Complete Steam Fortress on Objective Hard Difficulty", + "displayName": "Touchdown Brings Me Down", + "hidden": "0", + "icon": "images/Achievement_245.jpg", + "icongray": "images/Achievement_245_gray.jpg", + "name": "Achievement_245" + }, + { + "description": "Complete Steam Fortress on Objective Suicidal Difficulty", + "displayName": "High as a Kite", + "hidden": "0", + "icon": "images/Achievement_246.jpg", + "icongray": "images/Achievement_246_gray.jpg", + "name": "Achievement_246" + }, + { + "description": "Complete Steam Fortress on Objective Hell On Earth Difficulty", + "displayName": "It's Cold as Hell", + "hidden": "0", + "icon": "images/Achievement_247.jpg", + "icongray": "images/Achievement_247_gray.jpg", + "name": "Achievement_247" + }, + { + "description": "Destroy 10 Steam Batteries on Steam Fortress", + "displayName": "All This Science I Don't Understand", + "hidden": "0", + "icon": "images/Achievement_248.jpg", + "icongray": "images/Achievement_248_gray.jpg", + "name": "Achievement_248" + }, + { + "description": "Complete Zed Landing on Objective Normal Difficulty", + "displayName": "Droning On", + "hidden": "0", + "icon": "images/Achievement_249.jpg", + "icongray": "images/Achievement_249_gray.jpg", + "name": "Achievement_249" + }, + { + "description": "Complete Zed Landing on Objective Hard Difficulty", + "displayName": "Data Deliverier ", + "hidden": "0", + "icon": "images/Achievement_250.jpg", + "icongray": "images/Achievement_250_gray.jpg", + "name": "Achievement_250" + }, + { + "description": "Complete Zed Landing on Objective Suicidal Difficulty", + "displayName": "Island Isolation", + "hidden": "0", + "icon": "images/Achievement_251.jpg", + "icongray": "images/Achievement_251_gray.jpg", + "name": "Achievement_251" + }, + { + "description": "Complete Zed Landing on Objective Hell On Earth Difficulty", + "displayName": "Someone Call a Chopper?", + "hidden": "0", + "icon": "images/Achievement_252.jpg", + "icongray": "images/Achievement_252_gray.jpg", + "name": "Achievement_252" + }, + { + "description": "Complete Outpost on Objective Normal Difficulty", + "displayName": "It's Snow Good. ", + "hidden": "0", + "icon": "images/Achievement_253.jpg", + "icongray": "images/Achievement_253_gray.jpg", + "name": "Achievement_253" + }, + { + "description": "Complete Outpost on Objective Hard Difficulty", + "displayName": "Giving the Cold Shoulder", + "hidden": "0", + "icon": "images/Achievement_254.jpg", + "icongray": "images/Achievement_254_gray.jpg", + "name": "Achievement_254" + }, + { + "description": "Complete Outpost on Objective Suicidal Difficulty", + "displayName": "Frozen Assets", + "hidden": "0", + "icon": "images/Achievement_255.jpg", + "icongray": "images/Achievement_255_gray.jpg", + "name": "Achievement_255" + }, + { + "description": "Complete Outpost on Objective Hell On Earth Difficulty", + "displayName": "The Snow Must Go On", + "hidden": "0", + "icon": "images/Achievement_256.jpg", + "icongray": "images/Achievement_256_gray.jpg", + "name": "Achievement_256" + }, + { + "description": "Complete Ashwood Asylum on Survival Hard or higher Difficulty", + "displayName": "Cackling Crazy", + "hidden": "0", + "icon": "images/Achievement_257.jpg", + "icongray": "images/Achievement_257_gray.jpg", + "name": "Achievement_257" + }, + { + "description": "Complete Ashwood Asylum on Survival Hell On Earth Difficulty", + "displayName": "Non Compos Mentis", + "hidden": "0", + "icon": "images/Achievement_258.jpg", + "icongray": "images/Achievement_258_gray.jpg", + "name": "Achievement_258" + }, + { + "description": "Destroy 10 red skulls on Ashwood Asylum", + "displayName": "Money Mania", + "hidden": "0", + "icon": "images/Achievement_259.jpg", + "icongray": "images/Achievement_259_gray.jpg", + "name": "Achievement_259" + }, + { + "description": "Complete Nuked on Objective Hard or higher Difficulty", + "displayName": "An Explosive Ending", + "hidden": "0", + "icon": "images/Achievement_260.jpg", + "icongray": "images/Achievement_260_gray.jpg", + "name": "Achievement_260" + }, + { + "description": "Complete Nuked on Objective Hell On Earth Difficulty", + "displayName": "The World On Fire", + "hidden": "0", + "icon": "images/Achievement_261.jpg", + "icongray": "images/Achievement_261_gray.jpg", + "name": "Achievement_261" + } +] \ No newline at end of file diff --git a/files_example/steam_settings.EXAMPLE/default_items.EXAMPLE.json b/files_example/steam_settings.EXAMPLE/default_items.EXAMPLE.json new file mode 100644 index 00000000..34db3278 --- /dev/null +++ b/files_example/steam_settings.EXAMPLE/default_items.EXAMPLE.json @@ -0,0 +1,4 @@ +{ + "2001": 1, + "2002": 1 +} \ No newline at end of file diff --git a/files_example/steam_settings.EXAMPLE/disable_networking.EXAMPLE.txt b/files_example/steam_settings.EXAMPLE/disable_networking.EXAMPLE.txt new file mode 100644 index 00000000..4cc106f6 --- /dev/null +++ b/files_example/steam_settings.EXAMPLE/disable_networking.EXAMPLE.txt @@ -0,0 +1 @@ +Rename this to: disable_networking.txt to disable all networking functionality. \ No newline at end of file diff --git a/steamclient_loader/ColdClientLoader.cpp b/steamclient_loader/ColdClientLoader.cpp new file mode 100644 index 00000000..9ddf8d79 --- /dev/null +++ b/steamclient_loader/ColdClientLoader.cpp @@ -0,0 +1,212 @@ +// My own modified version of ColdClientLoader originally written by Rat431 + +#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers +// Windows Header Files +#include +// C RunTime Header Files +#include +#include +#include +#include + +bool IsNotRelativePathOrRemoveFileName(CHAR* output, bool Remove) +{ + int LG = lstrlenA(output); + for (int i = LG; i > 0; i--) { + if (output[i] == '\\') { + if(Remove) + RtlFillMemory(&output[i], LG - i, NULL); + return true; + } + } + return false; +} + +int APIENTRY wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPWSTR lpCmdLine, _In_ int nCmdShow) +{ + CHAR CurrentDirectory[MAX_PATH] = { 0 }; + CHAR Client64Path[MAX_PATH] = { 0 }; + CHAR ClientPath[MAX_PATH] = { 0 }; + CHAR ExeFile[MAX_PATH] = { 0 }; + CHAR ExeRunDir[MAX_PATH] = { 0 }; + CHAR ExeCommandLine[300] = { 0 }; + CHAR AppId[128] = { 0 }; + + STARTUPINFOA info = { sizeof(info) }; + PROCESS_INFORMATION processInfo; + + int Length = GetModuleFileNameA(GetModuleHandleA(NULL), CurrentDirectory, sizeof(CurrentDirectory)) + 1; + for (int i = Length; i > 0; i--) { + if (CurrentDirectory[i] == '\\') { + lstrcpyA(&CurrentDirectory[i + 1], "ColdClientLoader.ini"); + break; + } + } + if (GetFileAttributesA(CurrentDirectory) == INVALID_FILE_ATTRIBUTES) { + MessageBoxA(NULL, "Couldn't find the configuration file(ColdClientLoader.ini).", "ColdClientLoader", MB_ICONERROR); + ExitProcess(NULL); + } + + GetPrivateProfileStringA("SteamClient", "SteamClient64Dll", "", Client64Path, MAX_PATH, CurrentDirectory); + GetPrivateProfileStringA("SteamClient", "SteamClientDll", "", ClientPath, MAX_PATH, CurrentDirectory); + GetPrivateProfileStringA("SteamClient", "Exe", NULL, ExeFile, MAX_PATH, CurrentDirectory); + GetPrivateProfileStringA("SteamClient", "ExeRunDir", NULL, ExeRunDir, MAX_PATH, CurrentDirectory); + GetPrivateProfileStringA("SteamClient", "ExeCommandLine", NULL, ExeCommandLine, 300, CurrentDirectory); + GetPrivateProfileStringA("SteamClient", "AppId", NULL, AppId, sizeof(AppId), CurrentDirectory); + + if (AppId[0]) { + SetEnvironmentVariableA("SteamAppId", AppId); + SetEnvironmentVariableA("SteamGameId", AppId); + } + + CHAR TMP[MAX_PATH] = { 0 }; + if (!IsNotRelativePathOrRemoveFileName(Client64Path, false)) { + ZeroMemory(TMP, sizeof(TMP)); + lstrcpyA(TMP, Client64Path); + ZeroMemory(Client64Path, sizeof(Client64Path)); + GetFullPathNameA(TMP, MAX_PATH, Client64Path, NULL); + } + if (!IsNotRelativePathOrRemoveFileName(ClientPath, false)) { + ZeroMemory(TMP, sizeof(TMP)); + lstrcpyA(TMP, ClientPath); + ZeroMemory(ClientPath, sizeof(ClientPath)); + GetFullPathNameA(TMP, MAX_PATH, ClientPath, NULL); + } + if (!IsNotRelativePathOrRemoveFileName(ExeFile, false)) { + ZeroMemory(TMP, sizeof(TMP)); + lstrcpyA(TMP, ExeFile); + ZeroMemory(ExeFile, sizeof(ExeFile)); + GetFullPathNameA(TMP, MAX_PATH, ExeFile, NULL); + } + if (!IsNotRelativePathOrRemoveFileName(ExeRunDir, false)) { + ZeroMemory(TMP, sizeof(TMP)); + lstrcpyA(TMP, ExeRunDir); + ZeroMemory(ExeRunDir, sizeof(ExeRunDir)); + GetFullPathNameA(TMP, MAX_PATH, ExeRunDir, NULL); + } + + if (GetFileAttributesA(Client64Path) == INVALID_FILE_ATTRIBUTES) { + MessageBoxA(NULL, "Couldn't find the requested SteamClient64Dll.", "ColdClientLoader", MB_ICONERROR); + ExitProcess(NULL); + } + + if (GetFileAttributesA(ClientPath) == INVALID_FILE_ATTRIBUTES) { + MessageBoxA(NULL, "Couldn't find the requested SteamClientDll.", "ColdClientLoader", MB_ICONERROR); + ExitProcess(NULL); + } + + if (GetFileAttributesA(ExeFile) == INVALID_FILE_ATTRIBUTES) { + MessageBoxA(NULL, "Couldn't find the requested Exe file.", "ColdClientLoader", MB_ICONERROR); + ExitProcess(NULL); + } + + if (!ExeFile[0] || !CreateProcessA(ExeFile, ExeCommandLine, NULL, NULL, TRUE, CREATE_SUSPENDED, NULL, ExeRunDir, &info, &processInfo)) + { + MessageBoxA(NULL, "Unable to load the requested EXE file.", "ColdClientLoader", MB_ICONERROR); + ExitProcess(NULL); + } + HKEY Registrykey; + // Declare some variables to be used for Steam registry. + DWORD UserId = 0x03100004771F810D & 0xffffffff; + DWORD ProcessID = GetCurrentProcessId(); + + if (RegOpenKeyExA(HKEY_CURRENT_USER, "Software\\Valve\\Steam\\ActiveProcess", 0, KEY_ALL_ACCESS, &Registrykey) != ERROR_SUCCESS) + { + if (RegCreateKeyExA(HKEY_CURRENT_USER, "Software\\Valve\\Steam\\ActiveProcess", 0, 0, REG_OPTION_NON_VOLATILE, + KEY_ALL_ACCESS, NULL, &Registrykey, NULL) != ERROR_SUCCESS) + { + MessageBoxA(NULL, "Unable to patch Steam process informations on the Windows registry.", "ColdClientLoader", MB_ICONERROR); + TerminateProcess(processInfo.hProcess, NULL); + ExitProcess(NULL); + } + else + { + + // Set values to Windows registry. + RegSetValueExA(Registrykey, "ActiveUser", NULL, REG_DWORD, (LPBYTE)& UserId, sizeof(DWORD)); + RegSetValueExA(Registrykey, "pid", NULL, REG_DWORD, (LPBYTE)& ProcessID, sizeof(DWORD)); + + { + // Before saving to the registry check again if the path was valid and if the file exist + if (GetFileAttributesA(ClientPath) != INVALID_FILE_ATTRIBUTES) { + RegSetValueExA(Registrykey, "SteamClientDll", NULL, REG_SZ, (LPBYTE)ClientPath, (DWORD)lstrlenA(ClientPath) + 1); + } + else { + RegSetValueExA(Registrykey, "SteamClientDll", NULL, REG_SZ, (LPBYTE)"", (DWORD)lstrlenA(ClientPath) + 1); + } + if (GetFileAttributesA(Client64Path) != INVALID_FILE_ATTRIBUTES) { + RegSetValueExA(Registrykey, "SteamClientDll64", NULL, REG_SZ, (LPBYTE)Client64Path, (DWORD)lstrlenA(Client64Path) + 1); + } + else { + RegSetValueExA(Registrykey, "SteamClientDll64", NULL, REG_SZ, (LPBYTE)"", (DWORD)lstrlenA(Client64Path) + 1); + } + } + RegSetValueExA(Registrykey, "Universe", NULL, REG_SZ, (LPBYTE)"Public", (DWORD)lstrlenA("Public") + 1); + + // Close the HKEY Handle. + RegCloseKey(Registrykey); + + ResumeThread(processInfo.hThread); + WaitForSingleObject(processInfo.hThread, INFINITE); + CloseHandle(processInfo.hProcess); + CloseHandle(processInfo.hThread); + ExitProcess(NULL); + } + } + else + { + DWORD keyType = REG_SZ; + CHAR OrgSteamCDir[MAX_PATH] = { 0 }; + CHAR OrgSteamCDir64[MAX_PATH] = { 0 }; + DWORD Size1 = MAX_PATH; + DWORD Size2 = MAX_PATH; + + // Get original values to restore later. + RegQueryValueExA(Registrykey, "SteamClientDll", 0, &keyType, (LPBYTE)& OrgSteamCDir, &Size1); + RegQueryValueExA(Registrykey, "SteamClientDll64", 0, &keyType, (LPBYTE)& OrgSteamCDir64, &Size2); + + // Set values to Windows registry. + RegSetValueExA(Registrykey, "ActiveUser", NULL, REG_DWORD, (LPBYTE)& UserId, sizeof(DWORD)); + RegSetValueExA(Registrykey, "pid", NULL, REG_DWORD, (LPBYTE)& ProcessID, sizeof(DWORD)); + + + { + // Before saving to the registry check again if the path was valid and if the file exist + if (GetFileAttributesA(ClientPath) != INVALID_FILE_ATTRIBUTES) { + RegSetValueExA(Registrykey, "SteamClientDll", NULL, REG_SZ, (LPBYTE)ClientPath, (DWORD)lstrlenA(ClientPath) + 1); + } + else { + RegSetValueExA(Registrykey, "SteamClientDll", NULL, REG_SZ, (LPBYTE)"", (DWORD)lstrlenA(ClientPath) + 1); + } + if (GetFileAttributesA(Client64Path) != INVALID_FILE_ATTRIBUTES) { + RegSetValueExA(Registrykey, "SteamClientDll64", NULL, REG_SZ, (LPBYTE)Client64Path, (DWORD)lstrlenA(Client64Path) + 1); + } + else { + RegSetValueExA(Registrykey, "SteamClientDll64", NULL, REG_SZ, (LPBYTE)"", (DWORD)lstrlenA(Client64Path) + 1); + } + } + RegSetValueExA(Registrykey, "Universe", NULL, REG_SZ, (LPBYTE)"Public", (DWORD)lstrlenA("Public") + 1); + + // Close the HKEY Handle. + RegCloseKey(Registrykey); + + ResumeThread(processInfo.hThread); + WaitForSingleObject(processInfo.hThread, INFINITE); + CloseHandle(processInfo.hProcess); + CloseHandle(processInfo.hThread); + + if (RegOpenKeyExA(HKEY_CURRENT_USER, "Software\\Valve\\Steam\\ActiveProcess", 0, KEY_ALL_ACCESS, &Registrykey) == ERROR_SUCCESS) + { + // Restore the values. + RegSetValueExA(Registrykey, "SteamClientDll", NULL, REG_SZ, (LPBYTE)OrgSteamCDir, (DWORD)lstrlenA(OrgSteamCDir) + 1); + RegSetValueExA(Registrykey, "SteamClientDll64", NULL, REG_SZ, (LPBYTE)OrgSteamCDir64, (DWORD)lstrlenA(OrgSteamCDir64) + 1); + + // Close the HKEY Handle. + RegCloseKey(Registrykey); + } + ExitProcess(NULL); + } + + + return 1; +} diff --git a/steamclient_loader/ColdClientLoader.ini b/steamclient_loader/ColdClientLoader.ini new file mode 100644 index 00000000..bc65aa24 --- /dev/null +++ b/steamclient_loader/ColdClientLoader.ini @@ -0,0 +1,10 @@ +#My own modified version of ColdClientLoader originally by Rat431 +[SteamClient] +Exe=game.exe +ExeRunDir=. +ExeCommandLine= +#IMPORTANT: +AppId= + +SteamClientDll=steamclient.dll +SteamClient64Dll=steamclient64.dll