Console improvement & printing
s1 command system"needs work"
This commit is contained in:
Skull 2022-02-04 23:17:56 +02:00
parent a326a70a7f
commit 1b91a2deac
9 changed files with 720 additions and 377 deletions

View File

@ -1,13 +1,10 @@
#include <std_include.hpp> #include <std_include.hpp>
#include "loader/component_loader.hpp" #include "loader/component_loader.hpp"
#include "game/game.hpp"
#include "game/dvars.hpp"
#include "command.hpp" #include "command.hpp"
#include "console.hpp" #include "console.hpp"
#include "game_console.hpp" #include "game_console.hpp"
#include "game/game.hpp"
#include <utils/hook.hpp> #include <utils/hook.hpp>
#include <utils/string.hpp> #include <utils/string.hpp>
@ -18,10 +15,9 @@ namespace command
{ {
namespace namespace
{ {
utils::hook::detour dvar_command_hook; utils::hook::detour client_command_hook;
std::unordered_map<std::string, std::function<void(params&)>> handlers; std::unordered_map<std::string, std::function<void(params&)>> handlers;
std::unordered_map<std::string, std::function<void(int, params_sv&)>> handlers_sv; std::unordered_map<std::string, std::function<void(int, params_sv&)>> handlers_sv;
void main_handler() void main_handler()
@ -45,53 +41,78 @@ namespace command
handlers_sv[command](client_num, params); handlers_sv[command](client_num, params);
} }
dvar_command_hook.invoke<void>(client_num, a2); client_command_hook.invoke<void>(client_num, a2);
} }
void enum_assets(const game::XAssetType type, const std::function<void(game::XAssetHeader)>& callback, const bool includeOverride) // Shamelessly stolen from Quake3
// https://github.com/id-Software/Quake-III-Arena/blob/dbe4ddb10315479fc00086f08e25d968b4b43c49/code/qcommon/common.c#L364
void parse_command_line()
{ {
game::DB_EnumXAssets_Internal(type, static_cast<void(*)(game::XAssetHeader, void*)>([](game::XAssetHeader header, void* data) static auto parsed = false;
if (parsed)
{ {
const auto& cb = *static_cast<const std::function<void(game::XAssetHeader)>*>(data); return;
cb(header);
}), &callback, includeOverride);
} }
game::dvar_t* dvar_command_stub() static std::string comand_line_buffer = GetCommandLineA();
{ auto* command_line = comand_line_buffer.data();
const params args;
if (args.size() <= 0) auto& com_num_console_lines = *reinterpret_cast<int*>(0x142623FB4); //H1(1.4)
auto* com_console_lines = reinterpret_cast<char**>(0x142623FC0); //H1(1.4)
auto inq = false;
com_console_lines[0] = command_line;
com_num_console_lines = 0;
while (*command_line)
{ {
return 0; if (*command_line == '"')
{
inq = !inq;
}
// look for a + separating character
// if commandLine came from a file, we might have real line seperators
if ((*command_line == '+' && !inq) || *command_line == '\n' || *command_line == '\r')
{
if (com_num_console_lines == 0x20) // MAX_CONSOLE_LINES
{
break;
}
com_console_lines[com_num_console_lines] = command_line + 1;
com_num_console_lines++;
*command_line = '\0';
}
command_line++;
}
parsed = true;
} }
const auto dvar = game::Dvar_FindVar(args[0]); void parse_commandline_stub()
if (dvar)
{ {
if (args.size() == 1) parse_command_line();
{ reinterpret_cast<void(*)()>(0x1400D8210)(); // mwr: test
const auto current = game::Dvar_ValueToString(dvar, dvar->current, 0);
const auto reset = game::Dvar_ValueToString(dvar, dvar->reset, 0);
game_console::print(game_console::con_type_info, "\"%s\" is: \"%s\" default: \"%s\" hash: %i",
args[0], current, reset, dvar->hash);
game_console::print(game_console::con_type_info, " %s\n",
dvars::dvar_get_domain(dvar->type, dvar->domain).data());
} }
//else
//{
// char command[0x1000] = { 0 };
// game::Dvar_GetCombinedString(command, 1);
// game::Dvar_SetCommand(dvar->name, command);
//}
return dvar;
} }
return 0; void read_startup_variable(const std::string& dvar)
{
// parse the commandline if it's not parsed
parse_command_line();
auto& com_num_console_lines = *reinterpret_cast<int*>(0x142623FB4); //H1(1.4)
auto* com_console_lines = reinterpret_cast<char**>(0x142623FC0); //H1(1.4)
for (int i = 0; i < com_num_console_lines; i++)
{
game::Cmd_TokenizeString(com_console_lines[i]);
// only +set dvar value
if (game::Cmd_Argc() >= 3 && game::Cmd_Argv(0) == "set"s && game::Cmd_Argv(1) == dvar)
{
game::Dvar_SetCommand(game::Cmd_Argv(1), game::Cmd_Argv(2));
}
game::Cmd_EndTokenizeString();
} }
} }
@ -164,8 +185,16 @@ namespace command
game::Cmd_AddCommandInternal(name, callback, utils::memory::get_allocator()->allocate<game::cmd_function_s>()); game::Cmd_AddCommandInternal(name, callback, utils::memory::get_allocator()->allocate<game::cmd_function_s>());
} }
void add_test(const char* name, void (*callback)())
{
static game::cmd_function_s cmd_test;
return game::Cmd_AddCommandInternal(name, callback, &cmd_test);
}
void add(const char* name, const std::function<void(const params&)>& callback) void add(const char* name, const std::function<void(const params&)>& callback)
{ {
static game::cmd_function_s cmd_test;
const auto command = utils::string::to_lower(name); const auto command = utils::string::to_lower(name);
if (handlers.find(command) == handlers.end()) if (handlers.find(command) == handlers.end())
@ -207,73 +236,13 @@ namespace command
} }
} }
void parse_command_line() void enum_assets(const game::XAssetType type, const std::function<void(game::XAssetHeader)>& callback, const bool includeOverride)
{ {
static auto parsed = false; game::DB_EnumXAssets_Internal(type, static_cast<void(*)(game::XAssetHeader, void*)>([](game::XAssetHeader header, void* data)
if (parsed)
{ {
return; const auto& cb = *static_cast<const std::function<void(game::XAssetHeader)>*>(data);
} cb(header);
}), &callback, includeOverride);
static std::string comand_line_buffer = GetCommandLineA();
auto* command_line = comand_line_buffer.data();
auto& com_num_console_lines = *reinterpret_cast<int*>(0x142623FB4);
auto* com_console_lines = reinterpret_cast<char**>(0x142623FC0);
auto inq = false;
com_console_lines[0] = command_line;
com_num_console_lines = 0;
while (*command_line)
{
if (*command_line == '"')
{
inq = !inq;
}
// look for a + separating character
// if commandLine came from a file, we might have real line seperators
if ((*command_line == '+' && !inq) || *command_line == '\n' || *command_line == '\r')
{
if (com_num_console_lines == 0x20) // MAX_CONSOLE_LINES
{
break;
}
com_console_lines[com_num_console_lines] = command_line + 1;
com_num_console_lines++;
*command_line = '\0';
}
command_line++;
}
parsed = true;
}
void parse_commandline_stub()
{
parse_command_line();
reinterpret_cast<void(*)()>(0x1400D8210)(); // mwr: test
}
void read_startup_variable(const std::string& dvar)
{
// parse the commandline if it's not parsed
parse_command_line();
auto& com_num_console_lines = *reinterpret_cast<int*>(0x142623FB4);
auto* com_console_lines = reinterpret_cast<char**>(0x142623FC0);
for (int i = 0; i < com_num_console_lines; i++)
{
game::Cmd_TokenizeString(com_console_lines[i]);
// only +set dvar value
if (game::Cmd_Argc() >= 3 && game::Cmd_Argv(0) == "set"s && game::Cmd_Argv(1) == dvar)
{
game::Dvar_SetCommand(game::Cmd_Argv(1), game::Cmd_Argv(2));
}
game::Cmd_EndTokenizeString();
}
} }
class component final : public component_interface class component final : public component_interface
@ -281,136 +250,445 @@ namespace command
public: public:
void post_unpack() override void post_unpack() override
{ {
//utils::hook::jump(game::base_address + 0x000000, dvar_command_stub, true); // H1 if (game::environment::is_sp())
static game::cmd_function_s cmd_test;
game::Cmd_AddCommandInternal("quit", game::Com_Quit_f, &cmd_test);
/*game::Cmd_AddCommandInternal("connect", []() {
}, game::cmd_function_s);*/
//add_raw("quit", main_handler);
//add("quit", game::Com_Quit_f);
//add("startmap", [](const params& params)
//{
// const auto map = params.get(1);
// const auto exists = utils::hook::invoke<bool>(game::base_address + 0x412B50, map, 0);
// if (!exists)
// {
// game_console::print(game_console::con_type_error, "map '%s' not found\n", map);
// return;
// }
// // SV_SpawnServer
// utils::hook::invoke<void>(game::base_address + 0x6B3AA0, map, 0, 0, 0, 0);
//});
/*add("say", [](const params& params)
{ {
chat::print(params.join(1)); add_commands_sp();
}
else
{
utils::hook::call(0x1400D728F, &parse_commandline_stub); // MWR TEST
add_commands_mp();
}
add_commands_generic();
}
private:
static void add_commands_generic()
{
add_test("quit", game::Com_Quit_f);
add("quit_hard", utils::nt::raise_hard_exception);
add("crash", []()
{
*reinterpret_cast<int*>(1) = 0;
});
/*add("consoleList", [](const params& params)
{
const std::string input = params.get(1);
std::vector<std::string> matches;
game_console::find_matches(input, matches, false);
for (auto& match : matches)
{
auto* dvar = game::Dvar_FindVar(match.c_str());
if (!dvar)
{
console::info("[CMD]\t %s\n", match.c_str());
}
else
{
console::info("[DVAR]\t%s \"%s\"\n", match.c_str(), game::Dvar_ValueToString(dvar, dvar->current, 0));
}
}
console::info("Total %i matches\n", matches.size());
});
add("dvarDump", [](const params& argument)
{
console::info("================================ DVAR DUMP ========================================\n");
std::string filename;
if (argument.size() == 2)
{
filename = "s1x/";
filename.append(argument[1]);
if (!filename.ends_with(".txt"))
{
filename.append(".txt");
}
}
for (auto i = 0; i < *game::dvarCount; i++)
{
const auto dvar = game::sortedDvars[i];
if (dvar)
{
if (!filename.empty())
{
const auto line = std::format("{} \"{}\"\r\n", dvar->hash,
game::Dvar_ValueToString(dvar, dvar->current, 0));
utils::io::write_file(filename, line, i != 0);
}
console::info("%s \"%s\"\n", dvar->hash,
game::Dvar_ValueToString(dvar, dvar->current, 0));
}
}
console::info("\n%i dvars\n", *game::dvarCount);
console::info("================================ END DVAR DUMP ====================================\n");
});
add("commandDump", [](const params& argument)
{
console::info("================================ COMMAND DUMP =====================================\n");
game::cmd_function_s* cmd = (*game::cmd_functions);
std::string filename;
if (argument.size() == 2)
{
filename = "s1x/";
filename.append(argument[1]);
if (!filename.ends_with(".txt"))
{
filename.append(".txt");
}
}
int i = 0;
while (cmd)
{
if (cmd->name)
{
if (!filename.empty())
{
const auto line = std::format("{}\r\n", cmd->name);
utils::io::write_file(filename, line, i != 0);
}
console::info("%s\n", cmd->name);
i++;
}
cmd = cmd->next;
}
console::info("\n%i commands\n", i);
console::info("================================ END COMMAND DUMP =================================\n");
});
add("listassetpool", [](const params& params)
{
if (params.size() < 2)
{
console::info("listassetpool <poolnumber> [filter]: list all the assets in the specified pool\n");
for (auto i = 0; i < game::XAssetType::ASSET_TYPE_COUNT; i++)
{
console::info("%d %s\n", i, game::g_assetNames[i]);
}
}
else
{
const auto type = static_cast<game::XAssetType>(atoi(params.get(1)));
if (type < 0 || type >= game::XAssetType::ASSET_TYPE_COUNT)
{
console::error("Invalid pool passed must be between [%d, %d]\n", 0, game::XAssetType::ASSET_TYPE_COUNT - 1);
return;
}
console::info("Listing assets in pool %s\n", game::g_assetNames[type]);
const std::string filter = params.get(2);
enum_assets(type, [type, filter](const game::XAssetHeader header)
{
const auto asset = game::XAsset{ type, header };
const auto* const asset_name = game::DB_GetXAssetName(&asset);
//const auto entry = game::DB_FindXAssetEntry(type, asset_name);
//TODO: display which zone the asset is from
if (!filter.empty() && !game_console::match_compare(filter, asset_name, false))
{
return;
}
console::info("%s\n", asset_name);
}, true);
}
});
add("vstr", [](const params& params)
{
if (params.size() < 2)
{
console::info("vstr <variablename> : execute a variable command\n");
return;
}
const auto* dvarName = params.get(1);
const auto* dvar = game::Dvar_FindVar(dvarName);
if (dvar == nullptr)
{
console::info("%s doesn't exist\n", dvarName);
return;
}
if (dvar->type != game::dvar_type::string
&& dvar->type != game::dvar_type::enumeration)
{
console::info("%s is not a string-based dvar\n", dvar->hash);
return;
}
execute(dvar->current.string);
});*/ });*/
}
//add("listassetpool", [](const params& params) static void add_commands_sp()
//{ {
// if (params.size() < 2) add("god", []()
// {
// game_console::print(game_console::con_type_info, "listassetpool <poolnumber>: list all the assets in the specified pool\n");
// for (auto i = 0; i < game::XAssetType::ASSET_TYPE_COUNT; i++)
// {
// game_console::print(game_console::con_type_info, "%d %s\n", i, game::g_assetNames[i]);
// }
// }
// else
// {
// const auto type = static_cast<game::XAssetType>(atoi(params.get(1)));
// if (type < 0 || type >= game::XAssetType::ASSET_TYPE_COUNT)
// {
// game_console::print(game_console::con_type_info, "Invalid pool passed must be between [%d, %d]\n", 0, game::XAssetType::ASSET_TYPE_COUNT - 1);
// return;
// }
// game_console::print(game_console::con_type_info, "Listing assets in pool %s\n", game::g_assetNames[type]);
// enum_assets(type, [type](const game::XAssetHeader header)
// {
// const auto asset = game::XAsset{type, header};
// const auto* const asset_name = game::DB_GetXAssetName(&asset);
// //const auto entry = game::DB_FindXAssetEntry(type, asset_name);
// //TODO: display which zone the asset is from
// game_console::print(game_console::con_type_info, "%s\n", asset_name);
// }, true);
// }
//});
//add("commandDump", []()
//{
// printf("======== Start command dump =========\n");
// game::cmd_function_s* cmd = (*game::cmd_functions);
// while (cmd)
// {
// if (cmd->name)
// {
// game_console::print(game_console::con_type_info, "%s\n", cmd->name);
// }
// cmd = cmd->next;
// }
// printf("======== End command dump =========\n");
//});
/*add("god", []()
{ {
if (!game::SV_Loaded()) if (!game::SV_Loaded())
{ {
return; return;
} }
game::mp::g_entities[0].flags ^= game::FL_GODMODE; game::sp::g_entities[0].flags ^= 1;
game::CG_GameMessage(0, utils::string::va("godmode %s", game::CG_GameMessage(0, utils::string::va("godmode %s",
game::g_entities[0].flags & game::FL_GODMODE game::sp::g_entities[0].flags & 1
? "^2on" ? "^2on"
: "^1off")); : "^1off"));
});
/*add("demigod", []()
{
if (!game::SV_Loaded())
{
return;
}
game::sp::g_entities[0].flags ^= 2;
game::CG_GameMessage(0, utils::string::va("demigod mode %s",
game::sp::g_entities[0].flags & 2
? "^2on"
: "^1off"));
});
add("notarget", []()
{
if (!game::SV_Loaded())
{
return;
}
game::sp::g_entities[0].flags ^= 4;
game::CG_GameMessage(0, utils::string::va("notarget %s",
game::sp::g_entities[0].flags & 4
? "^2on"
: "^1off"));
});
add("noclip", []()
{
if (!game::SV_Loaded())
{
return;
}
game::sp::g_entities[0].client->flags ^= 1;
game::CG_GameMessage(0, utils::string::va("noclip %s",
game::sp::g_entities[0].client->flags & 1
? "^2on"
: "^1off"));
});
add("ufo", []()
{
if (!game::SV_Loaded())
{
return;
}
game::sp::g_entities[0].client->flags ^= 2;
game::CG_GameMessage(0, utils::string::va("ufo %s",
game::sp::g_entities[0].client->flags & 2
? "^2on"
: "^1off"));
});
add("give", [](const params& params)
{
if (!game::SV_Loaded())
{
return;
}
if (params.size() < 2)
{
game::CG_GameMessage(0, "You did not specify a weapon name");
return;
}
auto ps = game::SV_GetPlayerstateForClientNum(0);
const auto wp = game::G_GetWeaponForName(params.get(1));
if (wp)
{
if (game::G_GivePlayerWeapon(ps, wp, 0, 0, 0, 0, 0, 0))
{
game::G_InitializeAmmo(ps, wp, 0);
game::G_SelectWeapon(0, wp);
}
}
});
add("take", [](const params& params)
{
if (!game::SV_Loaded())
{
return;
}
if (params.size() < 2)
{
game::CG_GameMessage(0, "You did not specify a weapon name");
return;
}
auto ps = game::SV_GetPlayerstateForClientNum(0);
const auto wp = game::G_GetWeaponForName(params.get(1));
if (wp)
{
game::G_TakePlayerWeapon(ps, wp);
}
});*/ });*/
}
static void add_commands_mp()
{
//client_command_hook.create(0x1402E98F0, &client_command);
/*add_sv("god", [](const int client_num, const params_sv&)
{
if (!game::Dvar_FindVar("sv_cheats")->current.enabled)
{
game::SV_GameSendServerCommand(client_num, game::SV_CMD_RELIABLE,
"f \"Cheats are not enabled on this server\"");
return;
}
//add("notarget", []() game::mp::g_entities[client_num].flags ^= 1;
//{ game::SV_GameSendServerCommand(client_num, game::SV_CMD_RELIABLE,
// if (!game::SV_Loaded()) utils::string::va("f \"godmode %s\"",
// { game::mp::g_entities[client_num].flags & 1
// return; ? "^2on"
// } : "^1off"));
});
// game::g_entities[0].flags ^= game::FL_NOTARGET; add_sv("demigod", [](const int client_num, const params_sv&)
// game::CG_GameMessage(0, utils::string::va("notarget %s", {
// game::g_entities[0].flags & game::FL_NOTARGET if (!game::Dvar_FindVar("sv_cheats")->current.enabled)
// ? "^2on" {
// : "^1off")); game::SV_GameSendServerCommand(client_num, game::SV_CMD_RELIABLE,
//}); "f \"Cheats are not enabled on this server\"");
return;
}
//add("noclip", []() game::mp::g_entities[client_num].flags ^= 2;
//{ game::SV_GameSendServerCommand(client_num, game::SV_CMD_RELIABLE,
// if (!game::SV_Loaded()) utils::string::va("f \"demigod mode %s\"",
// { game::mp::g_entities[client_num].flags & 2
// return; ? "^2on"
// } : "^1off"));
});
// game::g_entities[0].client->flags ^= 1; add_sv("notarget", [](const int client_num, const params_sv&)
// game::CG_GameMessage(0, utils::string::va("noclip %s", {
// game::g_entities[0].client->flags & 1 if (!game::Dvar_FindVar("sv_cheats")->current.enabled)
// ? "^2on" {
// : "^1off")); game::SV_GameSendServerCommand(client_num, game::SV_CMD_RELIABLE,
//}); "f \"Cheats are not enabled on this server\"");
return;
}
game::mp::g_entities[client_num].flags ^= 4;
game::SV_GameSendServerCommand(client_num, game::SV_CMD_RELIABLE,
utils::string::va("f \"notarget %s\"",
game::mp::g_entities[client_num].flags & 4
? "^2on"
: "^1off"));
});
add_sv("noclip", [](const int client_num, const params_sv&)
{
if (!game::Dvar_FindVar("sv_cheats")->current.enabled)
{
game::SV_GameSendServerCommand(client_num, game::SV_CMD_RELIABLE,
"f \"Cheats are not enabled on this server\"");
return;
}
game::mp::g_entities[client_num].client->flags ^= 1;
game::SV_GameSendServerCommand(client_num, game::SV_CMD_RELIABLE,
utils::string::va("f \"noclip %s\"",
game::mp::g_entities[client_num].client->flags & 1
? "^2on"
: "^1off"));
});
add_sv("ufo", [](const int client_num, const params_sv&)
{
if (!game::Dvar_FindVar("sv_cheats")->current.enabled)
{
game::SV_GameSendServerCommand(client_num, game::SV_CMD_RELIABLE,
"f \"Cheats are not enabled on this server\"");
return;
}
game::mp::g_entities[client_num].client->flags ^= 2;
game::SV_GameSendServerCommand(client_num, game::SV_CMD_RELIABLE,
utils::string::va("f \"ufo %s\"",
game::mp::g_entities[client_num].client->flags & 2
? "^2on"
: "^1off"));
});
add_sv("give", [](const int client_num, const params_sv& params)
{
if (!game::Dvar_FindVar("sv_cheats")->current.enabled)
{
game::SV_GameSendServerCommand(client_num, game::SV_CMD_RELIABLE,
"f \"Cheats are not enabled on this server\"");
return;
}
if (params.size() < 2)
{
game::SV_GameSendServerCommand(client_num, game::SV_CMD_RELIABLE,
"f \"You did not specify a weapon name\"");
return;
}
auto ps = game::SV_GetPlayerstateForClientNum(client_num);
const auto wp = game::G_GetWeaponForName(params.get(1));
if (wp)
{
if (game::G_GivePlayerWeapon(ps, wp, 0, 0, 0, 0, 0, 0))
{
game::G_InitializeAmmo(ps, wp, 0);
game::G_SelectWeapon(client_num, wp);
}
}
});
add_sv("take", [](const int client_num, const params_sv& params)
{
if (!game::Dvar_FindVar("sv_cheats")->current.enabled)
{
game::SV_GameSendServerCommand(client_num, game::SV_CMD_RELIABLE,
"f \"Cheats are not enabled on this server\"");
return;
}
if (params.size() < 2)
{
game::SV_GameSendServerCommand(client_num, game::SV_CMD_RELIABLE,
"f \"You did not specify a weapon name\"");
return;
}
auto ps = game::SV_GetPlayerstateForClientNum(client_num);
const auto wp = game::G_GetWeaponForName(params.get(1));
if (wp)
{
game::G_TakePlayerWeapon(ps, wp);
}
}); */
} }
}; };
} }

View File

@ -46,11 +46,11 @@ namespace console
void dispatch_message(const int type, const std::string& message) void dispatch_message(const int type, const std::string& message)
{ {
/*if (rcon::message_redirect(message)) game_console::print(type, message);
messages.access([&message](message_queue& msgs)
{ {
return; msgs.emplace(message);
}*/ });
} }
void append_text(const char* text) void append_text(const char* text)
@ -172,7 +172,6 @@ namespace console
}); });
} }
void log_messages() void log_messages()
{ {
/*while*/ /*while*/
@ -203,10 +202,6 @@ namespace console
{ {
OutputDebugStringA(message.data()); OutputDebugStringA(message.data());
game::Conbuf_AppendText(message.data()); game::Conbuf_AppendText(message.data());
FILE* pFile = fopen("debug.log", "a");
fprintf(pFile, "%s\n", message.data());
fclose(pFile);
} }
void runner() void runner()

View File

@ -416,9 +416,13 @@ namespace demonware
void post_unpack() override void post_unpack() override
{ {
/*
mwr has upgraded some networking methods and the gethostbyname import from winsock library is no longer used
gethostbyname has been replaced with getaddrinfo
btw, still you can't get online..
*/
utils::hook::jump(SELECT_VALUE(0x140610320, 0x1407400B0), bd_logger_stub); // H1MP64(1.4) utils::hook::jump(SELECT_VALUE(0x140610320, 0x1407400B0), bd_logger_stub); // H1MP64(1.4)
//singleplayer not supported so far.
if (game::environment::is_sp()) if (game::environment::is_sp())
{ {
utils::hook::set<uint8_t>(0x1405FCA00, 0xC3); // bdAuthSteam H1(1.4) utils::hook::set<uint8_t>(0x1405FCA00, 0xC3); // bdAuthSteam H1(1.4)

View File

@ -1,15 +1,18 @@
#include <std_include.hpp> #include <std_include.hpp>
#include "loader/component_loader.hpp" #include "loader/component_loader.hpp"
#include "game_console.hpp"
#include "command.hpp"
#include "console.hpp"
#include "scheduler.hpp"
#include "game/game.hpp" #include "game/game.hpp"
#include "game/dvars.hpp" #include "game/dvars.hpp"
#include "game_console.hpp"
#include "command.hpp"
#include "scheduler.hpp"
#include <utils/string.hpp> #include <utils/string.hpp>
#include <utils/hook.hpp> #include <utils/hook.hpp>
#include <utils/concurrency.hpp>
#include "version.hpp"
#define console_font game::R_RegisterFont("fonts/fira_mono_regular.ttf", 18) #define console_font game::R_RegisterFont("fonts/fira_mono_regular.ttf", 18)
#define material_white game::Material_RegisterHandle("white") #define material_white game::Material_RegisterHandle("white")
@ -20,66 +23,68 @@ namespace game_console
{ {
struct console_globals struct console_globals
{ {
float x; float x{};
float y; float y{};
float left_x; float left_x{};
float font_height; float font_height{};
bool may_auto_complete; bool may_auto_complete{};
char auto_complete_choice[64]; char auto_complete_choice[64]{};
int info_line_count; int info_line_count{};
}; };
using output_queue = std::deque<std::string>;
struct ingame_console struct ingame_console
{ {
char buffer[256]; char buffer[256]{};
int cursor; int cursor{};
int font_height; int font_height{};
int visible_line_count; int visible_line_count{};
int visible_pixel_width; int visible_pixel_width{};
float screen_min[2]; //left & top float screen_min[2]{}; //left & top
float screen_max[2]; //right & bottom float screen_max[2]{}; //right & bottom
console_globals globals; console_globals globals{};
bool output_visible; bool output_visible{};
int display_line_offset; int display_line_offset{};
int line_count; int line_count{};
std::deque<std::string> output; utils::concurrency::container<output_queue, std::recursive_mutex> output{};
}; };
ingame_console con; ingame_console con{};
std::int32_t history_index = -1; std::int32_t history_index = -1;
std::deque<std::string> history; std::deque<std::string> history{};
std::string fixed_input; std::string fixed_input{};
std::vector<std::string> matches; std::vector<std::string> matches{};
float color_white[4] = { 1.0f, 1.0f, 1.0f, 1.0f }; float color_white[4] = { 1.0f, 1.0f, 1.0f, 1.0f };
float color_title[4] = { 0.9f, 0.9f, 0.5f, 1.0f }; float color_title[4] = { 0.9f, 0.9f, 0.5f, 1.0f };
void clear() void clear()
{ {
strncpy_s(con.buffer, "", 256); strncpy_s(con.buffer, "", sizeof(con.buffer));
con.cursor = 0; con.cursor = 0;
fixed_input = ""; fixed_input = "";
matches.clear(); matches.clear();
} }
void print(const std::string& data) void print_internal(const std::string& data)
{ {
if (con.visible_line_count > 0 && con.display_line_offset == (con.output.size() - con.visible_line_count)) con.output.access([&](output_queue& output)
{
if (con.visible_line_count > 0
&& con.display_line_offset == (output.size() - con.visible_line_count))
{ {
con.display_line_offset++; con.display_line_offset++;
} }
output.push_back(data);
con.output.push_back(data); if (output.size() > 512)
printf("%s\n", data.data());
if (con.output.size() > 1024)
{ {
con.output.pop_front(); output.pop_front();
} }
});
} }
void toggle_console() void toggle_console()
@ -146,6 +151,8 @@ namespace game_console
color); color);
} }
void draw_input_text_and_over(const char* str, float* color) void draw_input_text_and_over(const char* str, float* color)
{ {
game::R_AddCmdDrawText(str, 0x7FFFFFFF, console_font, con.globals.x, game::R_AddCmdDrawText(str, 0x7FFFFFFF, console_font, con.globals.x,
@ -171,6 +178,7 @@ namespace game_console
game::R_AddCmdDrawText(text, 0x7FFFFFFF, console_font, con.globals.x + offset, _y, 1.0f, 1.0f, 0.0f, color, 0); game::R_AddCmdDrawText(text, 0x7FFFFFFF, console_font, con.globals.x + offset, _y, 1.0f, 1.0f, 0.0f, color, 0);
} }
bool match_compare(const std::string& input, const std::string& text, const bool exact) bool match_compare(const std::string& input, const std::string& text, const bool exact)
{ {
if (exact && text == input) return true; if (exact && text == input) return true;
@ -230,7 +238,7 @@ namespace game_console
con.globals.left_x = con.screen_min[0] + 6.0f; con.globals.left_x = con.screen_min[0] + 6.0f;
draw_input_box(1, dvars::con_inputBoxColor->current.vector); draw_input_box(1, dvars::con_inputBoxColor->current.vector);
draw_input_text_and_over("H1-Mod >", color_title); draw_input_text_and_over("H1-Mod: " VERSION ">", color_title);
con.globals.left_x = con.globals.x; con.globals.left_x = con.globals.x;
con.globals.auto_complete_choice[0] = 0; con.globals.auto_complete_choice[0] = 0;
@ -240,8 +248,8 @@ namespace game_console
con.globals.y + con.globals.font_height, 1.0f, 1.0f, 0, color_white, 0, con.globals.y + con.globals.font_height, 1.0f, 1.0f, 0, color_white, 0,
con.cursor, '|'); con.cursor, '|');
//game::R_AddCmdDrawText(con.buffer, 0x7FFF, console_font, con.globals.x, 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); con.globals.y + con.globals.font_height, 1.0f, 1.0f, 0.0f, color_white, 0);
// check if using a prefixed '/' or not // check if using a prefixed '/' or not
@ -328,19 +336,19 @@ namespace game_console
} }
} }
void draw_output_scrollbar(const float x, float y, const float width, const float height) void draw_output_scrollbar(const float x, float y, const float width, const float height, output_queue& output)
{ {
const auto _x = (x + width) - 10.0f; const auto _x = (x + width) - 10.0f;
draw_box(_x, y, 10.0f, height, dvars::con_outputBarColor->current.vector); draw_box(_x, y, 10.0f, height, dvars::con_outputBarColor->current.vector);
auto _height = height; auto _height = height;
if (con.output.size() > con.visible_line_count) if (output.size() > con.visible_line_count)
{ {
const auto percentage = static_cast<float>(con.visible_line_count) / con.output.size(); const auto percentage = static_cast<float>(con.visible_line_count) / output.size();
_height *= percentage; _height *= percentage;
const auto remainingSpace = height - _height; const auto remainingSpace = height - _height;
const auto percentageAbove = static_cast<float>(con.display_line_offset) / (con.output.size() - con. const auto percentageAbove = static_cast<float>(con.display_line_offset) / (output.size() - con.
visible_line_count); visible_line_count);
y = y + (remainingSpace * percentageAbove); y = y + (remainingSpace * percentageAbove);
@ -349,28 +357,30 @@ namespace game_console
draw_box(_x, y, 10.0f, _height, dvars::con_outputSliderColor->current.vector); draw_box(_x, y, 10.0f, _height, dvars::con_outputSliderColor->current.vector);
} }
void draw_output_text(const float x, float y) void draw_output_text(const float x, float y, output_queue& output)
{ {
const auto offset = con.output.size() >= con.visible_line_count const auto offset = output.size() >= con.visible_line_count
? 0.0f ? 0.0f
: (con.font_height * (con.visible_line_count - con.output.size())); : (con.font_height * (con.visible_line_count - output.size()));
for (auto i = 0; i < con.visible_line_count; i++) for (auto i = 0; i < con.visible_line_count; i++)
{ {
y = console_font->pixelHeight + y; y = console_font->pixelHeight + y;
const auto index = i + con.display_line_offset; const auto index = i + con.display_line_offset;
if (index >= con.output.size()) if (index >= output.size())
{ {
break; break;
} }
game::R_AddCmdDrawText(con.output.at(index).data(), 0x7FFF, console_font, x, y + offset, 1.0f, 1.0f, game::R_AddCmdDrawText(output.at(index).data(), 0x7FFF, console_font, x, y + offset, 1.0f, 1.0f,
0.0f, color_white, 0); 0.0f, color_white, 0);
} }
} }
void draw_output_window() void draw_output_window()
{
con.output.access([](output_queue& output)
{ {
draw_box(con.screen_min[0], con.screen_min[1] + 32.0f, con.screen_max[0] - con.screen_min[0], draw_box(con.screen_min[0], con.screen_min[1] + 32.0f, con.screen_max[0] - con.screen_min[0],
(con.screen_max[1] - con.screen_min[1]) - 32.0f, dvars::con_outputWindowColor->current.vector); (con.screen_max[1] - con.screen_min[1]) - 32.0f, dvars::con_outputWindowColor->current.vector);
@ -383,8 +393,9 @@ namespace game_console
game::R_AddCmdDrawText("H1-Mod 1.4", 0x7FFFFFFF, console_font, x, game::R_AddCmdDrawText("H1-Mod 1.4", 0x7FFFFFFF, console_font, x,
((height - 16.0f) + y) + console_font->pixelHeight, 1.0f, 1.0f, 0.0f, color_title, 0); ((height - 16.0f) + y) + console_font->pixelHeight, 1.0f, 1.0f, 0.0f, color_title, 0);
draw_output_scrollbar(x, y, width, height); draw_output_scrollbar(x, y, width, height, output);
draw_output_text(x, y); draw_output_text(x, y, output);
});
} }
void draw_console() void draw_console()
@ -408,7 +419,7 @@ namespace game_console
} }
} }
void print(const int type, const char* fmt, ...) void print_internal(const char* fmt, ...)
{ {
char va_buffer[0x200] = { 0 }; char va_buffer[0x200] = { 0 };
@ -420,37 +431,53 @@ namespace game_console
const auto formatted = std::string(va_buffer); const auto formatted = std::string(va_buffer);
const auto lines = utils::string::split(formatted, '\n'); const auto lines = utils::string::split(formatted, '\n');
for (auto& line : lines) for (const auto& line : lines)
{ {
print(type == con_type_info ? line : "^"s.append(std::to_string(type)).append(line)); print_internal(line);
} }
} }
bool console_char_event(const int localClientNum, const int key) void print(const int type, const std::string& data)
{ {
if (key == '`' || key == '~' || key == '|' || key == '\\') try
{
if (game::environment::is_dedi())
{
return;
}
}
catch (std::exception&)
{
return;
}
const auto lines = utils::string::split(data, '\n');
for (const auto& line : lines)
{
print_internal(type == console::con_type_info ? line : "^"s.append(std::to_string(type)).append(line));
}
}
bool console_char_event(const int local_client_num, const int key)
{
if (key == game::keyNum_t::K_GRAVE || key == game::keyNum_t::K_TILDE)
{ {
return false; return false;
} }
if (key > 127)
{
return true;
}
if (*game::keyCatchers & 1) if (*game::keyCatchers & 1)
{ {
if (key == game::keyNum_t::K_TAB) // tab (auto complete) if (key == game::keyNum_t::K_TAB) // tab (auto complete)
{ {
if (con.globals.may_auto_complete) if (con.globals.may_auto_complete)
{ {
const auto firstChar = con.buffer[0]; const auto first_char = con.buffer[0];
clear(); clear();
if (firstChar == '\\' || firstChar == '/') if (first_char == '\\' || first_char == '/')
{ {
con.buffer[0] = firstChar; con.buffer[0] = first_char;
con.buffer[1] = '\0'; con.buffer[1] = '\0';
} }
@ -473,9 +500,9 @@ namespace game_console
return false; return false;
} }
for (auto i = 0; i < clipboard.length(); i++) for (size_t i = 0; i < clipboard.length(); i++)
{ {
console_char_event(localClientNum, clipboard[i]); console_char_event(local_client_num, clipboard[i]);
} }
return false; return false;
@ -485,7 +512,11 @@ namespace game_console
{ {
clear(); clear();
con.line_count = 0; con.line_count = 0;
con.output.clear(); con.display_line_offset = 0;
con.output.access([](output_queue& output)
{
output.clear();
});
history_index = -1; history_index = -1;
history.clear(); history.clear();
@ -527,13 +558,18 @@ namespace game_console
return true; return true;
} }
void execute(const char* cmd) bool console_key_event(const int local_client_num, const int key, const int down)
{ {
game::Cbuf_AddText(0, utils::string::va("%s \n", cmd)); if (key == game::keyNum_t::K_F10)
{
if (game::mp::svs_clients[local_client_num].header.state >= 1)
{
return false;
}
game::Cmd_ExecuteSingleCommand(local_client_num, 0, "lui_open menu_systemlink_join\n");
} }
bool console_key_event(const int localClientNum, const int key, const int down)
{
if (key == game::keyNum_t::K_GRAVE || key == game::keyNum_t::K_TILDE) if (key == game::keyNum_t::K_GRAVE || key == game::keyNum_t::K_TILDE)
{ {
if (!down) if (!down)
@ -541,7 +577,7 @@ namespace game_console
return false; return false;
} }
if (game::playerKeys[localClientNum].keys[game::keyNum_t::K_SHIFT].down) if (game::playerKeys[local_client_num].keys[game::keyNum_t::K_SHIFT].down)
{ {
if (!(*game::keyCatchers & 1)) if (!(*game::keyCatchers & 1))
toggle_console(); toggle_console();
@ -613,24 +649,29 @@ namespace game_console
//scroll through output //scroll through output
if (key == game::keyNum_t::K_MWHEELUP || key == game::keyNum_t::K_PGUP) if (key == game::keyNum_t::K_MWHEELUP || key == game::keyNum_t::K_PGUP)
{ {
if (con.output.size() > con.visible_line_count && con.display_line_offset > 0) con.output.access([](output_queue& output)
{
if (output.size() > con.visible_line_count && con.display_line_offset > 0)
{ {
con.display_line_offset--; con.display_line_offset--;
} }
});
} }
else if (key == game::keyNum_t::K_MWHEELDOWN || key == game::keyNum_t::K_PGDN) else if (key == game::keyNum_t::K_MWHEELDOWN || key == game::keyNum_t::K_PGDN)
{ {
if (con.output.size() > con.visible_line_count && con.display_line_offset < (con.output.size() - con.output.access([](output_queue& output)
con. {
visible_line_count)) if (output.size() > con.visible_line_count
&& con.display_line_offset < (output.size() - con.visible_line_count))
{ {
con.display_line_offset++; con.display_line_offset++;
} }
});
} }
if (key == game::keyNum_t::K_ENTER) if (key == game::keyNum_t::K_ENTER)
{ {
execute(fixed_input.data()); game::Cbuf_AddText(0, utils::string::va("%s \n", fixed_input.data()));
if (history_index != -1) if (history_index != -1)
{ {
@ -644,7 +685,7 @@ namespace game_console
history.push_front(con.buffer); history.push_front(con.buffer);
print(""s.append(con.buffer)); console::info("]%s\n", con.buffer);
if (history.size() > 10) if (history.size() > 10)
{ {
@ -661,13 +702,31 @@ namespace game_console
return true; return true;
} }
class component final : public component_interface class component final : public component_interface
{ {
public: public:
void post_load() override
{
if (game::environment::is_dedi())
{
return;
}
//scheduler::loop(draw_console, scheduler::pipeline::renderer);
}
void post_unpack() override void post_unpack() override
{ {
scheduler::loop(draw_console, scheduler::pipeline::renderer); scheduler::loop(draw_console, scheduler::pipeline::renderer);
if (game::environment::is_dedi())
{
return;
}
// initialize our structs
con.cursor = 0; con.cursor = 0;
con.visible_line_count = 0; con.visible_line_count = 0;
con.output_visible = false; con.output_visible = false;
@ -683,70 +742,49 @@ namespace game_console
con.globals.info_line_count = 0; con.globals.info_line_count = 0;
strncpy_s(con.globals.auto_complete_choice, "", 64); strncpy_s(con.globals.auto_complete_choice, "", 64);
// //add clear command // add clear command
//command::add("clear", [&]() command::add("clear", [&]()
//{ {
//clear(); clear();
//con.line_count = 0; con.line_count = 0;
//con.output.clear(); con.display_line_offset = 0;
//history_index = -1; con.output.access([](output_queue& output)
//history.clear(); {
//}); output.clear();
});
history_index = -1;
history.clear();
});
// add our dvars // add our dvars
dvars::con_inputBoxColor = dvars::register_vec4( dvars::con_inputBoxColor = dvars::register_vec4("con_inputBoxColor", 0.2f, 0.2f, 0.2f, 0.9f, 0.0f, 1.0f,
"con_inputBoxColor", game::DVAR_FLAG_SAVED,
0.2f, 0.2f, 0.2f, 0.9f, "color of console input box");
dvars::con_inputHintBoxColor = dvars::register_vec4("con_inputHintBoxColor", 0.3f, 0.3f, 0.3f, 1.0f,
0.0f, 1.0f, 0.0f, 1.0f,
game::DVAR_FLAG_SAVED); game::DVAR_FLAG_SAVED, "color of console input hint box");
dvars::con_outputBarColor = dvars::register_vec4("con_outputBarColor", 0.5f, 0.5f, 0.5f, 0.6f, 0.0f,
dvars::con_inputHintBoxColor = dvars::register_vec4( 1.0f, game::DVAR_FLAG_SAVED,
"con_inputHintBoxColor", "color of console output bar");
0.3f, 0.3f, 0.3f, 1.0f, dvars::con_outputSliderColor = dvars::register_vec4("con_outputSliderColor", 1.0f, 0.8f, 0.0f, 1.0f,
0.0f, 1.0f, 0.0f, 1.0f,
game::DVAR_FLAG_SAVED); game::DVAR_FLAG_SAVED, "color of console output slider");
dvars::con_outputWindowColor = dvars::register_vec4("con_outputWindowColor", 0.25f, 0.25f, 0.25f, 0.85f,
dvars::con_outputBarColor = dvars::register_vec4( 0.0f,
"con_outputBarColor", 1.0f, game::DVAR_FLAG_SAVED, "color of console output window");
0.5f, 0.5f, 0.5f, 0.6f, dvars::con_inputDvarMatchColor = dvars::register_vec4("con_inputDvarMatchColor", 1.0f, 1.0f, 0.8f, 1.0f,
0.0f, 1.0f, 0.0f,
game::DVAR_FLAG_SAVED); 1.0f, game::DVAR_FLAG_SAVED, "color of console matched dvar");
dvars::con_inputDvarValueColor = dvars::register_vec4("con_inputDvarValueColor", 1.0f, 1.0f, 0.8f, 1.0f,
dvars::con_outputSliderColor = dvars::register_vec4( 0.0f,
"con_outputSliderColor", 1.0f, game::DVAR_FLAG_SAVED, "color of console matched dvar value");
0.9f, 0.9f, 0.5f, 1.00f,
0.0f, 1.0f,
game::DVAR_FLAG_SAVED);
dvars::con_outputWindowColor = dvars::register_vec4(
"con_outputWindowColor",
0.25f, 0.25f, 0.25f, 0.85f,
0.0f, 1.0f,
game::DVAR_FLAG_SAVED);
dvars::con_inputDvarMatchColor = dvars::register_vec4(
"con_inputDvarMatchColor",
1.0f, 1.0f, 0.8f, 1.0f,
0.0f, 1.0f,
game::DVAR_FLAG_SAVED);
dvars::con_inputDvarValueColor = dvars::register_vec4(
"con_inputDvarValueColor",
1.0f, 1.0f, 0.8f, 1.0f,
0.0f, 1.0f,
game::DVAR_FLAG_SAVED);
dvars::con_inputDvarInactiveValueColor = dvars::register_vec4( dvars::con_inputDvarInactiveValueColor = dvars::register_vec4(
"con_inputDvarInactiveValueColor", "con_inputDvarInactiveValueColor", 0.8f, 0.8f,
0.8f, 0.8f, 0.8f, 1.0f, 0.8f, 1.0f, 0.0f, 1.0f, game::DVAR_FLAG_SAVED,
0.0f, 1.0f, "color of console inactive dvar value");
game::DVAR_FLAG_SAVED); dvars::con_inputCmdMatchColor = dvars::register_vec4("con_inputCmdMatchColor", 0.80f, 0.80f, 1.0f, 1.0f,
0.0f,
dvars::con_inputCmdMatchColor = dvars::register_vec4( 1.0f, game::DVAR_FLAG_SAVED, "color of console matched command");
"con_inputCmdMatchColor",
0.80f, 0.80f, 1.0f, 1.0f,
0.0f, 1.0f,
game::DVAR_FLAG_SAVED);
} }
}; };
} }

View File

@ -9,10 +9,12 @@ namespace game_console
con_type_info = 7 con_type_info = 7
}; };
void print(int type, const char* fmt, ...); //void print(int type, const char* fmt, ...);
bool console_char_event(int local_client_num, int key); bool console_char_event(int local_client_num, int key);
bool console_key_event(int local_client_num, int key, int down); bool console_key_event(int local_client_num, int key, int down);
bool match_compare(const std::string& input, const std::string& text, const bool exact);
void find_matches(std::string input, std::vector<std::string>& suggestions, const bool exact);
void execute(const char* cmd); void execute(const char* cmd);
} }

View File

@ -9,6 +9,7 @@
#include <utils/hook.hpp> #include <utils/hook.hpp>
#include <utils/string.hpp> #include <utils/string.hpp>
#include <game/dvars.hpp> #include <game/dvars.hpp>
#include <component/console.hpp>
namespace network namespace network
{ {
@ -268,7 +269,7 @@ namespace network
} }
else else
{ {
game_console::print(game_console::con_type_info, "%s\n", message.data()); console::info("%s\n", message.data()); //test
} }
}); });
} }

View File

@ -46,6 +46,26 @@ namespace dvars
} }
} }
namespace
{
template <typename T>
T* find_dvar(std::unordered_map<std::string, T>& map, const std::string& name)
{
auto i = map.find(name);
if (i != map.end())
{
return &i->second;
}
return nullptr;
}
bool find_dvar(std::unordered_set<std::string>& set, const std::string& name)
{
return set.find(name) != set.end();
}
}
std::string dvar_get_domain(const game::dvar_type type, const game::dvar_limits& domain) std::string dvar_get_domain(const game::dvar_type type, const game::dvar_limits& domain)
{ {
std::string str; std::string str;

View File

@ -932,7 +932,7 @@ namespace game
struct dvar_t struct dvar_t
{ {
int hash; const char* hash;
unsigned int flags; unsigned int flags;
dvar_type type; dvar_type type;
bool modified; bool modified;

View File

@ -46,7 +46,7 @@ namespace game
WEAK symbol<bool()> CL_IsCgameInitialized{ 0x14017EE30, 0x140245650 }; // H1(1.4) WEAK symbol<bool()> CL_IsCgameInitialized{ 0x14017EE30, 0x140245650 }; // H1(1.4)
WEAK symbol<unsigned int(int)> Live_SyncOnlineDataFlags{ 0, 0x14059A700 }; // H1(1.4) WEAK symbol<unsigned int(int)> Live_SyncOnlineDataFlags{ 0, 0x14059A700 }; // H1(1.4)
WEAK symbol<void()> Sys_Milliseconds{ 0x1403E2B10, 0x140513710 }; // H1(1.4) WEAK symbol<int()> Sys_Milliseconds{ 0x1403E2B10, 0x140513710 }; // H1(1.4)
WEAK symbol<bool()> Sys_IsDatabaseReady2{ 0, 0x14042B090 }; // H1(1.4) WEAK symbol<bool()> Sys_IsDatabaseReady2{ 0, 0x14042B090 }; // H1(1.4)
WEAK symbol<void(netadr_s* from)> SV_DirectConnect{ 0, 0x140480860 }; // H1(1.4) WEAK symbol<void(netadr_s* from)> SV_DirectConnect{ 0, 0x140480860 }; // H1(1.4)
@ -151,6 +151,11 @@ namespace game
WEAK symbol<void()> Sys_ShowConsole{ 0x1403E3B90, 0x140514910 }; // H1(1.4) WEAK symbol<void()> Sys_ShowConsole{ 0x1403E3B90, 0x140514910 }; // H1(1.4)
WEAK symbol<const char* (const char*)> UI_GetMapDisplayName{ 0, 0x140408CC0 }; // H1(1.4)
WEAK symbol<const char* (const char*)> UI_GetGameTypeDisplayName{ 0, 0x1404086A0 }; // H1(1.4)
WEAK symbol<const char* (const char* string)> UI_SafeTranslateString{ 0x140350430, 0x1405A2930 }; // H1(1.4) WEAK symbol<const char* (const char* string)> UI_SafeTranslateString{ 0x140350430, 0x1405A2930 }; // H1(1.4)
WEAK symbol<void* (jmp_buf* Buf, int Value)> longjmp{ 0x140648FD4, 0x14089EED0 }; // H1(1.4) WEAK symbol<void* (jmp_buf* Buf, int Value)> longjmp{ 0x140648FD4, 0x14089EED0 }; // H1(1.4)