Custom loadscreen dvars

This commit is contained in:
Federico Cecchetto 2022-07-18 19:20:09 +02:00
parent 899f8d27a6
commit b476859f09
9 changed files with 363 additions and 9 deletions

View File

@ -0,0 +1,246 @@
#include <std_include.hpp>
#include "loader/component_loader.hpp"
#include "game/game.hpp"
#include "game/dvars.hpp"
#include "scheduler.hpp"
#include "loadscreen.hpp"
#include <utils/hook.hpp>
#include <utils/io.hpp>
namespace loadscreen
{
namespace
{
game::dvar_t* cl_disable_map_movies = nullptr;
game::dvar_t* cl_loadscreen_image = nullptr;
game::dvar_t* cl_loadscreen_title = nullptr;
game::dvar_t* cl_loadscreen_desc = nullptr;
game::dvar_t* cl_loadscreen_obj = nullptr;
game::dvar_t* cl_loadscreen_obj_icon = nullptr;
utils::hook::detour ui_draw_loadbar_hook;
float white_color[4] = {0.8f, 0.8f, 0.8f, 1.f};
float text_color[4] = {0.65f, 0.65f, 0.65f, 1.f};
float gray_color[4] = {0.2f, 0.2f, 0.2f, 1.f};
float icon_yellow_color[4] = {0.86f, 0.81f, 0.34f, 1.f};
float icon_grey_color[4] = {0.6f, 0.6f, 0.6f, 1.f};
void draw_loadscreen_image()
{
const auto material = game::Material_RegisterHandle(cl_loadscreen_image->current.string);
const auto placement = game::ScrPlace_GetViewPlacement();
game::rectangle rect{};
rect.p0.x = 0;
rect.p0.y = 0;
rect.p0.f2 = 0.f;
rect.p0.f3 = 1.f;
rect.p1.x = 0 + placement->realViewportSize[0];
rect.p1.y = 0;
rect.p1.f2 = 0.f;
rect.p1.f3 = 1.f;
rect.p2.x = 0 + placement->realViewportSize[0];
rect.p2.y = 0 + placement->realViewportSize[1];
rect.p2.f2 = 0.f;
rect.p2.f3 = 1.f;
rect.p3.x = 0;
rect.p3.y = 0 + placement->realViewportSize[1];
rect.p3.f2 = 0.f;
rect.p3.f3 = 1.f;
game::R_DrawRectangle(&rect, 0.f, 0.f, 1.f, 1.f,
white_color, material);
}
void draw_loadscreen_progress_bar()
{
const auto fraction = utils::hook::invoke<float>(0x140287E30);
const auto w = 290.f;
const auto w_progress = w * fraction;
const auto material = game::Material_RegisterHandle("white");
const auto placement = game::ScrPlace_GetViewPlacement();
game::CL_DrawStretchPic(placement, -20, 290, w, 3, 0, 0, 0.0f, 0.0f, 1.0f, 1.0f, gray_color, material);
game::CL_DrawStretchPic(placement, -20, 290, w_progress, 3, 0, 0, 0.0f, 0.0f, 1.0f, 1.0f, white_color, material);
}
void draw_loadscreen_title()
{
auto x = -20.f;
auto y = 290.f;
auto h = 24.f;
auto w = 0.f;
const auto placement = game::ScrPlace_GetViewPlacement();
game::ScrPlace_ApplyRect(placement, &x, &y, &w, &h, 0, 0);
const auto font = game::R_RegisterFont("fonts/default.otf", static_cast<int>(h));
game::R_AddCmdDrawText(cl_loadscreen_title->current.string, 0x7FFFFFFF, font, x, y, 1.f, 1.f, 0.f, text_color, 0);
}
void draw_loadscreen_desc()
{
const auto font = game::R_RegisterFont("fonts/default.otf", 20);
const auto placement = game::ScrPlace_GetViewPlacement();
const auto text = cl_loadscreen_desc->current.string;
game::rectDef_s rect{};
rect.x = 0;
rect.y = 0;
rect.w = 290;
rect.horzAlign = 0;
rect.vertAlign = 0;
game::rectDef_s text_rect{};
game::UI_DrawWrappedText(placement, text, &rect, font, -20, 310, 0.25f, text_color, 0, 0, &text_rect, 0);
}
void draw_loadscreen_objective_icons()
{
if (*cl_loadscreen_obj_icon->current.string == 0)
{
return;
}
const auto material = game::Material_RegisterHandle(cl_loadscreen_obj_icon->current.string);
const auto placement = game::ScrPlace_GetViewPlacement();
const auto w = 15.f;
const auto base_y = 365.f;
const auto base_x = -20.f;
for (auto row = 0; row < 3; row++)
{
for (auto column = 0; column < 3; column++)
{
auto x = base_x + column * w;
auto y = base_y + row * w + 2;
const auto color = column <= row ? icon_yellow_color : icon_grey_color;
game::CL_DrawStretchPic(placement, x, y, w, w, 0, 0, 0.f, 0.f, 1.f, 1.f, color, material);
}
}
}
void draw_loadscreen_objective()
{
if (*cl_loadscreen_obj->current.string == 0)
{
return;
}
draw_loadscreen_objective_icons();
const auto font = game::R_RegisterFont("fonts/default.otf", 20);
const auto placement = game::ScrPlace_GetViewPlacement();
const auto text = cl_loadscreen_obj->current.string;
game::rectDef_s rect{};
rect.x = 0;
rect.y = 0;
rect.w = 290.f;
rect.horzAlign = 0;
rect.vertAlign = 0;
game::rectDef_s text_rect{};
game::UI_DrawWrappedText(placement, text, &rect, font, 30.f, 365.f + 17.5f, 0.25f, text_color, 0, 0, &text_rect, 0);
}
void draw_loadscreen()
{
if (!cl_disable_map_movies->current.enabled)
{
return;
}
if (cl_loadscreen_image == nullptr || cl_loadscreen_title == nullptr ||
cl_loadscreen_desc == nullptr || *cl_loadscreen_image->current.string == 0)
{
return;
}
draw_loadscreen_image();
draw_loadscreen_progress_bar();
draw_loadscreen_title();
draw_loadscreen_desc();
draw_loadscreen_objective();
}
bool in_loadscreen()
{
return *reinterpret_cast<int*>(0x14203F3C4) == 4;
}
void ui_set_active_menu_stub(utils::hook::assembler& a)
{
const auto player_start = a.newLabel();
a.mov(rax, qword_ptr(reinterpret_cast<uint64_t>(&cl_disable_map_movies)));
a.mov(al, byte_ptr(rax, 0x10));
a.cmp(al, 1);
a.jz(player_start);
a.mov(rax, qword_ptr(static_cast<uint64_t>(0x14BE6EA10)));
a.mov(al, byte_ptr(rax, 0x10));
a.cmp(al, 1);
a.jz(player_start);
a.jmp(0x1405F4701);
a.bind(player_start);
a.call(0x1405F1B00);
a.jmp(0x1405F44F2);
}
}
void clear()
{
game::Dvar_Reset(cl_disable_map_movies, game::DVAR_SOURCE_INTERNAL);
game::Dvar_Reset(cl_loadscreen_image, game::DVAR_SOURCE_INTERNAL);
game::Dvar_Reset(cl_loadscreen_title, game::DVAR_SOURCE_INTERNAL);
game::Dvar_Reset(cl_loadscreen_desc, game::DVAR_SOURCE_INTERNAL);
}
class component final : public component_interface
{
public:
void post_unpack() override
{
// not registered, used in CL_StartLoading
cl_disable_map_movies = dvars::register_bool("cl_disableMapMovies", false, 0, "Disable map loading videos");
// auto start the game if cl_disableMapMovies is enabled
utils::hook::jump(0x1405F46EA, utils::hook::assemble(ui_set_active_menu_stub), true);
scheduler::once([]()
{
cl_loadscreen_image = dvars::register_string("cl_loadscreenImage", "", 0, "Loadscreen background image");
cl_loadscreen_title = dvars::register_string("cl_loadscreenTitle", "", 0, "Loadscreen mission title");
cl_loadscreen_desc = dvars::register_string("cl_loadscreenDesc", "", 0, "Loadscreen mission description");
cl_loadscreen_obj = dvars::register_string("cl_loadscreenObj", "", 0, "Loadscreen mission objective");
cl_loadscreen_obj_icon = dvars::register_string("cl_loadscreenObjIcon", "", 0, "Loadscreen mission objective icon");
}, scheduler::pipeline::main);
scheduler::loop([]()
{
if (in_loadscreen())
{
draw_loadscreen();
}
}, scheduler::pipeline::renderer);
}
};
}
REGISTER_COMPONENT(loadscreen::component)

View File

@ -0,0 +1,6 @@
#pragma once
namespace loadscreen
{
void clear();
}

View File

@ -1,8 +1,10 @@
#include <std_include.hpp>
#include "loader/component_loader.hpp"
#include "game/game.hpp"
#include "console.hpp"
#include "loadscreen.hpp"
#include "game/game.hpp"
#include "game/dvars.hpp"
#include <utils/hook.hpp>
@ -60,6 +62,8 @@ namespace logger
console::error("Error: %s\n", buffer);
}
loadscreen::clear();
com_error_hook.invoke<void>(error, "%s", buffer);
}

View File

@ -4,6 +4,7 @@
#include "materials.hpp"
#include "console.hpp"
#include "filesystem.hpp"
#include "command.hpp"
#include "game/game.hpp"
#include "game/dvars.hpp"
@ -49,7 +50,7 @@ namespace materials
return image;
}
game::Material* create_material(const std::string& name, const std::string& data)
game::Material* create_material(const std::string& name, const utils::image& raw_image)
{
const auto white = *reinterpret_cast<game::Material**>(0x141B09208);
@ -66,7 +67,7 @@ namespace materials
image->name = material->name;
material->textureTable = texture_table;
material->textureTable->u.image = setup_image(image, data);
material->textureTable->u.image = setup_image(image, raw_image);
return material;
}
@ -96,16 +97,37 @@ namespace materials
data = i->second;
}
if (data.empty() && !filesystem::read_file(utils::string::va("materials/%s.png", name.data()), &data))
if (!data.empty())
{
data_.materials[name] = nullptr;
return nullptr;
const auto material = create_material(name, data);
data_.materials[name] = material;
return material;
}
const auto material = create_material(name, data);
data_.materials[name] = material;
if (filesystem::read_file(utils::string::va("materials/%s.stbi_img", name.data()), &data))
{
const auto buffer = data.data();
const auto width = *reinterpret_cast<int*>(buffer);
const auto height = *reinterpret_cast<int*>(buffer + 4);
const auto image_data = std::string(reinterpret_cast<char*>(buffer + 8), data.size() - 8);
return material;
const auto image = utils::image(image_data, width, height);
const auto material = create_material(name, image);
data_.materials[name] = material;
return material;
}
if (filesystem::read_file(utils::string::va("materials/%s.png", name.data()), &data))
{
const auto material = create_material(name, data);
data_.materials[name] = material;
return material;
}
data_.materials[name] = nullptr;
return nullptr;
});
}
@ -193,6 +215,54 @@ namespace materials
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);
command::add("preloadImage", [](const command::params& params)
{
if (params.size() < 2)
{
return;
}
const auto image_name = params.join(1);
if (!utils::io::file_exists(image_name))
{
console::error("Image file not found\n");
return;
}
const auto data = utils::io::read_file(image_name);
try
{
const auto image = utils::image{ data };
const auto last_of = image_name.find_last_of('.');
const auto new_name = image_name.substr(0, last_of) + ".stbi_img";
auto width = image.get_width();
auto height = image.get_height();
auto size = image.get_size();
/*
int width;
int height;
char* data;
*/
std::string buffer{};
buffer.append(reinterpret_cast<char*>(&width), 4);
buffer.append(reinterpret_cast<char*>(&height), 4);
buffer.append(image.get_data());
utils::io::write_file(new_name, buffer, false);
console::info("Image saved to %s\n", new_name.data());
}
catch (const std::exception& e)
{
console::error("Error processing image: %s\n", e.what());
}
});
}
};
}

View File

@ -12,6 +12,7 @@
#include "mods.hpp"
#include "mapents.hpp"
#include "localized_strings.hpp"
#include "loadscreen.hpp"
#include <utils/hook.hpp>
#include <utils/io.hpp>
@ -32,6 +33,7 @@ namespace mods
materials::clear();
fonts::clear();
mapents::clear_dvars();
loadscreen::clear();
}
mapents::clear();

View File

@ -1211,6 +1211,16 @@ namespace game
char __pad0[0x8];
};
struct rectDef_s
{
float x;
float y;
float w;
float h;
int horzAlign;
int vertAlign;
};
namespace hks
{
struct lua_State;

View File

@ -23,6 +23,9 @@ namespace game
WEAK symbol<char*(const unsigned int weapon,
bool isAlternate, char* outputBuffer, int bufferLen)> CG_GetWeaponDisplayName{0x1403B9210};
WEAK symbol<void(ScreenPlacement* place, float x, float y, float w, float h, int horzAlign, int vertAlign,
float t0, float s0, float t1, float s1, float* color, Material* material)> CL_DrawStretchPic{0x1403C9570};
WEAK symbol<void(const char* cmdName, void(), cmd_function_s* allocedCmd)> Cmd_AddCommandInternal{0x14059A5F0};
WEAK symbol<void(int localClientNum, int controllerIndex, const char* text)> Cmd_ExecuteSingleCommand{0x14059ABA0};
@ -58,6 +61,7 @@ namespace game
WEAK symbol<void(int hash, const char* name, const char* buffer)> Dvar_SetCommand{0x14061A5C0};
WEAK symbol<void(const char* dvarName, const char* string, DvarSetSource source)> Dvar_SetFromStringFromSource{0x14061A910};
WEAK symbol<void(const dvar_t* dvar, const char* value)> Dvar_SetString{0x14061ABF0};
WEAK symbol<void(const dvar_t* dvar, DvarSetSource source)> Dvar_Reset{0x140619FE0};
WEAK symbol<int(const char* fname)> generateHashValue{0x140343D20};
@ -143,6 +147,8 @@ namespace game
WEAK symbol<ScreenPlacement*()> ScrPlace_GetViewPlacement{0x1403E16A0};
WEAK symbol<ScreenPlacement*()> ScrPlace_GetView{0x1403E1660};
WEAK symbol<void(ScreenPlacement* scrPlace, float* x, float* y, float* w, float* h,
int horzAlign, int vertAlign)> ScrPlace_ApplyRect{0x1403E0BF0};
WEAK symbol<const char*(scr_string_t stringValue)> SL_ConvertToString{0x1405BFBB0};
WEAK symbol<scr_string_t(const char* str, unsigned int user)> SL_GetString{0x1405C0170};
@ -162,6 +168,8 @@ namespace game
WEAK symbol<const char*(const char* string)> UI_SafeTranslateString{0x1405A2930};
WEAK symbol<int(int localClientNum, const char* sound)> UI_PlayLocalSoundAlias{0x140606080};
WEAK symbol<void(ScreenPlacement* scrPlace, const char* text, rectDef_s* rect, Font_s* font, float x, float y,
float scale, const float* color, int style, int textAlignMode, rectDef_s* textRect, char a12)> UI_DrawWrappedText{0x1406055E0};
WEAK symbol<void(pmove_t* move, trace_t*, const float*, const float*,
const Bounds*, int, int)> PM_playerTrace{0x14068F0A0};

View File

@ -26,6 +26,13 @@ namespace utils
std::memmove(this->data.data(), rgb_image, size);
}
image::image(const std::string& data_, int width_, int height_)
: data(data_)
, width(width_)
, height(height_)
{
}
int image::get_width() const
{
return this->width;

View File

@ -8,6 +8,7 @@ namespace utils
{
public:
image(const std::string& data);
image(const std::string& data_, int width_, int height_);
int get_width() const;
int get_height() const;