aboutsummaryrefslogtreecommitdiff
path: root/NorthstarDedicatedTest/keyvalues.cpp
blob: 7afad20ce43e285d3d8b337f6c10573bcf999a40 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
#include "pch.h"
#include "keyvalues.h"
#include "modmanager.h"
#include "filesystem.h"
#include "hookutils.h"

#include <fstream>

// hook forward defs
typedef char(*KeyValues__LoadFromBufferType)(void* self, const char* resourceName, const char* pBuffer, void* pFileSystem, void* a5, void* a6, int a7);
KeyValues__LoadFromBufferType KeyValues__LoadFromBuffer;
char KeyValues__LoadFromBufferHook(void* self, const char* resourceName, const char* pBuffer, void* pFileSystem, void* a5, void* a6, int a7);

void InitialiseKeyValues(HMODULE baseAddress)
{
	HookEnabler hook;
	ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0x426C30, &KeyValues__LoadFromBufferHook, reinterpret_cast<LPVOID*>(&KeyValues__LoadFromBuffer));
}

void* savedFilesystemPtr;

char KeyValues__LoadFromBufferHook(void* self, const char* resourceName, const char* pBuffer, void* pFileSystem, void* a5, void* a6, int a7)
{
	// this is just to allow playlists to get a valid pFileSystem ptr for kv building, other functions that call this particular overload of LoadFromBuffer seem to get called on network stuff exclusively
	// not exactly sure what the address wanted here is, so just taking it from a function call that always happens before playlists is loaded
	if (pFileSystem != nullptr)
		savedFilesystemPtr = pFileSystem;
	if (!pFileSystem && !strcmp(resourceName, "playlists"))
		pFileSystem = savedFilesystemPtr;

	return KeyValues__LoadFromBuffer(self, resourceName, pBuffer, pFileSystem, a5, a6, a7);
}

void ModManager::TryBuildKeyValues(const char* filename)
{
	spdlog::info("Building KeyValues for file {}", filename);

	std::string normalisedPath = fs::path(filename).lexically_normal().string();
	fs::path compiledPath = COMPILED_ASSETS_PATH / filename;
	fs::path compiledDir = compiledPath.parent_path();
	fs::create_directories(compiledDir);

	fs::path kvPath(filename);
	std::string ogFilePath = "mod_original_";
	ogFilePath += kvPath.filename().string();

	std::string newKvs = "// AUTOGENERATED: MOD PATCH KV\n";

	int patchNum = 0;

	// copy over patch kv files, and add #bases to new file, last mods' patches should be applied first
	// note: #include should be identical but it's actually just broken, thanks respawn
	for (int i = m_loadedMods.size() - 1; i > -1; i--)
	{
		if (!m_loadedMods[i].Enabled)
			continue;

		size_t fileHash = std::hash<std::string>{}(normalisedPath);
		for (int j = 0; j < m_loadedMods[i].KeyValuesHash.size(); j++)
		{
			if (fileHash == m_loadedMods[i].KeyValuesHash[j])
			{
				// should result in smth along the lines of #include "mod_patch_5_mp_weapon_car.txt"

				std::string patchFilePath = "mod_patch_";
				patchFilePath += std::to_string(patchNum++);
				patchFilePath += "_";
				patchFilePath += kvPath.filename().string();

				newKvs += "#base \"";
				newKvs += patchFilePath;
				newKvs += "\"\n";

				fs::remove(compiledDir / patchFilePath);

				fs::copy_file(m_loadedMods[i].ModDirectory / "keyvalues" / filename, compiledDir / patchFilePath);
			}
		}
	}

	// add original #base last, #bases don't override preexisting keys, including the ones we've just done 
	newKvs += "#base \"";
	newKvs += ogFilePath;
	newKvs += "\"\n";

	// load original file, so we can parse out the name of the root obj (e.g. WeaponData for weapons)
	std::string originalFile = ReadVPKOriginalFile(filename);

	if (!originalFile.length())
	{
		spdlog::warn("Tried to patch kv {} but no base kv was found!", ogFilePath);
		return;
	}

	char rootName[64];
	memset(rootName, 0, sizeof(rootName));

	// iterate until we hit an ascii char that isn't in a # command or comment to get root obj name
	int i = 0;
	while (!(originalFile[i] >= 65 && originalFile[i] <= 122))
	{
		// if we hit a comment or # thing, iterate until end of line
		if (originalFile[i] == '/' || originalFile[i] == '#')
			while (originalFile[i] != '\n')
				i++;
	
		i++;
	}
	
	int j = 0;
	for (int j = 0; originalFile[i] >= 65 && originalFile[i] <= 122; j++)
		rootName[j] = originalFile[i++];
	
	// empty kv, all the other stuff gets #base'd
	newKvs += rootName;
	newKvs += "\n{\n}\n";

	std::ofstream originalFileWriteStream(compiledDir / ogFilePath);
	originalFileWriteStream << originalFile;
	originalFileWriteStream.close();

	std::ofstream writeStream(compiledPath);
	writeStream << newKvs;
	writeStream.close();

	ModOverrideFile overrideFile;
	overrideFile.owningMod = nullptr;
	overrideFile.path = normalisedPath;

	if (m_modFiles.find(normalisedPath) == m_modFiles.end())
		m_modFiles.insert(std::make_pair(normalisedPath, overrideFile));
	else
		m_modFiles[normalisedPath] = overrideFile;
}