diff --git a/.gitmodules b/.gitmodules index 11377e21..d9af3664 100644 --- a/.gitmodules +++ b/.gitmodules @@ -16,3 +16,6 @@ [submodule "deps/discord-rpc"] path = deps/discord-rpc url = https://github.com/discord/discord-rpc.git +[submodule "deps/stb"] + path = deps/stb + url = https://github.com/nothings/stb.git diff --git a/deps/premake/stb.lua b/deps/premake/stb.lua new file mode 100644 index 00000000..6f20a983 --- /dev/null +++ b/deps/premake/stb.lua @@ -0,0 +1,19 @@ +stb = { + source = path.join(dependencies.basePath, "stb"), +} + +function stb.import() + stb.includes() +end + +function stb.includes() + includedirs { + stb.source + } +end + +function stb.project() + +end + +table.insert(dependencies, stb) diff --git a/src/component/game_console.cpp b/src/component/game_console.cpp index 9069af40..f6dc5d75 100644 --- a/src/component/game_console.cpp +++ b/src/component/game_console.cpp @@ -239,9 +239,14 @@ namespace game_console con.globals.left_x = con.globals.x; con.globals.auto_complete_choice[0] = 0; + /* game::R_AddCmdDrawTextWithCursor(con.buffer, 0x7FFFFFFF, console_font, 18, con.globals.x, con.globals.y + con.globals.font_height, 1.0f, 1.0f, 0, color_white, 0, con.cursor, '|'); + */ + + game::R_AddCmdDrawText(con.buffer, 0x7FFF, console_font, con.globals.x, + con.globals.y + con.globals.font_height, 1.0f, 1.0f, 0.0f, color_white, 0); // check if using a prefixed '/' or not const auto input = con.buffer[1] && (con.buffer[0] == '/' || con.buffer[0] == '\\') @@ -428,21 +433,16 @@ namespace game_console bool console_char_event(const int localClientNum, const int key) { - if (key == '`' || key == '~') + if (key == '`' || key == '~' || key == '|' || key == '\\') { return false; } - if (key > 255) + if (key > 127) { return true; } - if (key == '|' || key == '\\') - { - return false; - } - if (*game::keyCatchers & 1) { if (key == game::keyNum_t::K_TAB) // tab (auto complete) diff --git a/src/component/images.cpp b/src/component/images.cpp new file mode 100644 index 00000000..cff9ec27 --- /dev/null +++ b/src/component/images.cpp @@ -0,0 +1,137 @@ +#include +#include "loader/component_loader.hpp" +#include "game/game.hpp" +#include "images.hpp" +#include "game_console.hpp" + +#include +#include +#include +#include +#include + +namespace images +{ + namespace + { + utils::hook::detour load_texture_hook; + utils::hook::detour setup_texture_hook; + utils::concurrency::container> overriden_textures; + + static_assert(sizeof(game::GfxImage) == 104); + static_assert(offsetof(game::GfxImage, name) == (sizeof(game::GfxImage) - sizeof(void*))); + static_assert(offsetof(game::GfxImage, pixelData) == 56); + static_assert(offsetof(game::GfxImage, width) == 44); + static_assert(offsetof(game::GfxImage, height) == 46); + static_assert(offsetof(game::GfxImage, depth) == 48); + static_assert(offsetof(game::GfxImage, numElements) == 50); + + std::optional load_image(game::GfxImage* image) + { + std::string data{}; + overriden_textures.access([&](const std::unordered_map& textures) + { + if (const auto i = textures.find(image->name); i != textures.end()) + { + data = i->second; + } + }); + + if (data.empty() && !utils::io::read_file(utils::string::va("images/%s.png", image->name), &data)) + { + return {}; + } + + return { std::move(data) }; + } + + std::optional load_raw_image_from_file(game::GfxImage* image) + { + const auto image_file = load_image(image); + if (!image_file) + { + return {}; + } + + return utils::image(*image_file); + } + + bool load_custom_texture(game::GfxImage* image) + { + auto raw_image = load_raw_image_from_file(image); + if (!raw_image) + { + return false; + } + + 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 true; + } + + std::unordered_map image_list; + + void load_texture_stub(game::GfxImage* image, void* a2, int* a3) + { + if (image_list.find(image->name) == image_list.end()) + { + //printf("Loading: %s\n", image->name); + image_list[image->name] = true; + } + + try + { + if (load_custom_texture(image)) + { + printf("Loaded custom image for: %s\n", image->name); + return; + } + } + catch (std::exception& e) + { + game_console::print(game_console::con_type_error, "Failed to load image %s: %s\n", image->name, e.what()); + } + + load_texture_hook.invoke(image, a2, a3); + } + + int setup_texture_stub(game::GfxImage* image, void* a2, void* a3) + { + if (image->resourceSize == -1) + { + return 0; + } + + return setup_texture_hook.invoke(image, a2, a3); + } + } + + void override_texture(std::string name, std::string data) + { + overriden_textures.access([&](std::unordered_map& textures) + { + textures[std::move(name)] = std::move(data); + }); + } + + class component final : public component_interface + { + public: + void post_unpack() override + { + setup_texture_hook.create(game::base_address + 0x74A390, setup_texture_stub); + load_texture_hook.create(game::base_address + 0x2A7940, load_texture_stub); + } + }; +} + +REGISTER_COMPONENT(images::component) diff --git a/src/component/images.hpp b/src/component/images.hpp new file mode 100644 index 00000000..a1c81344 --- /dev/null +++ b/src/component/images.hpp @@ -0,0 +1,6 @@ +#pragma once + +namespace images +{ + void override_texture(std::string name, std::string data); +} diff --git a/src/game/structs.hpp b/src/game/structs.hpp index 5dbefb9d..e3af8923 100644 --- a/src/game/structs.hpp +++ b/src/game/structs.hpp @@ -1,4 +1,5 @@ #pragma once +#include namespace game { @@ -20,6 +21,36 @@ namespace game char __pad0[13508]; }; + enum $219904913BC1E6DB920C78C8CC0BD8F1 + { + FL_GODMODE = 0x1, + FL_DEMI_GODMODE = 0x2, + FL_NOTARGET = 0x4, + FL_NO_KNOCKBACK = 0x8, + FL_NO_RADIUS_DAMAGE = 0x10, + FL_SUPPORTS_LINKTO = 0x20, + FL_NO_AUTO_ANIM_UPDATE = 0x40, + FL_GRENADE_TOUCH_DAMAGE = 0x80, + FL_STABLE_MISSILES = 0x100, + FL_REPEAT_ANIM_UPDATE = 0x200, + FL_VEHICLE_TARGET = 0x400, + FL_GROUND_ENT = 0x800, + FL_CURSOR_HINT = 0x1000, + FL_MISSILE_ATTRACTOR_OR_REPULSOR = 0x2000, + FL_WEAPON_BEING_GRABBED = 0x4000, + FL_DELETE = 0x8000, + FL_BOUNCE = 0x10000, + FL_MOVER_SLIDE = 0x20000, + FL_MOVING = 0x40000, + FL_DONT_AUTOBOLT_MISSILE_EFFECTS = 0x80000, + FL_DISABLE_MISSILE_STICK = 0x100000, + FL_NO_MELEE_TARGET = 0x2000000, + FL_DYNAMICPATH = 0x8000000, + FL_AUTO_BLOCKPATHS = 0x10000000, + FL_OBSTACLE = 0x20000000, + FL_BADPLACE_VOLUME = 0x80000000, + }; + struct EntityState { char entityNum; @@ -33,7 +64,7 @@ namespace game gclient_s* client; char __pad2[0x4C]; char flags; - //char __pad3[392]; + char __pad3[392]; }; // size = 760 struct Material @@ -757,4 +788,66 @@ namespace game int emissiveTechType; int forceTechType; }; + + struct GfxImageLoadDef + { + char levelCount; + char numElements; + char pad[2]; + int flags; + int format; + int resourceSize; + char data[1]; + }; + + union $3FA29451CE6F1FA138A5ABAB84BE9676 + { + ID3D11Texture1D* linemap; + ID3D11Texture2D* map; + ID3D11Texture3D* volmap; + ID3D11Texture2D* cubemap; + GfxImageLoadDef* loadDef; + }; + + struct GfxTexture + { + $3FA29451CE6F1FA138A5ABAB84BE9676 ___u0; + ID3D11ShaderResourceView* shaderView; + ID3D11ShaderResourceView* shaderViewAlternate; + }; + + struct Picmip + { + char platform[2]; + }; + + struct CardMemory + { + int platform[2]; + }; + + struct GfxImage + { + GfxTexture textures; + int flags; + int imageFormat; + int resourceSize; + char mapType; + char semantic; + char category; + char flags2; + Picmip picmip; + char track; + //CardMemory cardMemory; + unsigned short width; + unsigned short height; + unsigned short depth; + unsigned short numElements; + char pad3[4]; + void* pixelData; + //GfxImageLoadDef *loadDef; + uint64_t streams[4]; + const char* name; + }; + } \ No newline at end of file diff --git a/src/game/symbols.hpp b/src/game/symbols.hpp index 77569c50..2346e01b 100644 --- a/src/game/symbols.hpp +++ b/src/game/symbols.hpp @@ -22,6 +22,9 @@ namespace game DB_EnumXAssets_Internal{0x4129F0}; WEAK symbol DB_GetXAssetName{0x3E4090}; WEAK symbol DB_LoadXAssets{0x414FF0}; + WEAK symbol DB_FindXAssetHeader{0x412F60}; + WEAK symbol DB_GetRawFileLen{0x413D80}; + WEAK symbol DB_GetRawBuffer{0x413C40}; WEAK symbol Dvar_FindVar{0x618F90}; WEAK symbol Dvar_GetCombinedString{0x5A75D0}; @@ -51,6 +54,9 @@ namespace game WEAK symbol I_CleanStr{0x620660}; + WEAK symbol Image_Setup{0x74B2A0}; + WEAK symbol LUI_OpenMenu{0x5F0EE0}; WEAK symbol Material_RegisterHandle{0x759BA0}; @@ -70,6 +76,8 @@ namespace game WEAK symbol R_RegisterFont{0x746FE0}; WEAK symbol R_TextWidth{0x7472A0}; WEAK symbol R_SyncRenderThread{0x76E7D0}; + WEAK symbol R_AddDObjToScene{0x775C40}; WEAK symbol ScrPlace_GetViewPlacement{0x3E16A0}; diff --git a/src/stdinc.hpp b/src/stdinc.hpp index a8f96e08..d6b98fa7 100644 --- a/src/stdinc.hpp +++ b/src/stdinc.hpp @@ -22,6 +22,7 @@ #include #include #include +#include #ifdef max #undef max diff --git a/src/utils/image.cpp b/src/utils/image.cpp new file mode 100644 index 00000000..dccd160e --- /dev/null +++ b/src/utils/image.cpp @@ -0,0 +1,53 @@ +#include +#include "image.hpp" + +#define STB_IMAGE_IMPLEMENTATION +#include + +namespace utils +{ + image::image(const std::string& image_data) + { + int channels{}; + auto* rgb_image = stbi_load_from_memory(reinterpret_cast(image_data.data()), static_cast(image_data.size()), &this->width, &this->height, &channels, 4); + if(!rgb_image) + { + throw std::runtime_error("Unable to load image"); + } + + auto _ = gsl::finally([rgb_image]() + { + stbi_image_free(rgb_image); + }); + + const auto size = this->width * this->height * 4; + this->data.resize(size); + + std::memmove(this->data.data(), rgb_image, size); + } + + int image::get_width() const + { + return this->width; + } + + int image::get_height() const + { + return this->height; + } + + const void* image::get_buffer() const + { + return this->data.data(); + } + + size_t image::get_size() const + { + return this->data.size(); + } + + const std::string& image::get_data() const + { + return this->data; + } +} diff --git a/src/utils/image.hpp b/src/utils/image.hpp new file mode 100644 index 00000000..a617df76 --- /dev/null +++ b/src/utils/image.hpp @@ -0,0 +1,24 @@ +#pragma once + +#include + +namespace utils +{ + class image + { + public: + image(const std::string& data); + + int get_width() const; + int get_height() const; + const void* get_buffer() const; + size_t get_size() const; + + const std::string& get_data() const; + + private: + int width{}; + int height{}; + std::string data{}; + }; +}