Custom font & material support

This commit is contained in:
Federico Cecchetto 2022-03-19 23:06:00 +01:00
parent 2dc8a30ea6
commit 964ceb5be9
12 changed files with 542 additions and 26 deletions

View File

@ -0,0 +1,56 @@
#include <std_include.hpp>
#include "loader/component_loader.hpp"
#include "filesystem.hpp"
#include <utils/io.hpp>
namespace filesystem
{
std::unordered_set<std::string>& get_search_paths()
{
static std::unordered_set<std::string> search_paths{};
return search_paths;
}
std::string read_file(const std::string& path)
{
for (const auto& search_path : get_search_paths())
{
const auto path_ = search_path + "/" + path;
if (utils::io::file_exists(path_))
{
return utils::io::read_file(path_);
}
}
return {};
}
bool read_file(const std::string& path, std::string* data)
{
for (const auto& search_path : get_search_paths())
{
const auto path_ = search_path + "/" + path;
if (utils::io::read_file(path_, data))
{
return true;
}
}
return false;
}
class component final : public component_interface
{
public:
void post_unpack() override
{
get_search_paths().insert(".");
get_search_paths().insert("h2-mod");
get_search_paths().insert("data");
}
};
}
REGISTER_COMPONENT(filesystem::component)

View File

@ -0,0 +1,8 @@
#pragma once
namespace filesystem
{
std::unordered_set<std::string>& get_search_paths();
std::string read_file(const std::string& path);
bool read_file(const std::string& path, std::string* data);
}

View File

@ -0,0 +1,131 @@
#include <std_include.hpp>
#include "loader/component_loader.hpp"
#include "fonts.hpp"
#include "game_console.hpp"
#include "filesystem.hpp"
#include "game/game.hpp"
#include "game/dvars.hpp"
#include <utils/hook.hpp>
#include <utils/memory.hpp>
#include <utils/io.hpp>
#include <utils/string.hpp>
#include <utils/image.hpp>
#include <utils/concurrency.hpp>
namespace fonts
{
namespace
{
struct font_data_t
{
std::unordered_map<std::string, game::TTF*> fonts;
std::unordered_map<std::string, std::string> raw_fonts;
};
utils::concurrency::container<font_data_t> font_data;
game::TTF* create_font(const std::string& name, const std::string& data)
{
const auto font = utils::memory::get_allocator()->allocate<game::TTF>();
font->name = utils::memory::get_allocator()->duplicate_string(name);
font->buffer = utils::memory::get_allocator()->duplicate_string(data);
font->len = static_cast<int>(data.size());
font->fontFace = 0;
return font;
}
void free_font(game::TTF* font)
{
utils::memory::get_allocator()->free(font->buffer);
utils::memory::get_allocator()->free(font->name);
utils::memory::get_allocator()->free(font);
}
game::TTF* load_font(const std::string& name)
{
return font_data.access<game::TTF*>([&](font_data_t& data_) -> game::TTF*
{
if (const auto i = data_.fonts.find(name); i != data_.fonts.end())
{
return i->second;
}
std::string data{};
if (const auto i = data_.raw_fonts.find(name); i != data_.raw_fonts.end())
{
data = i->second;
}
if (data.empty() && !filesystem::read_file(name, &data))
{
return nullptr;
}
const auto material = create_font(name, data);
data_.fonts[name] = material;
return material;
});
}
game::TTF* try_load_font(const std::string& name)
{
try
{
return load_font(name);
}
catch (const std::exception& e)
{
game_console::print(game_console::con_type_error, "Failed to load font %s: %s\n", name.data(), e.what());
}
return nullptr;
}
game::TTF* db_find_xasset_header_stub(game::XAssetType type, const char* name, int create_default)
{
auto result = try_load_font(name);
if (result == nullptr)
{
result = game::DB_FindXAssetHeader(type, name, create_default).ttf;
}
return result;
}
}
void add(const std::string& name, const std::string& data)
{
font_data.access([&](font_data_t& data_)
{
data_.raw_fonts[name] = data;
});
}
void clear()
{
font_data.access([&](font_data_t& data_)
{
for (auto& font : data_.fonts)
{
free_font(font.second);
}
data_.fonts.clear();
utils::hook::set<int>(0x14EE3ACB8, 0); // reset registered font count
});
}
class component final : public component_interface
{
public:
void post_unpack() override
{
utils::hook::call(0x140747096, db_find_xasset_header_stub);
}
};
}
REGISTER_COMPONENT(fonts::component)

View File

@ -0,0 +1,7 @@
#pragma once
namespace fonts
{
void add(const std::string& name, const std::string& data);
void clear();
}

View File

@ -1,8 +1,11 @@
#include <std_include.hpp> #include <std_include.hpp>
#include "loader/component_loader.hpp" #include "loader/component_loader.hpp"
#include "game/game.hpp"
#include "images.hpp" #include "images.hpp"
#include "game_console.hpp" #include "game_console.hpp"
#include "filesystem.hpp"
#include "game/game.hpp"
#include <utils/hook.hpp> #include <utils/hook.hpp>
#include <utils/string.hpp> #include <utils/string.hpp>
@ -29,7 +32,7 @@ namespace images
} }
}); });
if (data.empty() && !utils::io::read_file(utils::string::va("images/%s.png", image->name), &data)) if (data.empty() && !filesystem::read_file(utils::string::va("images/%s.png", image->name), &data))
{ {
return {}; return {};
} }

View File

@ -0,0 +1,200 @@
#include <std_include.hpp>
#include "loader/component_loader.hpp"
#include "materials.hpp"
#include "game_console.hpp"
#include "filesystem.hpp"
#include "game/game.hpp"
#include "game/dvars.hpp"
#include <utils/hook.hpp>
#include <utils/memory.hpp>
#include <utils/io.hpp>
#include <utils/string.hpp>
#include <utils/image.hpp>
#include <utils/concurrency.hpp>
namespace materials
{
namespace
{
utils::hook::detour db_material_streaming_fail_hook;
utils::hook::detour material_register_handle_hook;
utils::hook::detour db_get_material_index_hook;
struct material_data_t
{
std::unordered_map<std::string, game::Material*> materials;
std::unordered_map<std::string, std::string> images;
};
char constant_table[0x20] = {};
utils::concurrency::container<material_data_t> material_data;
game::GfxImage* setup_image(game::GfxImage* image, const utils::image& raw_image)
{
image->imageFormat = 0x1000003;
image->resourceSize = -1;
D3D11_SUBRESOURCE_DATA data{};
data.SysMemPitch = raw_image.get_width() * 4;
data.SysMemSlicePitch = data.SysMemPitch * raw_image.get_height();
data.pSysMem = raw_image.get_buffer();
game::Image_Setup(image, raw_image.get_width(), raw_image.get_height(), image->depth, image->numElements,
image->imageFormat, DXGI_FORMAT_R8G8B8A8_UNORM, 0, image->name, &data);
return image;
}
game::Material* create_material(const std::string& name, const std::string& data)
{
const auto white = *reinterpret_cast<game::Material**>(0x141B09208);
const auto material = utils::memory::get_allocator()->allocate<game::Material>();
const auto texture_table = utils::memory::get_allocator()->allocate<game::MaterialTextureDef>();
const auto image = utils::memory::get_allocator()->allocate<game::GfxImage>();
std::memcpy(material, white, sizeof(game::Material));
std::memcpy(texture_table, white->textureTable, sizeof(game::MaterialTextureDef));
std::memcpy(image, white->textureTable->u.image, sizeof(game::GfxImage));
material->constantTable = &constant_table;
material->name = utils::memory::get_allocator()->duplicate_string(name);
image->name = material->name;
material->textureTable = texture_table;
material->textureTable->u.image = setup_image(image, data);
return material;
}
void free_material(game::Material* material)
{
material->textureTable->u.image->textures.___u0.map->Release();
material->textureTable->u.image->textures.shaderView->Release();
utils::memory::get_allocator()->free(material->textureTable->u.image);
utils::memory::get_allocator()->free(material->textureTable);
utils::memory::get_allocator()->free(material->name);
utils::memory::get_allocator()->free(material);
}
game::Material* load_material(const std::string& name)
{
return material_data.access<game::Material*>([&](material_data_t& data_) -> game::Material*
{
if (const auto i = data_.materials.find(name); i != data_.materials.end())
{
return i->second;
}
std::string data{};
if (const auto i = data_.images.find(name); i != data_.images.end())
{
data = i->second;
}
if (data.empty() && !filesystem::read_file(utils::string::va("materials/%s.png", name.data()), &data))
{
data_.materials[name] = nullptr;
return nullptr;
}
const auto material = create_material(name, data);
data_.materials[name] = material;
return material;
});
}
game::Material* try_load_material(const std::string& name)
{
if (name == "white")
{
return nullptr;
}
try
{
return load_material(name);
}
catch (const std::exception& e)
{
game_console::print(game_console::con_type_error, "Failed to load material %s: %s\n", name.data(), e.what());
}
return nullptr;
}
game::Material* material_register_handle_stub(const char* name)
{
auto result = try_load_material(name);
if (result == nullptr)
{
result = material_register_handle_hook.invoke<game::Material*>(name);
}
return result;
}
int db_material_streaming_fail_stub(game::Material* material)
{
if (material->constantTable == &constant_table)
{
return 0;
}
return db_material_streaming_fail_hook.invoke<int>(material);
}
unsigned int db_get_material_index_stub(game::Material* material)
{
if (material->constantTable == &constant_table)
{
return 0;
}
return db_get_material_index_hook.invoke<unsigned int>(material);
}
}
void add(const std::string& name, const std::string& data)
{
material_data.access([&](material_data_t& data_)
{
data_.images[name] = data;
});
}
void clear()
{
material_data.access([&](material_data_t& data_)
{
for (auto& material : data_.materials)
{
if (material.second == nullptr)
{
continue;
}
free_material(material.second);
}
data_.materials.clear();
});
}
class component final : public component_interface
{
public:
void post_unpack() override
{
material_register_handle_hook.create(game::Material_RegisterHandle.get(), material_register_handle_stub);
db_material_streaming_fail_hook.create(0x14041D140, db_material_streaming_fail_stub);
db_get_material_index_hook.create(0x140413BC0, db_get_material_index_stub);
}
};
}
REGISTER_COMPONENT(materials::component)

View File

@ -0,0 +1,7 @@
#pragma once
namespace materials
{
void add(const std::string& name, const std::string& data);
void clear();
}

View File

@ -6,12 +6,35 @@
#include "command.hpp" #include "command.hpp"
#include "game_console.hpp" #include "game_console.hpp"
#include "scheduler.hpp" #include "scheduler.hpp"
#include "filesystem.hpp"
#include "materials.hpp"
#include "fonts.hpp"
#include <utils/hook.hpp> #include <utils/hook.hpp>
#include <utils/io.hpp> #include <utils/io.hpp>
namespace mods namespace mods
{ {
namespace
{
utils::hook::detour db_release_xassets_hook;
void db_release_xassets_stub()
{
materials::clear();
fonts::clear();
db_release_xassets_hook.invoke<void>();
}
void restart()
{
scheduler::once([]()
{
game::Com_Shutdown("");
}, scheduler::pipeline::main);
}
}
class component final : public component_interface class component final : public component_interface
{ {
public: public:
@ -22,6 +45,8 @@ namespace mods
utils::io::create_directory("mods"); utils::io::create_directory("mods");
} }
db_release_xassets_hook.create(0x140416A80, db_release_xassets_stub);
command::add("loadmod", [](const command::params& params) command::add("loadmod", [](const command::params& params)
{ {
if (params.size() < 2) if (params.size() < 2)
@ -30,7 +55,7 @@ namespace mods
return; return;
} }
if (::game::SV_Loaded()) if (!game::Com_InFrontend())
{ {
game_console::print(game_console::con_type_error, "Cannot load mod while in-game!\n"); game_console::print(game_console::con_type_error, "Cannot load mod while in-game!\n");
game::CG_GameMessage(0, "^1Cannot unload mod while in-game!"); game::CG_GameMessage(0, "^1Cannot unload mod while in-game!");
@ -46,12 +71,9 @@ namespace mods
return; return;
} }
filesystem::get_search_paths().insert(path);
game::mod_folder = path; game::mod_folder = path;
restart();
scheduler::once([]()
{
command::execute("lui_restart", true);
}, scheduler::pipeline::renderer);
}); });
command::add("unloadmod", [](const command::params& params) command::add("unloadmod", [](const command::params& params)
@ -62,7 +84,7 @@ namespace mods
return; return;
} }
if (::game::SV_Loaded()) if (!game::Com_InFrontend())
{ {
game_console::print(game_console::con_type_error, "Cannot unload mod while in-game!\n"); game_console::print(game_console::con_type_error, "Cannot unload mod while in-game!\n");
game::CG_GameMessage(0, "^1Cannot unload mod while in-game!"); game::CG_GameMessage(0, "^1Cannot unload mod while in-game!");
@ -70,12 +92,9 @@ namespace mods
} }
game_console::print(game_console::con_type_info, "Unloading mod %s\n", game::mod_folder.data()); game_console::print(game_console::con_type_info, "Unloading mod %s\n", game::mod_folder.data());
filesystem::get_search_paths().erase(game::mod_folder);
game::mod_folder.clear(); game::mod_folder.clear();
restart();
scheduler::once([]()
{
command::execute("lui_restart", true);
}, scheduler::pipeline::renderer);
}); });
} }
}; };

View File

@ -3,6 +3,7 @@
#include "context.hpp" #include "context.hpp"
#include "../../../component/notifies.hpp" #include "../../../component/notifies.hpp"
#include "../../../component/filesystem.hpp"
#include "../execution.hpp" #include "../execution.hpp"
#include <utils/io.hpp> #include <utils/io.hpp>
@ -49,13 +50,9 @@ namespace scripting::lua::engine
load_generic_script(); load_generic_script();
load_scripts("scripts/"); for (const auto& path : filesystem::get_search_paths())
load_scripts("h2-mod/scripts/");
load_scripts("data/scripts/");
if (!game::mod_folder.empty())
{ {
load_scripts(utils::string::va("%s/scripts/", game::mod_folder.data())); load_scripts(path + "/scripts/");
} }
} }

View File

@ -126,11 +126,90 @@ namespace game
// ... // ...
}; };
struct GfxImage;
union MaterialTextureDefInfo
{
GfxImage* image;
void* water;
};
struct MaterialTextureDef
{
unsigned int nameHash;
char nameStart;
char nameEnd;
char samplerState;
char semantic;
MaterialTextureDefInfo u;
};
struct MaterialPass
{
void* vertexShader;
void* vertexDecl;
void* hullShader;
void* domainShader;
void* pixelShader;
char pixelOutputMask;
char perPrimArgCount;
char perObjArgCount;
char stableArgCount;
unsigned __int16 perPrimArgSize;
unsigned __int16 perObjArgSize;
unsigned __int16 stableArgSize;
char zone;
char perPrimConstantBuffer;
char perObjConstantBuffer;
char stableConstantBuffer;
unsigned int customBufferFlags;
char customSamplerFlags;
char precompiledIndex;
char stageConfig;
void* args;
};
struct MaterialTechnique
{
const char* name;
unsigned __int16 flags;
unsigned __int16 passCount;
MaterialPass passArray[1];
};
struct MaterialTechniqueSet
{
const char* name;
unsigned __int16 flags;
char worldVertFormat;
char preDisplacementOnlyCount;
MaterialTechnique* techniques[309];
};
struct GfxStateBits
{
unsigned int loadBits[3];
char zone;
char depthStencilState[11];
char blendState;
char rasterizerState;
};
struct Material struct Material
{ {
const char* name; const char* name;
char __pad0[0x124];
char textureCount;
char __pad1[0xB];
MaterialTechniqueSet* techniqueSet;
MaterialTextureDef* textureTable;
void* constantTable;
GfxStateBits* stateBitsTable;
char __pad2[0x118];
}; };
static_assert(sizeof(Material) == 0x270);
struct point struct point
{ {
float x; float x;
@ -678,6 +757,14 @@ namespace game
const char* buffer; const char* buffer;
}; };
struct TTF
{
const char* name;
int len;
const char* buffer;
int fontFace;
};
union XAssetHeader union XAssetHeader
{ {
void* data; void* data;
@ -687,6 +774,7 @@ namespace game
ScriptFile* scriptfile; ScriptFile* scriptfile;
StringTable* stringTable; StringTable* stringTable;
LuaFile* luaFile; LuaFile* luaFile;
TTF* ttf;
}; };
struct XAsset struct XAsset

View File

@ -27,7 +27,9 @@ namespace game
WEAK symbol<void(int localClientNum, int controllerIndex, const char* text)> Cmd_ExecuteSingleCommand{0x14059ABA0}; WEAK symbol<void(int localClientNum, int controllerIndex, const char* text)> Cmd_ExecuteSingleCommand{0x14059ABA0};
WEAK symbol<void(errorParm code, const char* message, ...)> Com_Error{0x1405A2D80}; WEAK symbol<void(errorParm code, const char* message, ...)> Com_Error{0x1405A2D80};
WEAK symbol<void(char const* finalMessage)> Com_Shutdown{0x1405A62C0};
WEAK symbol<void()> Com_Quit_f{0x1405A50D0}; WEAK symbol<void()> Com_Quit_f{0x1405A50D0};
WEAK symbol<bool()> Com_InFrontend{0x140328BD0};
WEAK symbol<void()> Quit{0x1405A52A0}; WEAK symbol<void()> Quit{0x1405A52A0};
WEAK symbol<void(XAssetType type, void(__cdecl* func)(game::XAssetHeader, void*), const void* inData, bool includeOverride)> WEAK symbol<void(XAssetType type, void(__cdecl* func)(game::XAssetHeader, void*), const void* inData, bool includeOverride)>
@ -117,6 +119,7 @@ namespace game
WEAK symbol<Font_s*(const char* font, int size)> R_RegisterFont{0x140746FE0}; WEAK symbol<Font_s*(const char* font, int size)> R_RegisterFont{0x140746FE0};
WEAK symbol<int(const char* text, int maxChars, Font_s* font)> R_TextWidth{0x1407472A0}; WEAK symbol<int(const char* text, int maxChars, Font_s* font)> R_TextWidth{0x1407472A0};
WEAK symbol<void()> R_SyncRenderThread{0x14076E7D0}; WEAK symbol<void()> R_SyncRenderThread{0x14076E7D0};
WEAK symbol<void()> R_WaitWorkerCmds{0x140794330};
WEAK symbol<void(const void* obj, void* pose, unsigned int entnum, unsigned int renderFxFlags, float* lightingOrigin, WEAK symbol<void(const void* obj, void* pose, unsigned int entnum, unsigned int renderFxFlags, float* lightingOrigin,
float materialTime, __int64 a7, __int64 a8)> R_AddDObjToScene{0x140775C40}; float materialTime, __int64 a7, __int64 a8)> R_AddDObjToScene{0x140775C40};

View File

@ -4,6 +4,7 @@
#include "../../../component/scheduler.hpp" #include "../../../component/scheduler.hpp"
#include "../../../component/ui_scripting.hpp" #include "../../../component/ui_scripting.hpp"
#include "../../../component/filesystem.hpp"
#include <utils/io.hpp> #include <utils/io.hpp>
#include <utils/string.hpp> #include <utils/string.hpp>
@ -73,13 +74,9 @@ namespace ui_scripting::lua::engine
load_code(updater_script); load_code(updater_script);
load_scripts("ui_scripts/"); for (const auto& path : filesystem::get_search_paths())
load_scripts("h2-mod/ui_scripts/");
load_scripts("data/ui_scripts/");
if (!game::mod_folder.empty())
{ {
load_scripts(utils::string::va("%s/ui_scripts/", game::mod_folder.data())); load_scripts(path + "/ui_scripts/");
} }
} }