Skip to content

Commit

Permalink
add serialization entry points
Browse files Browse the repository at this point in the history
  • Loading branch information
Deweh committed Jul 29, 2024
1 parent 2d3b773 commit f748c0a
Show file tree
Hide file tree
Showing 7 changed files with 322 additions and 2 deletions.
11 changes: 9 additions & 2 deletions sfse/GameEvents.h
Original file line number Diff line number Diff line change
Expand Up @@ -685,8 +685,15 @@ struct TESEscortWaitStartEvent {};
struct TESEscortWaitStopEvent {};
struct TESExitBleedoutEvent {};
struct TESExitFurnitureEvent {};
struct TESFormDeleteEvent {};
struct TESFormIDRemapEvent {};
struct TESFormDeleteEvent
{
u32 formId; //00
};
struct TESFormIDRemapEvent
{
u32 oldID; //00
u32 newID; //04
};
struct TESFurnitureEvent {};
struct TESGrabReleaseEvent {};
struct TESInitScriptEvent {};
Expand Down
105 changes: 105 additions & 0 deletions sfse/Hooks_Serialization.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
#include "Hooks_Serialization.h"

#include "sfse_common/BranchTrampoline.h"
#include "sfse_common/Relocation.h"
#include "sfse_common/SafeWrite.h"

#include "sfse/PluginManager.h"
#include "sfse/Serialization.h"

#include "xbyak/xbyak.h"

class BGSSaveLoadGame;
class BGSSaveLoadManager;

typedef void (*_SaveGame)(BGSSaveLoadGame* a_this, void* a_unk1, void* a_unk2, const char* a_name);
RelocAddr <_SaveGame> SaveGame_Call(0x024ACCB0 + 0x12B);
RelocAddr <_SaveGame> SaveGame_Original(0x024AFCC0);

typedef bool (*_LoadGame)(BGSSaveLoadGame* a_this, const char* a_name, void* a_unk1, void* a_unk2);
RelocAddr <_LoadGame> LoadGame_Call(0x024DFF80 + 0x572);
RelocAddr <_LoadGame> LoadGame_Original(0x024B55B0);

typedef bool (*_DeleteSaveFile)(const char* a_filePath);
RelocAddr <_DeleteSaveFile> DeleteSaveFile_Call(0x024DF75C + 0x65);
RelocAddr <_DeleteSaveFile> DeleteSaveFile_Original(0x024DE118);

typedef bool (*_VM_SaveGame)(void* a_this, void* a_storage, void* a_handleReaderWriter, bool a_flag);
typedef bool (*_VM_LoadGame)(void* a_this, void* a_storage, void* a_handleReaderWriter, bool* a_flag, bool* b_flag);
typedef void* (*_VM_DropAllRunningData)(void* a_this);
_VM_SaveGame VM_SaveGame_Original = nullptr;
_VM_LoadGame VM_LoadGame_Original = nullptr;
_VM_DropAllRunningData VM_DropAllRunningData_Original = nullptr;
RelocAddr <uintptr_t> VirtualMachine_IVMSaveLoadInterface_VTable(0x0545A240);

void SaveGame_Hook(BGSSaveLoadGame* a_this, void* a_unk1, void* a_unk2, const char* a_name)
{
Serialization::SetSaveName(a_name, true);
PluginManager::dispatchMessage(0, SFSEMessagingInterface::kMessage_PreSaveGame, (void*)a_name, (u32)strlen(a_name), NULL);
SaveGame_Original(a_this, a_unk1, a_unk2, a_name);
PluginManager::dispatchMessage(0, SFSEMessagingInterface::kMessage_PostSaveGame, (void*)a_name, (u32)strlen(a_name), NULL);
Serialization::SetSaveName(NULL);
}

bool LoadGame_Hook(BGSSaveLoadGame* a_this, const char* a_name, void* a_unk1, void* a_unk2)
{
Serialization::SetSaveName(a_name, false);
Serialization::HandleBeginLoad();
PluginManager::dispatchMessage(0, SFSEMessagingInterface::kMessage_PreLoadGame, (void*)a_name, (u32)strlen(a_name), NULL);
bool result = LoadGame_Original(a_this, a_name, a_unk1, a_unk2);
PluginManager::dispatchMessage(0, SFSEMessagingInterface::kMessage_PostLoadGame, (void*)a_name, (u32)strlen(a_name), NULL);
Serialization::HandleEndLoad();
Serialization::SetSaveName(NULL);
return result;
}

bool DeleteSaveFile_Hook(const char* a_filePath)
{
bool result = DeleteSaveFile_Original(a_filePath);
Serialization::HandleDeleteSave(a_filePath);
return result;
}

void* VM_DropAllRunningData_Hook(void* a_this)
{
void* result = VM_DropAllRunningData_Original(a_this);
Serialization::HandleRevertGlobalData();
return result;
}

bool VM_SaveGame_Hook(void* a_this, void* a_storage, void* a_handleReaderWriter, bool a_flag)
{
bool result = VM_SaveGame_Original(a_this, a_storage, a_handleReaderWriter, a_flag);
Serialization::HandleSaveGlobalData();
return result;
}

bool VM_LoadGame_Hook(void* a_this, void* a_storage, void* a_handleReaderWriter, bool* a_flag, bool* b_flag)
{
bool result = VM_LoadGame_Original(a_this, a_storage, a_handleReaderWriter, a_flag, b_flag);
Serialization::HandleLoadGlobalData();
return result;
}

void Hooks_Serialization_Apply()
{
//write call hooks for SaveGame, LoadGame & DeleteSaveFile
g_branchTrampoline.write5Call(SaveGame_Call.getUIntPtr(), (uintptr_t)SaveGame_Hook);
g_branchTrampoline.write5Call(LoadGame_Call.getUIntPtr(), (uintptr_t)LoadGame_Hook);
g_branchTrampoline.write5Call(DeleteSaveFile_Call.getUIntPtr(), (uintptr_t)DeleteSaveFile_Hook);

//get pointers to IVMSaveLoadInterface vfunc entries
uintptr_t VM_SaveGame_VFunc = VirtualMachine_IVMSaveLoadInterface_VTable.getUIntPtr() + (0x1 * 0x8);
uintptr_t VM_LoadGame_VFunc = VirtualMachine_IVMSaveLoadInterface_VTable.getUIntPtr() + (0x2 * 0x8);
uintptr_t VM_DropAllRunningData_VFunc = VirtualMachine_IVMSaveLoadInterface_VTable.getUIntPtr() + (0x7 * 0x8);

//save original vfuncs
VM_SaveGame_Original = *reinterpret_cast<_VM_SaveGame*>(VM_SaveGame_VFunc);
VM_LoadGame_Original = *reinterpret_cast<_VM_LoadGame*>(VM_LoadGame_VFunc);
VM_DropAllRunningData_Original = *reinterpret_cast<_VM_DropAllRunningData*>(VM_DropAllRunningData_VFunc);

//overwrite vfuncs
safeWrite64(VM_SaveGame_VFunc, (uintptr_t)VM_SaveGame_Hook);
safeWrite64(VM_LoadGame_VFunc, (uintptr_t)VM_LoadGame_Hook);
safeWrite64(VM_DropAllRunningData_VFunc, (uintptr_t)VM_DropAllRunningData_Hook);
}
3 changes: 3 additions & 0 deletions sfse/Hooks_Serialization.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#pragma once

void Hooks_Serialization_Apply();
4 changes: 4 additions & 0 deletions sfse/PluginAPI.h
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,10 @@ struct SFSEMessagingInterface
kMessage_PostPostLoad, // sent right after kMessage_PostPostLoad to facilitate the correct dispatching/registering of messages/listeners
kMessage_PostDataLoad, // sent right after all game data has loaded (Passes TESDataHandler as pointer)
kMessage_PostPostDataLoad, // sent after all game data has loaded, and all PostDataLoad events have handled (Passes TESDataHandler as pointer)
kMessage_PreSaveGame,
kMessage_PostSaveGame,
kMessage_PreLoadGame,
kMessage_PostLoadGame,
};

std::uint32_t interfaceVersion;
Expand Down
182 changes: 182 additions & 0 deletions sfse/Serialization.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
#include "Serialization.h"
#include "GameEvents.h"

#include "sfse_common/Log.h"
#include "sfse_common/Errors.h"
#include "sfse_common/sfse_version.h"
#include "sfse_common/FileStream.h"
#include "sfse/GameSettings.h"

#include <ShlObj.h>
#include <unordered_map>
#include <unordered_set>
#include <io.h>

namespace Serialization
{
const char* kSavegamePath = "\\My Games\\" SAVE_FOLDER_NAME "\\";

std::unordered_map<u32, u32> changedIDs;
std::unordered_set<u32> deletedIDs;
std::string s_savePath;

struct IDRemapDeleteListener :
public BSTEventSink<TESFormIDRemapEvent>,
public BSTEventSink<TESFormDeleteEvent>
{
IDRemapDeleteListener()
{
GetEventSource<TESFormIDRemapEvent>()->RegisterSink(static_cast<BSTEventSink<TESFormIDRemapEvent>*>(this));
GetEventSource<TESFormDeleteEvent>()->RegisterSink(static_cast<BSTEventSink<TESFormDeleteEvent>*>(this));
}

virtual EventResult ProcessEvent(const TESFormIDRemapEvent& arEvent, BSTEventSource<TESFormIDRemapEvent>* eventSource)
{
changedIDs[arEvent.oldID] = arEvent.newID;
return EventResult::kContinue;
};

virtual EventResult ProcessEvent(const TESFormDeleteEvent& arEvent, BSTEventSource<TESFormDeleteEvent>* eventSource)
{
deletedIDs.insert(arEvent.formId);
return EventResult::kContinue;
};
};

void RemoveFileExtension(std::string& path)
{
size_t lastDot = path.find_last_of('.');
if (lastDot != std::string::npos) {
path.erase(lastDot);
}
}

std::string MakeSavePath(std::string name, const char* extension, bool hasExtension)
{
if (hasExtension)
{
RemoveFileExtension(name);
}

char path[MAX_PATH];
ASSERT(SUCCEEDED(SHGetFolderPath(NULL, CSIDL_MYDOCUMENTS, NULL, SHGFP_TYPE_CURRENT, path)));

std::string result = path;
result += kSavegamePath;
Setting* localSavePath = (*SettingT<INISettingCollection>::pCollection)->GetSetting("sLocalSavePath:General");
if (localSavePath && (localSavePath->GetType() == Setting::kType_String))
result += localSavePath->data.s;
else
result += "Saves\\";

result += "\\";
result += name;
if (extension)
result += extension;
return result;
}

void SetSaveName(const char* name, bool hasExtension)
{
if (name)
{
_MESSAGE("save name is %s", name);
s_savePath = MakeSavePath(name, ".sfse", hasExtension);
_MESSAGE("full save path: %s", s_savePath.c_str());
}
else
{
_MESSAGE("cleared save path");
s_savePath.clear();
}
}

void HandleBeginLoad()
{
//if the remap listener isn't already registered, register it now.
static IDRemapDeleteListener listener{};
changedIDs.clear();
deletedIDs.clear();
}

void HandleEndLoad()
{
changedIDs.clear();
deletedIDs.clear();
}

bool ResolveFormId(u32 formId, u32* formIdOut)
{
if (auto iter = changedIDs.find(formId); iter != changedIDs.end()) {
(*formIdOut) = iter->second;
return true;
}

if (deletedIDs.find(formId) == deletedIDs.end())
{
(*formIdOut) = formId;
return true;
}
else
{
return false;
}
}

bool ResolveHandle(u64 handle, u64* handleOut)
{
u32 formId = static_cast<u32>(handle & 0x00000000FFFFFFFF);
if (auto iter = changedIDs.find(formId); iter != changedIDs.end()) {
(*handleOut) = (handle & 0xFFFFFFFF00000000) | static_cast<u64>(iter->second);
return true;
}

if (deletedIDs.find(formId) == deletedIDs.end())
{
(*handleOut) = handle;
return true;
}
else
{
return false;
}
}

void HandleRevertGlobalData()
{
_MESSAGE("RevertGlobalData");

//TODO: add implementation for revert callbacks.
}

void HandleSaveGlobalData()
{
_MESSAGE("SaveGlobalData");

//TODO: add implementation for serialization & save callbacks.
}

void HandleLoadGlobalData()
{
_MESSAGE("LoadGlobalData");

//TODO: add implementation for deserialization & load callbacks.
}

void HandleDeleteSave(std::string filePath)
{
//check if old file is gone
FileStream saveFile;
if (!saveFile.open(filePath.c_str()))
{
RemoveFileExtension(filePath);
filePath += ".sfse";
_MESSAGE("deleting co-save %s", filePath.c_str());
DeleteFile(filePath.c_str());
}
else
{
_MESSAGE("skipped delete of co-save for file %s", filePath.c_str());
}
}
}
17 changes: 17 additions & 0 deletions sfse/Serialization.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#pragma once
#include <string>
#include "sfse_common/Types.h"

namespace Serialization
{
void SetSaveName(const char* name, bool hasExtension = false);
void HandleBeginLoad();
void HandleEndLoad();
bool ResolveFormId(u32 formId, u32* formIdOut);
bool ResolveHandle(u64 handle, u64* handleOut);
void HandleRevertGlobalData();
void HandleSaveGlobalData();
void HandleLoadGlobalData();

void HandleDeleteSave(std::string filePath);
}
2 changes: 2 additions & 0 deletions sfse/sfse.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#include "Hooks_Version.h"
#include "Hooks_Script.h"
#include "Hooks_Scaleform.h"
#include "Hooks_Serialization.h"
#include "Hooks_Data.h"
#include "Hooks_Command.h"

Expand Down Expand Up @@ -146,6 +147,7 @@ void SFSE_Initialize()
Hooks_Version_Apply();
Hooks_Script_Apply();
Hooks_Scaleform_Apply();
Hooks_Serialization_Apply();
Hooks_Data_Apply();
Hooks_Command_Apply();

Expand Down

0 comments on commit f748c0a

Please sign in to comment.