From 11666016edaf32dfdbc297f200ab0fd3e98a876c Mon Sep 17 00:00:00 2001 From: HW12Dev <26612968+HW12Dev@users.noreply.github.com> Date: Fri, 24 Feb 2023 21:06:49 +1100 Subject: [PATCH] Initial Commit --- .gitignore | 8 + AchievementHandler/AchievementHandler.filters | 36 ++ AchievementHandler/AchievementHandler.vcxproj | 109 ++++++ AchievementHandler/achievementshandler.cpp | 350 ++++++++++++++++++ AchievementHandler/achievementshandler.h | 33 ++ AchievementHandler/log.hpp | 35 ++ AchievementHandler/main.cpp | 77 ++++ DInputProxy/DInputProxy.vcxproj | 148 ++++++++ DInputProxy/DInputProxy.vcxproj.filters | 22 ++ DInputProxy/main.cpp | 101 +++++ Hitman3EpicSteamAchievements.sln | 41 ++ README.MD | 33 ++ 12 files changed, 993 insertions(+) create mode 100644 .gitignore create mode 100644 AchievementHandler/AchievementHandler.filters create mode 100644 AchievementHandler/AchievementHandler.vcxproj create mode 100644 AchievementHandler/achievementshandler.cpp create mode 100644 AchievementHandler/achievementshandler.h create mode 100644 AchievementHandler/log.hpp create mode 100644 AchievementHandler/main.cpp create mode 100644 DInputProxy/DInputProxy.vcxproj create mode 100644 DInputProxy/DInputProxy.vcxproj.filters create mode 100644 DInputProxy/main.cpp create mode 100644 Hitman3EpicSteamAchievements.sln create mode 100644 README.MD diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c4537a8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +x64/ +External/EOSSDK/** +External/steam/** + +*.user +*.log + +.vs/ \ No newline at end of file diff --git a/AchievementHandler/AchievementHandler.filters b/AchievementHandler/AchievementHandler.filters new file mode 100644 index 0000000..8f7c9b6 --- /dev/null +++ b/AchievementHandler/AchievementHandler.filters @@ -0,0 +1,36 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Source Files + + + Source Files + + + + + Header Files + + + Header Files + + + Header Files + + + \ No newline at end of file diff --git a/AchievementHandler/AchievementHandler.vcxproj b/AchievementHandler/AchievementHandler.vcxproj new file mode 100644 index 0000000..2b87526 --- /dev/null +++ b/AchievementHandler/AchievementHandler.vcxproj @@ -0,0 +1,109 @@ + + + + + Debug + x64 + + + Release + x64 + + + + {A263EC9F-83C0-4C51-8D9B-0D5888AA5452} + + + + + + + + + + + 16.0 + Win32Proj + {a263ec9f-83c0-4c51-8d9b-0d5888aa5452} + AchievementHandler + 10.0 + AchievementHandler + x64-windows-static + + + + DynamicLibrary + true + v143 + MultiByte + + + DynamicLibrary + false + v143 + true + MultiByte + + + + + + + + + + + + + + + $(SolutionDir)External\steam\sdk\redistributable_bin\win64;$(SolutionDir)External\EOSSDK\Lib;$(SolutionDir)External\bin;$(LibraryPath) + $(HITMAN3)\Retail + + + $(SolutionDir)External\steam\sdk\redistributable_bin\win64;$(SolutionDir)External\EOSSDK\Lib;$(SolutionDir)External\bin;$(LibraryPath) + $(HITMAN3)\Retail + + + true + + + + Level3 + true + _CRT_SECURE_NO_WARNINGS;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + $(SolutionDir)External\steam\sdk\public;$(SolutionDir)External\EOSSDK\Include;%(AdditionalIncludeDirectories) + stdcpplatest + + + Console + true + EOSSDK-Win64-Shipping.lib;%(AdditionalDependencies) + + + + + + + Level3 + true + true + true + _CRT_SECURE_NO_WARNINGS;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + $(SolutionDir)External\steam\sdk\public;$(SolutionDir)External\EOSSDK\Include;%(AdditionalIncludeDirectories) + stdcpplatest + + + Console + true + true + true + EOSSDK-Win64-Shipping.lib;%(AdditionalDependencies) + + + + + + \ No newline at end of file diff --git a/AchievementHandler/achievementshandler.cpp b/AchievementHandler/achievementshandler.cpp new file mode 100644 index 0000000..6435a6c --- /dev/null +++ b/AchievementHandler/achievementshandler.cpp @@ -0,0 +1,350 @@ +#ifndef _ACHIEVEMENTS_HANDLER_HPP_ +#define _ACHIEVEMENTS_HANDLER_HPP_ + +#include "achievementshandler.h" +#include "log.hpp" + +EOS_HPlatform EOSPlatform; + +AchievementsHandler g_achievements_handler = AchievementsHandler(); + +AchievementsHandler::AchievementsHandler() : CallbackAchievementStored(this, &AchievementsHandler::OnAchievementsStored) { + epic_to_steam_ach_bindings = { + // The Result of Previous Training +{ "ACH_000", "ACH_1" }, + +// Cleared for Field Duty +{ "ACH_001", "ACH_2" }, + +// Seizing the Opportunity +{ "ACH_002", "ACH_3" }, + +// The Creative Assassin +{ "ACH_003", "ACH_4" }, + +// Silent Assassin +{ "ACH_004", "ACH_5" }, + +// Training Escalated +{ "ACH_005", "ACH_6" }, + +// Top of the Class +{ "ACH_006", "ACH_7" }, + +// A New Profile +{ "ACH_007", "ACH_8" }, + +// Tools of the Trade +{ "ACH_008", "ACH_9" }, + +// Unseen Assassin +{ "ACH_009", "ACH_10" }, + +// Shortcut Killer +{ "ACH_010", "ACH_11" }, + +// Stylish Assassin +{ "ACH_011", "ACH_12" }, + +// Death From Above +{ "ACH_012", "ACH_13" }, + +// Dune Raider +{ "ACH_013", "ACH_14" }, + +// Treacherous Architecture +{ "ACH_014", "ACH_15" }, + +// Keep Your Eyes Peeled +{ "ACH_015", "ACH_16" }, + +// Rise Up +{ "ACH_016", "ACH_17" }, + +// Stair Master +{ "ACH_017", "ACH_18" }, + +// Master of the Household +{ "ACH_018", "ACH_19" }, + +// No Stone Unturned +{ "ACH_019", "ACH_20" }, + +// Upstairs, Downstairs +{ "ACH_020", "ACH_21" }, + +// Family Feud +{ "ACH_021", "ACH_22" }, + +// Full House +{ "ACH_022", "ACH_23" }, + +// The Great Outdoors +{ "ACH_023", "ACH_24" }, + +// Death of the Party +{ "ACH_024", "ACH_25" }, + +// Followed the Trails +{ "ACH_025", "ACH_26" }, + +// Partied Out +{ "ACH_026", "ACH_27" }, + +// Bird Art +{ "ACH_027", "ACH_28" }, + +// Last Call +{ "ACH_028", "ACH_29" }, + +// Warehouse Veteran +{ "ACH_029", "ACH_30" }, + +// NEXUS-47 +{ "ACH_030", "ACH_31" }, + +// Surveillance Master +{ "ACH_031", "ACH_32" }, + +// Future Shock +{ "ACH_032", "ACH_33" }, + +// Console Cowboy +{ "ACH_033", "ACH_34" }, + +// Icebreaker +{ "ACH_034", "ACH_35" }, + +// Hack the Planet +{ "ACH_035", "ACH_36" }, + +// The Last Tango +{ "ACH_036", "ACH_37" }, + +// Master the Terroir +{ "ACH_037", "ACH_38" }, + +// Ripe for the Picking +{ "ACH_038", "ACH_39" }, + +// Evil Wine Club +{ "ACH_039", "ACH_40" }, + +// Rich Harvest +{ "ACH_040", "ACH_41" }, + +// Vineyard Virtuoso +{ "ACH_041", "ACH_42" }, + +// Nightmare Fuel +{ "ACH_042", "ACH_43" }, + +// Bullet Train +{ "ACH_043", "ACH_44" }, + +// Count Down From 47 +{ "ACH_044", "ACH_45" }, + +// Train Surfing +{ "ACH_045", "ACH_46" }, + +// Last Stop +{ "ACH_046", "ACH_47" }, + +// Seven Figures +{ "ACH_047", "ACH_48" }, + +// Hawkeye +{ "ACH_048", "ACH_49" }, + +// Pure Poetry +{ "ACH_049", "ACH_50" }, + +// Break the Bank +{ "ACH_050", "ACH_51" }, + +// Top of the Heap +{ "ACH_051", "ACH_52" }, + +// In a League of Their Own +{ "ACH_052", "ACH_53" }, + +// Never Knew What Hit Them +{ "ACH_053", "ACH_54" }, + +// Capital Punishment +{ "ACH_054", "ACH_55" }, + +// Island and Chill +{ "ACH_055", "ACH_56" }, + +// Null and Void +{ "ACH_056", "ACH_57" }, + +// Infiltrator +{ "ACH_057", "ACH_58" }, + +// Local Knowledge +{ "ACH_058", "ACH_59" }, + +// Damage Control +{ "ACH_059", "ACH_60" }, + +// Miami Wise +{ "ACH_060", "ACH_61" }, + +// Tactical Strike +{ "ACH_061", "ACH_62" }, + +// Dark Tourist +{ "ACH_062", "ACH_63" }, + +// Pirate Hunter +{ "ACH_063", "ACH_64" }, + +// Keys to the City +{ "ACH_064", "ACH_65" }, + +// Long Shot +{ "ACH_065", "ACH_66" }, + +// Pillar of the Community +{ "ACH_066", "ACH_67" }, + +// This is Maintenance +{ "ACH_067", "ACH_68" }, + +// Honorary Member +{ "ACH_068", "ACH_69" }, + +// Silent Sniper +{ "ACH_069", "ACH_70" }, + +// When No One Else Dares +{ "ACH_070", "ACH_71" }, + +// City of Light +{ "ACH_071", "ACH_72" }, + +// Die By the Sword +{ "ACH_072", "ACH_73" }, + +// Amalfi Pearl +{ "ACH_073", "ACH_74" }, + +// Too Big to Fail +{ "ACH_074", "ACH_75" }, + +// Ancient Marrakesh +{ "ACH_075", "ACH_76" }, + +// Perfectionist +{ "ACH_076", "ACH_77" }, + +// Shining Bright +{ "ACH_077", "ACH_78" }, + +// One Night in Bangkok +{ "ACH_078", "ACH_79" }, + +// Guerrilla Warfare +{ "ACH_079", "ACH_80" }, + +// Mission Complete +{ "ACH_080", "ACH_81" }, + +// A Long Time Coming +{ "ACH_081", "ACH_82" }, + +// Sayōnara +{ "ACH_082", "ACH_83" } + + + }; + + SteamUserStats()->RequestCurrentStats(); + for (auto& entry : epic_to_steam_ach_bindings) { + bool is_unlocked; + SteamUserStats()->GetAchievement(entry.second.c_str(), &is_unlocked); + + if (is_unlocked) { + unlocked_steam_achievements.push_back(entry.second); + } + } +} + +std::string AchievementsHandler::get_steam_ach_id_from_epic(std::string_view epic) { + auto entry = epic_to_steam_ach_bindings.find(epic); + auto to_return = entry == epic_to_steam_ach_bindings.end() ? "ERROR" : entry->second; + return to_return; +} + +void AchievementsHandler::UnlockSteamAchievement(std::string epic_id) { + auto steam_ach_name = get_steam_ach_id_from_epic(epic_id); + + if (std::find(unlocked_steam_achievements.begin(), unlocked_steam_achievements.end(), steam_ach_name) != unlocked_steam_achievements.end()) { + // Don't bother unlocked an achievement already unlocked + return; + } + if (steam_ach_name != "ERROR") { + log(std::string("Unlocking Epic Achievement: ") + epic_id + std::string(" on Steam: ") + steam_ach_name); + SteamUserStats()->SetAchievement(steam_ach_name.c_str()); + SteamUserStats()->StoreStats(); + } +} + +void AchievementsHandler::OnAchievementsStored(UserAchievementStored_t* pParam) { + unlocked_steam_achievements.push_back(std::string(pParam->m_rgchAchievementName)); +} + +void AchievementsHandler::GetAllAchievementsForEpicAndUnlockThem(EOS_HPlatform platform, EOS_ProductUserId user_id) { + GetAllAchievements(platform, user_id); +} + +void PlayerAchievementStuff(EOS_HPlatform platform, EOS_ProductUserId user_id) { + EOS_HAchievements Achievements = EOS_Platform_GetAchievementsInterface(platform); + + EOS_Achievements_GetPlayerAchievementCountOptions ACO = {}; + ACO.ApiVersion = EOS_ACHIEVEMENTS_GETPLAYERACHIEVEMENTCOUNT_API_LATEST; + ACO.UserId = user_id; + + uint32_t AC = EOS_Achievements_GetPlayerAchievementCount(Achievements, &ACO); + + EOS_Achievements_CopyPlayerAchievementByIndexOptions CO = {}; + CO.ApiVersion = 1; + CO.TargetUserId = user_id; + CO.LocalUserId = user_id; + + for (CO.AchievementIndex = 0; CO.AchievementIndex < AC; ++CO.AchievementIndex) { + EOS_Achievements_PlayerAchievement* Achievement = NULL; + EOS_EResult CAR = EOS_Achievements_CopyPlayerAchievementByIndex(Achievements, &CO, &Achievement); + if (CAR != EOS_EResult::EOS_Success) { + log(std::string("ERROR: CPA FAILURE ") + EOS_EResult_ToString(CAR)); + break; + } + + if (Achievement->UnlockTime != -1) { + g_achievements_handler.UnlockSteamAchievement(std::string(Achievement->AchievementId)); + } + } +} + +void EOS_CALL QueryPlayerAchievementsReceivedCallbackFn(const EOS_Achievements_OnQueryPlayerAchievementsCompleteCallbackInfo* Data) { + if (Data->ResultCode != EOS_EResult::EOS_Success) { + log(std::string("Error: Failed to get Player Achievements. Error: ") + std::string(EOS_EResult_ToString(Data->ResultCode))); + return; + } + PlayerAchievementStuff(EOSPlatform, (EOS_ProductUserId)Data->ClientData); +} + +void GetAllAchievements(EOS_HPlatform platform, EOS_ProductUserId user_id) { + // HITMAN 3 uses ApiVersion 1, otherwise it errors as EOS_IncompatibleVersion + EOS_Achievements_QueryPlayerAchievementsOptions QPAO = {}; + QPAO.ApiVersion = 1; + QPAO.LocalUserId = user_id; + QPAO.TargetUserId = user_id; + EOSPlatform = platform; + EOS_Achievements_QueryPlayerAchievements(EOS_Platform_GetAchievementsInterface(platform), &QPAO, user_id, QueryPlayerAchievementsReceivedCallbackFn); +} + +#endif \ No newline at end of file diff --git a/AchievementHandler/achievementshandler.h b/AchievementHandler/achievementshandler.h new file mode 100644 index 0000000..9d8ff48 --- /dev/null +++ b/AchievementHandler/achievementshandler.h @@ -0,0 +1,33 @@ +#ifndef _ACHIEVEMENTS_HANDLER_H_ +#define _ACHIEVEMENTS_HANDLER_H_ +#include +#include + +#pragma comment(lib, "steam_api64.lib") +#include + +#include +#include +#include +#include + +class AchievementsHandler { +public: + AchievementsHandler(); + + std::string get_steam_ach_id_from_epic(std::string_view epic); + void UnlockSteamAchievement(std::string epic_id); + void GetAllAchievementsForEpicAndUnlockThem(EOS_HPlatform platform, EOS_ProductUserId user_id); + STEAM_CALLBACK(AchievementsHandler, OnAchievementsStored, UserAchievementStored_t, CallbackAchievementStored); +private: + std::unordered_map epic_to_steam_ach_bindings; + + // Stored in steam achievement ids. Ex. ACH_1...ACH_83 + std::vector unlocked_steam_achievements; +}; +void GetAllAchievements(EOS_HPlatform platform, EOS_ProductUserId user_id); + + +extern AchievementsHandler g_achievements_handler; + +#endif \ No newline at end of file diff --git a/AchievementHandler/log.hpp b/AchievementHandler/log.hpp new file mode 100644 index 0000000..866b744 --- /dev/null +++ b/AchievementHandler/log.hpp @@ -0,0 +1,35 @@ +#ifndef _LOG_HPP_ +#define _LOG_HPP_ + +#include +#include + +std::ofstream open_log_file() { + return std::ofstream(std::filesystem::current_path().string() + "/achievementhandler.log", std::ios::app); +} + +void log(std::string message) { + auto ofs = open_log_file(); + + ofs << message << "\n"; + + ofs.close(); +} + +void log_ccharpp(const char** message) { + auto ofs = open_log_file(); + + ofs << message << "\n"; + + ofs.close(); +} + +void log_two(std::string var_name, std::string var_var) { + auto ofs = open_log_file(); + + ofs << var_name << ": " << var_var << "\n"; + + ofs.close(); +} + +#endif \ No newline at end of file diff --git a/AchievementHandler/main.cpp b/AchievementHandler/main.cpp new file mode 100644 index 0000000..31dd28c --- /dev/null +++ b/AchievementHandler/main.cpp @@ -0,0 +1,77 @@ +#define WIN32_LEAN_AND_MEAN +#include + +#include +#include + +#include + +#include +#include + +#include "achievementshandler.h" + +EOS_HPlatform platform; + +#define FUNC_SIG_DEF(RETURN_TYPE, FUNCTIONNAME, ...) typedef RETURN_TYPE(*t##FUNCTIONNAME)(__VA_ARGS__); + +#define FUNC_POINTER_DEF(TYPE, FUNCTION_NAME) TYPE fp##FUNCTION_NAME = NULL; + +#define CREATE_HOOK(FUNCTIONNAME) MH_CreateHook(&##FUNCTIONNAME, &Hook_##FUNCTIONNAME, reinterpret_cast(&fp##FUNCTIONNAME)) + +typedef EOS_HPlatform(*tEOS_Platform_Create)(const EOS_Platform_Options*); + +tEOS_Platform_Create fpEOS_Platform_Create = NULL; + + +EOS_HPlatform Hook_EOS_Platform_Create(const EOS_Platform_Options* Options) { + platform = fpEOS_Platform_Create(Options); + return platform; +} + + +typedef void (*tEOS_Achievements_UnlockAchievements)(EOS_HAchievements Handle, const EOS_Achievements_UnlockAchievementsOptions* Options, void* ClientData, const EOS_Achievements_OnUnlockAchievementsCompleteCallback CompletionDelegate); + +tEOS_Achievements_UnlockAchievements fpEOS_Achievements_UnlockAchievements = NULL; +bool ran = false; +void Hook_EOS_Achievements_UnlockAchievements(EOS_HAchievements Handle, const EOS_Achievements_UnlockAchievementsOptions* Options, void* ClientData, const EOS_Achievements_OnUnlockAchievementsCompleteCallback CompletionDelegate) { + if (!ran) { + // For all achievements unlocked before using this mod + g_achievements_handler.GetAllAchievementsForEpicAndUnlockThem(platform, Options->UserId); + ran = true; + } + for (int i = 0; i < Options->AchievementsCount; i++) { + g_achievements_handler.UnlockSteamAchievement(Options->AchievementIds[i]); + } + fpEOS_Achievements_UnlockAchievements(Handle, Options, ClientData, CompletionDelegate); +} + + +BOOL WINAPI DllMain(HINSTANCE module, DWORD reason, LPVOID) { + if (reason == DLL_PROCESS_ATTACH) { + if (MH_Initialize() != MH_OK) { + MessageBox(NULL, "MH_Initialize failed", "", NULL); + return true; + } + + if (CREATE_HOOK(EOS_Platform_Create) != MH_OK || + + CREATE_HOOK(EOS_Achievements_UnlockAchievements) != MH_OK) { + MessageBox(NULL, "MH_CreateHook failed", "", NULL); + return true; + } + + if (MH_EnableHook(&EOS_Platform_Create) != MH_OK || + MH_EnableHook(&EOS_Achievements_UnlockAchievements) != MH_OK) { + MessageBox(NULL, "MH_EnableHook failed", "", NULL); + return true; + } + } + else if (reason == DLL_PROCESS_DETACH) { + if (MH_Uninitialize() != MH_OK) { + MessageBox(NULL, "MH_Uninitialize failed", "", NULL); + } + } + + return TRUE; +} \ No newline at end of file diff --git a/DInputProxy/DInputProxy.vcxproj b/DInputProxy/DInputProxy.vcxproj new file mode 100644 index 0000000..d7612c7 --- /dev/null +++ b/DInputProxy/DInputProxy.vcxproj @@ -0,0 +1,148 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 16.0 + Win32Proj + {2230944c-53f2-46bc-bec9-403bf51c1c68} + DInputProxy + 10.0.22000.0 + + + + DynamicLibrary + v143 + MultiByte + + + DynamicLibrary + v143 + MultiByte + + + DynamicLibrary + v143 + MultiByte + + + DynamicLibrary + v143 + MultiByte + + + + + + + + + + + + $(HITMAN3)\Retail + dinput8 + $(SolutionDir)External\steam\sdk\redistributable_bin\win64;$(LibraryPath) + + + $(HITMAN3)\Retail + dinput8 + $(SolutionDir)External\steam\sdk\redistributable_bin\win64;$(LibraryPath) + + + $(HITMAN3)\Retail + dinput8 + $(SolutionDir)External\steam\sdk\redistributable_bin\win64;$(LibraryPath) + + + $(HITMAN3)\Retail + dinput8 + $(SolutionDir)External\steam\sdk\redistributable_bin\win64;$(LibraryPath) + + + + Level3 + true + _CRT_SECURE_NO_WARNINGS;WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + $(SolutionDir)External\steam\sdk\public;%(AdditionalIncludeDirectories) + stdcpplatest + + + Console + true + + + + + Level3 + true + true + true + _CRT_SECURE_NO_WARNINGS;WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + $(SolutionDir)External\steam\sdk\public;%(AdditionalIncludeDirectories) + stdcpplatest + + + Console + true + true + true + + + + + Level3 + true + _CRT_SECURE_NO_WARNINGS;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + $(SolutionDir)External\steam\sdk\public;%(AdditionalIncludeDirectories) + stdcpplatest + + + Console + true + + + + + Level3 + true + true + true + _CRT_SECURE_NO_WARNINGS;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + $(SolutionDir)External\steam\sdk\public;%(AdditionalIncludeDirectories) + stdcpplatest + + + Console + true + true + true + + + + + + + + + \ No newline at end of file diff --git a/DInputProxy/DInputProxy.vcxproj.filters b/DInputProxy/DInputProxy.vcxproj.filters new file mode 100644 index 0000000..ce0c35c --- /dev/null +++ b/DInputProxy/DInputProxy.vcxproj.filters @@ -0,0 +1,22 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Source Files + + + \ No newline at end of file diff --git a/DInputProxy/main.cpp b/DInputProxy/main.cpp new file mode 100644 index 0000000..40bb18d --- /dev/null +++ b/DInputProxy/main.cpp @@ -0,0 +1,101 @@ +/* +* Code here is modifed from https://github.com/OrfeasZ/ZHMModSDK/blob/master/DirectInputProxy/Src/DirectInputProxy.cpp +* All credit goes to it's original authors +*/ + +#include +#include +#include +#include +#include +#include + +#include +#pragma comment(lib, "steam_api64.lib") + +static HMODULE g_OriginalDirectInput = nullptr; +static HMODULE g_ZHMModSDK = nullptr; +static HMODULE g_AchievementHandler = nullptr; + +typedef HRESULT(__stdcall* DirectInput8Create_t)(HINSTANCE, DWORD, REFIID, LPVOID*, LPUNKNOWN); +static DirectInput8Create_t o_DirectInput8Create = nullptr; + +BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpReserved) +{ + static wchar_t s_PathBuffer[8192]; + + if (fdwReason == DLL_PROCESS_ATTACH) + { + if (std::filesystem::exists("./Retail/AchievementHandler.dll")) { + SteamAPI_Init(); + g_AchievementHandler =LoadLibrary("AchievementHandler"); + } + if (!GetSystemDirectoryW(s_PathBuffer, sizeof(s_PathBuffer) / sizeof(wchar_t))) + return false; + + std::filesystem::path s_Dinput8Path = s_PathBuffer; + s_Dinput8Path += "/dinput8.dll"; + + g_OriginalDirectInput = LoadLibraryW(canonical(s_Dinput8Path).c_str()); + + if (g_OriginalDirectInput == nullptr) + return false; + + o_DirectInput8Create = reinterpret_cast(GetProcAddress(g_OriginalDirectInput, "DirectInput8Create")); + + if (o_DirectInput8Create == nullptr) + return false; + + // If this isn't the HITMAN3 executable then don't load the SDK. + if (!GetModuleFileNameW(nullptr, s_PathBuffer, sizeof(s_PathBuffer) / sizeof(wchar_t))) + return false; + + std::filesystem::path s_ModuleFilePath = s_PathBuffer; + std::string s_ExecutableName = s_ModuleFilePath.filename().string(); + std::transform(s_ExecutableName.begin(), s_ExecutableName.end(), s_ExecutableName.begin(), [](unsigned char c) { return std::tolower(c); }); + + // We return true here because if we return false then whatever app is loading this will crash. + if (s_ExecutableName != "hitman3.exe") + return true; + + // Load ZHMModSDK if present + if (std::filesystem::exists("./Retail/ZHMModSDK.dll")) + { + g_ZHMModSDK = LoadLibraryA("ZHMModSDK"); + + if (g_ZHMModSDK == nullptr) + return false; + } + } + else if (fdwReason == DLL_PROCESS_DETACH) + { + if (g_OriginalDirectInput != nullptr) + { + o_DirectInput8Create = nullptr; + + FreeLibrary(g_OriginalDirectInput); + g_OriginalDirectInput = nullptr; + } + + if (g_ZHMModSDK != nullptr) + { + FreeLibrary(g_ZHMModSDK); + g_ZHMModSDK = nullptr; + } + if (g_AchievementHandler != nullptr) + { + FreeLibrary(g_AchievementHandler); + g_AchievementHandler = nullptr; + } + } + + return true; +} + +extern "C" __declspec(dllexport) HRESULT DirectInput8Create(HINSTANCE hinst, DWORD dwVersion, REFIID riidltf, LPVOID * ppvOut, LPUNKNOWN punkOuter) +{ + if (o_DirectInput8Create == nullptr) + return MAKE_HRESULT(SEVERITY_ERROR, FACILITY_WIN32, ERROR_NOT_READY); // DIERR_NOTINITIALIZED + + return o_DirectInput8Create(hinst, dwVersion, riidltf, ppvOut, punkOuter); +} diff --git a/Hitman3EpicSteamAchievements.sln b/Hitman3EpicSteamAchievements.sln new file mode 100644 index 0000000..2be80e7 --- /dev/null +++ b/Hitman3EpicSteamAchievements.sln @@ -0,0 +1,41 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.4.33122.133 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "AchievementHandler", "AchievementHandler\AchievementHandler.vcxproj", "{A263EC9F-83C0-4C51-8D9B-0D5888AA5452}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "DInputProxy", "DInputProxy\DInputProxy.vcxproj", "{2230944C-53F2-46BC-BEC9-403BF51C1C68}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {A263EC9F-83C0-4C51-8D9B-0D5888AA5452}.Debug|x64.ActiveCfg = Debug|x64 + {A263EC9F-83C0-4C51-8D9B-0D5888AA5452}.Debug|x64.Build.0 = Debug|x64 + {A263EC9F-83C0-4C51-8D9B-0D5888AA5452}.Debug|x86.ActiveCfg = Debug|x64 + {A263EC9F-83C0-4C51-8D9B-0D5888AA5452}.Debug|x86.Build.0 = Debug|x64 + {A263EC9F-83C0-4C51-8D9B-0D5888AA5452}.Release|x64.ActiveCfg = Release|x64 + {A263EC9F-83C0-4C51-8D9B-0D5888AA5452}.Release|x64.Build.0 = Release|x64 + {A263EC9F-83C0-4C51-8D9B-0D5888AA5452}.Release|x86.ActiveCfg = Release|x64 + {A263EC9F-83C0-4C51-8D9B-0D5888AA5452}.Release|x86.Build.0 = Release|x64 + {2230944C-53F2-46BC-BEC9-403BF51C1C68}.Debug|x64.ActiveCfg = Debug|x64 + {2230944C-53F2-46BC-BEC9-403BF51C1C68}.Debug|x64.Build.0 = Debug|x64 + {2230944C-53F2-46BC-BEC9-403BF51C1C68}.Debug|x86.ActiveCfg = Debug|Win32 + {2230944C-53F2-46BC-BEC9-403BF51C1C68}.Debug|x86.Build.0 = Debug|Win32 + {2230944C-53F2-46BC-BEC9-403BF51C1C68}.Release|x64.ActiveCfg = Release|x64 + {2230944C-53F2-46BC-BEC9-403BF51C1C68}.Release|x64.Build.0 = Release|x64 + {2230944C-53F2-46BC-BEC9-403BF51C1C68}.Release|x86.ActiveCfg = Release|Win32 + {2230944C-53F2-46BC-BEC9-403BF51C1C68}.Release|x86.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {32C3A46C-8B4C-49DE-8542-5F2147A1668E} + EndGlobalSection +EndGlobal diff --git a/README.MD b/README.MD new file mode 100644 index 0000000..87df2e7 --- /dev/null +++ b/README.MD @@ -0,0 +1,33 @@ +**Hitman 3 Epic-Steam Achievements** + +This is a project I made to transfer any Epic Games Store achievements for HITMAN 3 to Steam as they are unlocked. + +This is not fully complete, so I will make changes overtime. + +**INSTALLING** + +NOTES: + - You require the Epic Games Store release of HITMAN 3 to use this. You must also own the Steam release, but having it installed this not required. + - `dinput8.dll` will also load ZHMModSDK if present in the HITMAN 3 install directory. + +INSTALLATION INSTRUCTIONS: + + - Download the latest release from the releases tab. + - Place `dinput8.dll`, `AchievementHandler.dll`, `steam_api64.dll` and `steam_appid.txt` into the HITMAN3/Retail folder. + - Once that is done, you can launch the game like normal. + +UNINSTALLATION INSTRUCTIONS: + - Delete `dinput8.dll`, `AchievementHandler.dll`, `steam_api64.dll` and `steam_appid.txt` from the HITMAN3/Retail folder. + +**BUILDING** +- Place the Epic Games Store SDK into `External/EOSSDK` so that the folder structure looks like: + + External/EOSSDK/Bin + External/EOSSDK/Include + External/EOSSDK/Lib + External/EOSSDK/Tools + +- Next, do the same with the Steamworks SDK so that the folder structure looks like: + + External/steam/sdk/public + External/steam/sdk/redistributable_bin