aboutsummaryrefslogtreecommitdiff
path: root/NorthstarDLL/keyvalues.cpp
blob: 08c3dac88bb27c4120482016ce6eb55445d3779f (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
#include "pch.h"
#include "modmanager.h"
#include "filesystem.h"

#include <fstream>

AUTOHOOK_INIT()

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

	std::string normalisedPath = g_pModManager->NormaliseModFilePath(fs::path(filename));
	fs::path compiledPath = GetCompiledAssetsPath() / 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 (int64_t i = m_LoadedMods.size() - 1; i > -1; i--)
	{
		if (!m_LoadedMods[i].m_bEnabled)
			continue;

		size_t fileHash = STR_HASH(normalisedPath);
		auto modKv = m_LoadedMods[i].KeyValues.find(fileHash);
		if (modKv != m_LoadedMods[i].KeyValues.end())
		{
			// 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].m_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 = R2::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, std::ios::binary);
	originalFileWriteStream << originalFile;
	originalFileWriteStream.close();

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

	ModOverrideFile overrideFile;
	overrideFile.m_pOwningMod = nullptr;
	overrideFile.m_Path = normalisedPath;

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

AUTOHOOK(KeyValues__LoadFromBuffer, engine.dll + 0x426C30,
char,, (void* self, const char* resourceName, const char* pBuffer, void* pFileSystem, void* a5, void* a6, int a7))
{
	static void* pSavedFilesystemPtr = nullptr;

	// 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

	// note: would be better if we could serialize this to disk for playlists, as this method breaks saving playlists in demos
	if (pFileSystem != nullptr)
		pSavedFilesystemPtr = pFileSystem;
	if (!pFileSystem && !strcmp(resourceName, "playlists"))
		pFileSystem = pSavedFilesystemPtr;

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

ON_DLL_LOAD("engine.dll", KeyValues, (CModule module))
{
	AUTOHOOK_DISPATCH()
}