Skip to content

Commit f748c0a

Browse files
committed
add serialization entry points
1 parent 2d3b773 commit f748c0a

File tree

7 files changed

+322
-2
lines changed

7 files changed

+322
-2
lines changed

sfse/GameEvents.h

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -685,8 +685,15 @@ struct TESEscortWaitStartEvent {};
685685
struct TESEscortWaitStopEvent {};
686686
struct TESExitBleedoutEvent {};
687687
struct TESExitFurnitureEvent {};
688-
struct TESFormDeleteEvent {};
689-
struct TESFormIDRemapEvent {};
688+
struct TESFormDeleteEvent
689+
{
690+
u32 formId; //00
691+
};
692+
struct TESFormIDRemapEvent
693+
{
694+
u32 oldID; //00
695+
u32 newID; //04
696+
};
690697
struct TESFurnitureEvent {};
691698
struct TESGrabReleaseEvent {};
692699
struct TESInitScriptEvent {};

sfse/Hooks_Serialization.cpp

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
#include "Hooks_Serialization.h"
2+
3+
#include "sfse_common/BranchTrampoline.h"
4+
#include "sfse_common/Relocation.h"
5+
#include "sfse_common/SafeWrite.h"
6+
7+
#include "sfse/PluginManager.h"
8+
#include "sfse/Serialization.h"
9+
10+
#include "xbyak/xbyak.h"
11+
12+
class BGSSaveLoadGame;
13+
class BGSSaveLoadManager;
14+
15+
typedef void (*_SaveGame)(BGSSaveLoadGame* a_this, void* a_unk1, void* a_unk2, const char* a_name);
16+
RelocAddr <_SaveGame> SaveGame_Call(0x024ACCB0 + 0x12B);
17+
RelocAddr <_SaveGame> SaveGame_Original(0x024AFCC0);
18+
19+
typedef bool (*_LoadGame)(BGSSaveLoadGame* a_this, const char* a_name, void* a_unk1, void* a_unk2);
20+
RelocAddr <_LoadGame> LoadGame_Call(0x024DFF80 + 0x572);
21+
RelocAddr <_LoadGame> LoadGame_Original(0x024B55B0);
22+
23+
typedef bool (*_DeleteSaveFile)(const char* a_filePath);
24+
RelocAddr <_DeleteSaveFile> DeleteSaveFile_Call(0x024DF75C + 0x65);
25+
RelocAddr <_DeleteSaveFile> DeleteSaveFile_Original(0x024DE118);
26+
27+
typedef bool (*_VM_SaveGame)(void* a_this, void* a_storage, void* a_handleReaderWriter, bool a_flag);
28+
typedef bool (*_VM_LoadGame)(void* a_this, void* a_storage, void* a_handleReaderWriter, bool* a_flag, bool* b_flag);
29+
typedef void* (*_VM_DropAllRunningData)(void* a_this);
30+
_VM_SaveGame VM_SaveGame_Original = nullptr;
31+
_VM_LoadGame VM_LoadGame_Original = nullptr;
32+
_VM_DropAllRunningData VM_DropAllRunningData_Original = nullptr;
33+
RelocAddr <uintptr_t> VirtualMachine_IVMSaveLoadInterface_VTable(0x0545A240);
34+
35+
void SaveGame_Hook(BGSSaveLoadGame* a_this, void* a_unk1, void* a_unk2, const char* a_name)
36+
{
37+
Serialization::SetSaveName(a_name, true);
38+
PluginManager::dispatchMessage(0, SFSEMessagingInterface::kMessage_PreSaveGame, (void*)a_name, (u32)strlen(a_name), NULL);
39+
SaveGame_Original(a_this, a_unk1, a_unk2, a_name);
40+
PluginManager::dispatchMessage(0, SFSEMessagingInterface::kMessage_PostSaveGame, (void*)a_name, (u32)strlen(a_name), NULL);
41+
Serialization::SetSaveName(NULL);
42+
}
43+
44+
bool LoadGame_Hook(BGSSaveLoadGame* a_this, const char* a_name, void* a_unk1, void* a_unk2)
45+
{
46+
Serialization::SetSaveName(a_name, false);
47+
Serialization::HandleBeginLoad();
48+
PluginManager::dispatchMessage(0, SFSEMessagingInterface::kMessage_PreLoadGame, (void*)a_name, (u32)strlen(a_name), NULL);
49+
bool result = LoadGame_Original(a_this, a_name, a_unk1, a_unk2);
50+
PluginManager::dispatchMessage(0, SFSEMessagingInterface::kMessage_PostLoadGame, (void*)a_name, (u32)strlen(a_name), NULL);
51+
Serialization::HandleEndLoad();
52+
Serialization::SetSaveName(NULL);
53+
return result;
54+
}
55+
56+
bool DeleteSaveFile_Hook(const char* a_filePath)
57+
{
58+
bool result = DeleteSaveFile_Original(a_filePath);
59+
Serialization::HandleDeleteSave(a_filePath);
60+
return result;
61+
}
62+
63+
void* VM_DropAllRunningData_Hook(void* a_this)
64+
{
65+
void* result = VM_DropAllRunningData_Original(a_this);
66+
Serialization::HandleRevertGlobalData();
67+
return result;
68+
}
69+
70+
bool VM_SaveGame_Hook(void* a_this, void* a_storage, void* a_handleReaderWriter, bool a_flag)
71+
{
72+
bool result = VM_SaveGame_Original(a_this, a_storage, a_handleReaderWriter, a_flag);
73+
Serialization::HandleSaveGlobalData();
74+
return result;
75+
}
76+
77+
bool VM_LoadGame_Hook(void* a_this, void* a_storage, void* a_handleReaderWriter, bool* a_flag, bool* b_flag)
78+
{
79+
bool result = VM_LoadGame_Original(a_this, a_storage, a_handleReaderWriter, a_flag, b_flag);
80+
Serialization::HandleLoadGlobalData();
81+
return result;
82+
}
83+
84+
void Hooks_Serialization_Apply()
85+
{
86+
//write call hooks for SaveGame, LoadGame & DeleteSaveFile
87+
g_branchTrampoline.write5Call(SaveGame_Call.getUIntPtr(), (uintptr_t)SaveGame_Hook);
88+
g_branchTrampoline.write5Call(LoadGame_Call.getUIntPtr(), (uintptr_t)LoadGame_Hook);
89+
g_branchTrampoline.write5Call(DeleteSaveFile_Call.getUIntPtr(), (uintptr_t)DeleteSaveFile_Hook);
90+
91+
//get pointers to IVMSaveLoadInterface vfunc entries
92+
uintptr_t VM_SaveGame_VFunc = VirtualMachine_IVMSaveLoadInterface_VTable.getUIntPtr() + (0x1 * 0x8);
93+
uintptr_t VM_LoadGame_VFunc = VirtualMachine_IVMSaveLoadInterface_VTable.getUIntPtr() + (0x2 * 0x8);
94+
uintptr_t VM_DropAllRunningData_VFunc = VirtualMachine_IVMSaveLoadInterface_VTable.getUIntPtr() + (0x7 * 0x8);
95+
96+
//save original vfuncs
97+
VM_SaveGame_Original = *reinterpret_cast<_VM_SaveGame*>(VM_SaveGame_VFunc);
98+
VM_LoadGame_Original = *reinterpret_cast<_VM_LoadGame*>(VM_LoadGame_VFunc);
99+
VM_DropAllRunningData_Original = *reinterpret_cast<_VM_DropAllRunningData*>(VM_DropAllRunningData_VFunc);
100+
101+
//overwrite vfuncs
102+
safeWrite64(VM_SaveGame_VFunc, (uintptr_t)VM_SaveGame_Hook);
103+
safeWrite64(VM_LoadGame_VFunc, (uintptr_t)VM_LoadGame_Hook);
104+
safeWrite64(VM_DropAllRunningData_VFunc, (uintptr_t)VM_DropAllRunningData_Hook);
105+
}

sfse/Hooks_Serialization.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
#pragma once
2+
3+
void Hooks_Serialization_Apply();

sfse/PluginAPI.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,10 @@ struct SFSEMessagingInterface
104104
kMessage_PostPostLoad, // sent right after kMessage_PostPostLoad to facilitate the correct dispatching/registering of messages/listeners
105105
kMessage_PostDataLoad, // sent right after all game data has loaded (Passes TESDataHandler as pointer)
106106
kMessage_PostPostDataLoad, // sent after all game data has loaded, and all PostDataLoad events have handled (Passes TESDataHandler as pointer)
107+
kMessage_PreSaveGame,
108+
kMessage_PostSaveGame,
109+
kMessage_PreLoadGame,
110+
kMessage_PostLoadGame,
107111
};
108112

109113
std::uint32_t interfaceVersion;

sfse/Serialization.cpp

Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
#include "Serialization.h"
2+
#include "GameEvents.h"
3+
4+
#include "sfse_common/Log.h"
5+
#include "sfse_common/Errors.h"
6+
#include "sfse_common/sfse_version.h"
7+
#include "sfse_common/FileStream.h"
8+
#include "sfse/GameSettings.h"
9+
10+
#include <ShlObj.h>
11+
#include <unordered_map>
12+
#include <unordered_set>
13+
#include <io.h>
14+
15+
namespace Serialization
16+
{
17+
const char* kSavegamePath = "\\My Games\\" SAVE_FOLDER_NAME "\\";
18+
19+
std::unordered_map<u32, u32> changedIDs;
20+
std::unordered_set<u32> deletedIDs;
21+
std::string s_savePath;
22+
23+
struct IDRemapDeleteListener :
24+
public BSTEventSink<TESFormIDRemapEvent>,
25+
public BSTEventSink<TESFormDeleteEvent>
26+
{
27+
IDRemapDeleteListener()
28+
{
29+
GetEventSource<TESFormIDRemapEvent>()->RegisterSink(static_cast<BSTEventSink<TESFormIDRemapEvent>*>(this));
30+
GetEventSource<TESFormDeleteEvent>()->RegisterSink(static_cast<BSTEventSink<TESFormDeleteEvent>*>(this));
31+
}
32+
33+
virtual EventResult ProcessEvent(const TESFormIDRemapEvent& arEvent, BSTEventSource<TESFormIDRemapEvent>* eventSource)
34+
{
35+
changedIDs[arEvent.oldID] = arEvent.newID;
36+
return EventResult::kContinue;
37+
};
38+
39+
virtual EventResult ProcessEvent(const TESFormDeleteEvent& arEvent, BSTEventSource<TESFormDeleteEvent>* eventSource)
40+
{
41+
deletedIDs.insert(arEvent.formId);
42+
return EventResult::kContinue;
43+
};
44+
};
45+
46+
void RemoveFileExtension(std::string& path)
47+
{
48+
size_t lastDot = path.find_last_of('.');
49+
if (lastDot != std::string::npos) {
50+
path.erase(lastDot);
51+
}
52+
}
53+
54+
std::string MakeSavePath(std::string name, const char* extension, bool hasExtension)
55+
{
56+
if (hasExtension)
57+
{
58+
RemoveFileExtension(name);
59+
}
60+
61+
char path[MAX_PATH];
62+
ASSERT(SUCCEEDED(SHGetFolderPath(NULL, CSIDL_MYDOCUMENTS, NULL, SHGFP_TYPE_CURRENT, path)));
63+
64+
std::string result = path;
65+
result += kSavegamePath;
66+
Setting* localSavePath = (*SettingT<INISettingCollection>::pCollection)->GetSetting("sLocalSavePath:General");
67+
if (localSavePath && (localSavePath->GetType() == Setting::kType_String))
68+
result += localSavePath->data.s;
69+
else
70+
result += "Saves\\";
71+
72+
result += "\\";
73+
result += name;
74+
if (extension)
75+
result += extension;
76+
return result;
77+
}
78+
79+
void SetSaveName(const char* name, bool hasExtension)
80+
{
81+
if (name)
82+
{
83+
_MESSAGE("save name is %s", name);
84+
s_savePath = MakeSavePath(name, ".sfse", hasExtension);
85+
_MESSAGE("full save path: %s", s_savePath.c_str());
86+
}
87+
else
88+
{
89+
_MESSAGE("cleared save path");
90+
s_savePath.clear();
91+
}
92+
}
93+
94+
void HandleBeginLoad()
95+
{
96+
//if the remap listener isn't already registered, register it now.
97+
static IDRemapDeleteListener listener{};
98+
changedIDs.clear();
99+
deletedIDs.clear();
100+
}
101+
102+
void HandleEndLoad()
103+
{
104+
changedIDs.clear();
105+
deletedIDs.clear();
106+
}
107+
108+
bool ResolveFormId(u32 formId, u32* formIdOut)
109+
{
110+
if (auto iter = changedIDs.find(formId); iter != changedIDs.end()) {
111+
(*formIdOut) = iter->second;
112+
return true;
113+
}
114+
115+
if (deletedIDs.find(formId) == deletedIDs.end())
116+
{
117+
(*formIdOut) = formId;
118+
return true;
119+
}
120+
else
121+
{
122+
return false;
123+
}
124+
}
125+
126+
bool ResolveHandle(u64 handle, u64* handleOut)
127+
{
128+
u32 formId = static_cast<u32>(handle & 0x00000000FFFFFFFF);
129+
if (auto iter = changedIDs.find(formId); iter != changedIDs.end()) {
130+
(*handleOut) = (handle & 0xFFFFFFFF00000000) | static_cast<u64>(iter->second);
131+
return true;
132+
}
133+
134+
if (deletedIDs.find(formId) == deletedIDs.end())
135+
{
136+
(*handleOut) = handle;
137+
return true;
138+
}
139+
else
140+
{
141+
return false;
142+
}
143+
}
144+
145+
void HandleRevertGlobalData()
146+
{
147+
_MESSAGE("RevertGlobalData");
148+
149+
//TODO: add implementation for revert callbacks.
150+
}
151+
152+
void HandleSaveGlobalData()
153+
{
154+
_MESSAGE("SaveGlobalData");
155+
156+
//TODO: add implementation for serialization & save callbacks.
157+
}
158+
159+
void HandleLoadGlobalData()
160+
{
161+
_MESSAGE("LoadGlobalData");
162+
163+
//TODO: add implementation for deserialization & load callbacks.
164+
}
165+
166+
void HandleDeleteSave(std::string filePath)
167+
{
168+
//check if old file is gone
169+
FileStream saveFile;
170+
if (!saveFile.open(filePath.c_str()))
171+
{
172+
RemoveFileExtension(filePath);
173+
filePath += ".sfse";
174+
_MESSAGE("deleting co-save %s", filePath.c_str());
175+
DeleteFile(filePath.c_str());
176+
}
177+
else
178+
{
179+
_MESSAGE("skipped delete of co-save for file %s", filePath.c_str());
180+
}
181+
}
182+
}

sfse/Serialization.h

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
#pragma once
2+
#include <string>
3+
#include "sfse_common/Types.h"
4+
5+
namespace Serialization
6+
{
7+
void SetSaveName(const char* name, bool hasExtension = false);
8+
void HandleBeginLoad();
9+
void HandleEndLoad();
10+
bool ResolveFormId(u32 formId, u32* formIdOut);
11+
bool ResolveHandle(u64 handle, u64* handleOut);
12+
void HandleRevertGlobalData();
13+
void HandleSaveGlobalData();
14+
void HandleLoadGlobalData();
15+
16+
void HandleDeleteSave(std::string filePath);
17+
}

sfse/sfse.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
#include "Hooks_Version.h"
1313
#include "Hooks_Script.h"
1414
#include "Hooks_Scaleform.h"
15+
#include "Hooks_Serialization.h"
1516
#include "Hooks_Data.h"
1617
#include "Hooks_Command.h"
1718

@@ -146,6 +147,7 @@ void SFSE_Initialize()
146147
Hooks_Version_Apply();
147148
Hooks_Script_Apply();
148149
Hooks_Scaleform_Apply();
150+
Hooks_Serialization_Apply();
149151
Hooks_Data_Apply();
150152
Hooks_Command_Apply();
151153

0 commit comments

Comments
 (0)