diff --git a/src/client/component/arxan.cpp b/src/client/component/arxan.cpp index 3f77095f..123eab45 100644 --- a/src/client/component/arxan.cpp +++ b/src/client/component/arxan.cpp @@ -3,6 +3,8 @@ #include "scheduler.hpp" #include "game/game.hpp" +#include "arxan.hpp" + #include namespace arxan @@ -108,6 +110,22 @@ namespace arxan } } + bool is_wine() + { + static bool is_wine = false; + static bool is_wine_set = false; + + if (!is_wine_set) + { + const utils::nt::library ntdll("ntdll.dll"); + is_wine = ntdll.get_proc("wine_get_version") != nullptr; + + is_wine_set = true; + } + + return is_wine; + } + class component final : public component_interface { public: @@ -137,9 +155,12 @@ namespace arxan void post_unpack() override { // cba to implement sp, not sure if it's even needed - if (game::environment::is_sp()) return; + if (game::environment::is_sp()) + { + return; + } } }; } -REGISTER_COMPONENT(arxan::component) \ No newline at end of file +REGISTER_COMPONENT(arxan::component) diff --git a/src/client/component/arxan.hpp b/src/client/component/arxan.hpp new file mode 100644 index 00000000..5a67dece --- /dev/null +++ b/src/client/component/arxan.hpp @@ -0,0 +1,6 @@ +#pragma once + +namespace arxan +{ + bool is_wine(); +} \ No newline at end of file diff --git a/src/client/component/steam_proxy.cpp b/src/client/component/steam_proxy.cpp index db8da166..fa3f2f24 100644 --- a/src/client/component/steam_proxy.cpp +++ b/src/client/component/steam_proxy.cpp @@ -3,6 +3,8 @@ #include "steam_proxy.hpp" #include "scheduler.hpp" +#include "arxan.hpp" + #include #include #include @@ -102,7 +104,10 @@ namespace steam_proxy void load_client() { const std::filesystem::path steam_path = steam::SteamAPI_GetSteamInstallPath(); - if (steam_path.empty()) return; + if (steam_path.empty()) + { + return; + } utils::nt::library::load(steam_path / "tier0_s64.dll"); utils::nt::library::load(steam_path / "vstdlib_s64.dll"); diff --git a/src/client/component/system_check.cpp b/src/client/component/system_check.cpp index 471ae5e7..64c64ed1 100644 --- a/src/client/component/system_check.cpp +++ b/src/client/component/system_check.cpp @@ -1,6 +1,7 @@ #include #include "loader/component_loader.hpp" #include "system_check.hpp" +#include "arxan.hpp" #include "game/game.hpp" @@ -64,14 +65,26 @@ namespace system_check return verify_hashes(mp_zone_hashes) && (game::environment::is_dedi() || verify_hashes(sp_zone_hashes)); } - // need to update these values void verify_binary_version() { const auto value = *reinterpret_cast(0x1337_b); - if (value != 0x60202B6A && value != 0xBC0E9FE) + + if (arxan::is_wine()) { - throw std::runtime_error("Unsupported Call of Duty: Modern Warfare Remastered version (1.15)"); + if (value != 0xFFB81262 && value != 0xFFB81143) + { + return; + } } + else + { + if (value != 0x60202B6A && value != 0xBC0E9FE) + { + return; + } + } + + throw std::runtime_error("Unsupported Call of Duty: Modern Warfare Remastered version (1.15)"); } } diff --git a/src/client/main.cpp b/src/client/main.cpp index c1486f82..f769f457 100644 --- a/src/client/main.cpp +++ b/src/client/main.cpp @@ -4,6 +4,8 @@ #include "loader/component_loader.hpp" #include "game/game.hpp" +#include "component/arxan.hpp" + #include #include #include @@ -202,11 +204,16 @@ int main() ShowWindow(GetConsoleWindow(), SW_HIDE); FARPROC entry_point; - enable_dpi_awareness(); - // This requires admin privilege, but I suppose many - // people will start with admin rights if it crashes. - limit_parallel_dll_loading(); + // leaving these for Windows only for now, need to test to see if we can have for Wine -mikey + if (!arxan::is_wine()) + { + enable_dpi_awareness(); + + // This requires admin privilege, but I suppose many + // people will start with admin rights if it crashes. + limit_parallel_dll_loading(); + } srand(uint32_t(time(nullptr))); remove_crash_file(); diff --git a/src/client/steam/steam.cpp b/src/client/steam/steam.cpp index ae8cd9b4..cde3b72c 100644 --- a/src/client/steam/steam.cpp +++ b/src/client/steam/steam.cpp @@ -1,10 +1,65 @@ #include #include "steam.hpp" +#include "component/arxan.hpp" +#include "component/console.hpp" + #include +#include + +#define GENERIC_RETURN_IF_FAIL(condition) \ + if (condition != S_OK) \ + { \ + return; \ + } namespace steam { + namespace + { + void open_folder_prompt(char* directory) + { + if (CoInitializeEx(0, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE) != S_OK) + { + MSG_BOX_ERROR("CoInitializeEx failed. This could be because uninitialization failed, try again."); + return; + } + + const auto _ = gsl::finally([]() + { + CoUninitialize(); // uninitialize on any return so we can use this again + }); + + IFileOpenDialog* dialog; + GENERIC_RETURN_IF_FAIL(CoCreateInstance(CLSID_FileOpenDialog, 0, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&dialog))); + + FILEOPENDIALOGOPTIONS dw_options; + GENERIC_RETURN_IF_FAIL(dialog->GetOptions(&dw_options)); + GENERIC_RETURN_IF_FAIL(dialog->SetOptions(dw_options | FOS_PICKFOLDERS | FOS_FORCEFILESYSTEM | FOS_PATHMUSTEXIST)); + GENERIC_RETURN_IF_FAIL(dialog->SetTitle(L"Select a valid Steam install (contains libraries like 'steam_api64.dll')")); + + if (dialog->Show(0) != S_OK) // doesn't returns S_OK when operation is cancelled + { + dialog->Release(); + return; + } + + IShellItem* shell_item_result; + GENERIC_RETURN_IF_FAIL(dialog->GetResult(&shell_item_result)); + + PWSTR tmp = nullptr; + GENERIC_RETURN_IF_FAIL(shell_item_result->GetDisplayName(SIGDN_FILESYSPATH, &tmp)) + + shell_item_result->Release(); + dialog->Release(); + + GENERIC_RETURN_IF_FAIL(tmp == nullptr); + + std::size_t i; + wcstombs_s(&i, directory, MAX_PATH, tmp, MAX_PATH); + } + } + uint64_t callbacks::call_id_ = 0; std::recursive_mutex callbacks::mutex_; std::map callbacks::calls_; @@ -111,12 +166,16 @@ namespace steam bool SteamAPI_Init() { const std::filesystem::path steam_path = steam::SteamAPI_GetSteamInstallPath(); - if (steam_path.empty()) return true; + if (steam_path.empty()) + { + return true; + } + + ::utils::nt::library::load(steam_path / "tier0_s64.dll"); // not shipped with Steam on linux + ::utils::nt::library::load(steam_path / "vstdlib_s64.dll"); // ^ + ::utils::nt::library::load(steam_path / "gameoverlayrenderer64.dll"); // however, this one is shipped + ::utils::nt::library::load(steam_path / "steamclient64.dll"); // and this one too - ::utils::nt::library::load(steam_path / "tier0_s64.dll"); - ::utils::nt::library::load(steam_path / "vstdlib_s64.dll"); - ::utils::nt::library::load(steam_path / "gameoverlayrenderer64.dll"); - ::utils::nt::library::load(steam_path / "steamclient64.dll"); return true; } @@ -149,6 +208,8 @@ namespace steam callbacks::unregister_callback(handler); } + // all this additional work I did just for the user to manually select the file may not be needed (Proton recognizes Steam registry) + // or even work half the time. because of that, we may need to revert to the old file dialog too :P -mikey const char* SteamAPI_GetSteamInstallPath() { static std::string install_path{}; @@ -157,24 +218,74 @@ namespace steam return install_path.data(); } + char path[MAX_PATH] = {0}; + DWORD length = sizeof(path); + HKEY reg_key; + + // check if Steam contains information in registry for the install path if (RegOpenKeyExA(HKEY_LOCAL_MACHINE, "Software\\WOW6432Node\\Valve\\Steam", 0, KEY_QUERY_VALUE, - ®_key) == - ERROR_SUCCESS) + ®_key) == ERROR_SUCCESS) { - char path[MAX_PATH] = {0}; - DWORD length = sizeof(path); RegQueryValueExA(reg_key, "InstallPath", nullptr, nullptr, reinterpret_cast(path), &length); RegCloseKey(reg_key); - install_path = path; } + else + { + // if we can't find Steam in the registry, let's check if we are on Wine or not. + // if the user is using Steam's Proton, a fork of Wine, then this code won't even be hit as it works above + // however, on [other] Wine [forks], this should be hit. also, the file dialog may not work.. :P + HKEY steam_install_reg; + + if (arxan::is_wine()) + { + // let's check the registry to see if the user has already manually selected the Steam installation path + if (RegOpenKeyExA(HKEY_CURRENT_USER, "Software\\h1-mod", 0, KEY_QUERY_VALUE, &steam_install_reg) + != ERROR_SUCCESS) + { + // create a registry key + if (RegCreateKeyExA(HKEY_CURRENT_USER, "Software\\h1-mod", 0, nullptr, 0, KEY_WRITE, nullptr, &steam_install_reg, nullptr) + != ERROR_SUCCESS) + { + MSG_BOX_ERROR("Could not create registry for Steam install path."); + return ""; + } + + // create a pointer to our path variable to use in our open_folder function + char* directory_ptr = path; + + // open a file explorer prompt to find the Steam directory (user input) + open_folder_prompt(directory_ptr); + while (!strcmp(directory_ptr, "")) // if this while statement goes, this means that the operation was cancelled + { + MSG_BOX_ERROR("You must select a valid Steam directory before you can continue."); + open_folder_prompt(directory_ptr); + } + + // if the directory pointer is defined, then we set "steam_install" inside "Software\\h1-mod" to the path + if (RegSetKeyValueA(steam_install_reg, nullptr, "steam_install", REG_SZ, ::utils::string::va("\"%s\"", path), length) != ERROR_SUCCESS) + { + MSG_BOX_ERROR("Failed to set valid Steam install path in registry, please try again."); + return ""; + } + } + + // query "steam_install" inside "Software\\h1-mod" which will define our path variable + RegQueryValueExA(steam_install_reg, "steam_install", nullptr, nullptr, reinterpret_cast(path), &length); + install_path = path; + RegCloseKey(steam_install_reg); + } + else + { + MSG_BOX_ERROR("Failed to find a Steam installation."); + } + } return install_path.data(); } - bool SteamGameServer_Init() { return true; @@ -244,4 +355,4 @@ namespace steam } } -} \ No newline at end of file +}