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