diff --git a/src/client/component/asset_list.cpp b/src/client/component/asset_list.cpp new file mode 100644 index 00000000..cb2715f5 --- /dev/null +++ b/src/client/component/asset_list.cpp @@ -0,0 +1,102 @@ +#include +#include "loader/component_loader.hpp" + +#include "game/game.hpp" +#include "game/dvars.hpp" + +#include "scheduler.hpp" +#include "command.hpp" +#include "gui.hpp" + +#include +#include + +namespace asset_list +{ + namespace + { + void enum_assets(const game::XAssetType type, const std::function& callback, const bool includeOverride) + { + game::DB_EnumXAssets_Internal(type, static_cast([](game::XAssetHeader header, void* data) + { + const auto& cb = *static_cast*>(data); + cb(header); + }), &callback, includeOverride); + } + + void on_frame() + { + if (!gui::enabled_menus["asset_list"]) + { + return; + } + + static bool shown_assets[game::XAssetType::ASSET_TYPE_COUNT]; + + { + ImGui::Begin("Asset list", &gui::enabled_menus["asset_list"]); + + static char filter[0x200]{}; + ImGui::InputText("asset type", filter, IM_ARRAYSIZE(filter)); + for (auto i = 0; i < game::XAssetType::ASSET_TYPE_COUNT; i++) + { + const auto name = game::g_assetNames[i]; + const auto type = static_cast(i); + + if (strstr(name, filter)) + { + ImGui::Checkbox(name, &shown_assets[type]); + } + } + + ImGui::End(); + } + + for (auto i = 0; i < game::XAssetType::ASSET_TYPE_COUNT; i++) + { + const auto name = game::g_assetNames[i]; + const auto type = static_cast(i); + + if (!shown_assets[type]) + { + continue; + } + + ImGui::SetNextWindowSizeConstraints(ImVec2(500, 500), ImVec2(1000, 1000)); + ImGui::Begin(name, &shown_assets[type]); + + static char filter[0x200]{}; + ImGui::InputText("asset name", filter, IM_ARRAYSIZE(filter)); + + enum_assets(type, [type](const game::XAssetHeader header) + { + const auto asset = game::XAsset{type, header}; + const auto* const asset_name = game::DB_GetXAssetName(&asset); + + if (!strstr(asset_name, filter)) + { + return; + } + + if (ImGui::Button(asset_name)) + { + utils::string::set_clipboard_data(asset_name); + } + }, true); + + ImGui::End(); + } + } + } + + class component final : public component_interface + { + public: + void post_unpack() override + { + gui::on_frame(on_frame); + } + }; +} + +REGISTER_COMPONENT(asset_list::component) diff --git a/src/client/component/entity_list.cpp b/src/client/component/entity_list.cpp new file mode 100644 index 00000000..cf723276 --- /dev/null +++ b/src/client/component/entity_list.cpp @@ -0,0 +1,697 @@ +#include +#include "loader/component_loader.hpp" + +#include "game/game.hpp" +#include "game/dvars.hpp" + +#include "scheduler.hpp" +#include "command.hpp" +#include "gui.hpp" +#include "scripting.hpp" + +#include "game/scripting/execution.hpp" + +#include +#include +#include + +namespace entity_list +{ + namespace + { + enum entity_type + { + type_any, + actor, + spawner, + weapon, + node, + count, + }; + + enum entity_team + { + team_any, + neutral, + allies, + axis, + team3 + }; + + std::unordered_map team_names = + { + {entity_team::neutral, "neutral"}, + {entity_team::allies, "allies"}, + {entity_team::axis, "axis"}, + {entity_team::team3, "team3"}, + }; + + struct entity_info_t + { + unsigned int id; + unsigned int num; + std::unordered_map fields; + }; + + struct filters_t + { + bool filter_by_range{}; + float range{}; + entity_team team{}; + entity_type type{}; + std::vector> fields; + }; + + struct data_t + { + bool auto_update{}; + bool force_update{}; + filters_t filters; + std::chrono::milliseconds interval{}; + std::chrono::high_resolution_clock::time_point last_call{}; + std::vector entity_info{}; + std::unordered_map selected_fields = + { + {"code_classname", false}, + {"classname", true}, + {"origin", true}, + {"model", false}, + {"spawnflags", false}, + {"target", false}, + {"targetname", false}, + {"count", false}, + {"health", false}, + {"dmg", false}, + {"angles", true}, + {"script_linkname", false}, + {"script_noteworthy", false}, + {"maxhealth", false}, + {"anglelerprate", false}, + {"activator", false}, + {"slidevelocity", false}, + {"disableplayeradsloscheck", false}, + {"type", false}, + {"accuracy", false}, + {"lookforward", false}, + {"lookright", false}, + {"lookup", false}, + {"fovcosine", false}, + {"fovcosinebusy", false}, + {"fovcosinez", false}, + {"upaimlimit", false}, + {"downaimlimit", false}, + {"rightaimlimit", false}, + {"leftaimlimit", false}, + {"maxsightdistsqrd", false}, + {"sightlatency", false}, + {"defaultsightlatency", false}, + {"ignoreclosefoliage", false}, + {"interval", false}, + {"teammovewaittime", false}, + {"damagetaken", false}, + {"damagedir", false}, + {"damageyaw", false}, + {"damagelocation", false}, + {"damageweapon", false}, + {"damagemod", false}, + {"proneok", false}, + {"walkdistfacingmotion", false}, + {"walkdist", false}, + {"desiredangle", false}, + {"pacifist", false}, + {"pacifistwait", false}, + {"footstepdetectdist", false}, + {"footstepdetectdistwalk", false}, + {"footstepdetectdistsprint", false}, + {"reactiontargetpos", false}, + {"newenemyreactiondistsq", false}, + {"ignoreexplosionevents", false}, + {"ignoresuppression", false}, + {"suppressionwait", false}, + {"suppressionduration", false}, + {"suppressionstarttime", false}, + {"suppressionmeter", false}, + {"ignoreplayersuppression", false}, + {"name", false}, + {"weapon", false}, + {"dontavoidplayer", false}, + {"grenadeawareness", false}, + {"grenade", false}, + {"grenadeweapon", false}, + {"grenadeammo", false}, + {"grenadetargetpos", false}, + {"grenadetargetvalid", false}, + {"grenadetossvel", false}, + {"favoriteenemy", false}, + {"highlyawareradius", false}, + {"minpaindamage", false}, + {"allowpain", false}, + {"allowdeath", false}, + {"delayeddeath", false}, + {"diequietly", false}, + {"forceragdollimmediate", false}, + {"providecoveringfire", false}, + {"doingambush", false}, + {"combatmode", false}, + {"alertlevel", false}, + {"alertlevelint", false}, + {"useable", false}, + {"ignoretriggers", false}, + {"pushable", false}, + {"script_pushable", false}, + {"dropweapon", false}, + {"drawoncompass", false}, + {"groundtype", false}, + {"anim_pose", false}, + {"goalradius", false}, + {"goalheight", false}, + {"goalpos", false}, + {"nodeoffsetpos", false}, + {"ignoreforfixednodesafecheck", false}, + {"fixednode", false}, + {"fixednodesaferadius", false}, + {"pathgoalpos", false}, + {"pathrandompercent", false}, + {"usechokepoints", false}, + {"stopanimdistsq", false}, + {"lastenemysightpos", false}, + {"pathenemylookahead", false}, + {"pathenemyfightdist", false}, + {"meleeattackdist", false}, + {"movemode", false}, + {"script_move_distance_override", false}, + {"usecombatscriptatcover", false}, + {"safetochangescript", false}, + {"keepclaimednode", false}, + {"keepclaimednodeifvalid", false}, + {"keepnodeduringscriptedanim", false}, + {"dodangerreact", false}, + {"dangerreactduration", false}, + {"nododgemove", false}, + {"noteammove", false}, + {"leanamount", false}, + {"pitchamount", false}, + {"turnrate", false}, + {"turnanimactive", false}, + {"badplaceawareness", false}, + {"damageshield", false}, + {"nogrenadereturnthrow", false}, + {"noattackeraccuracymod", false}, + {"frontshieldanglecos", false}, + {"lookaheaddir", false}, + {"lookaheaddist", false}, + {"lookaheadhitsstairs", false}, + {"velocity", false}, + {"prevanimdelta", false}, + {"exposedduration", false}, + {"requestarrivalnotify", false}, + {"scriptedarrivalent", false}, + {"goingtoruntopos", false}, + {"engagemindist", false}, + {"engageminfalloffdist", false}, + {"engagemaxdist", false}, + {"engagemaxfalloffdist", false}, + {"usingcovermoveup", false}, + {"finalaccuracy", false}, + {"facemotion", false}, + {"gunblockedbywall", false}, + {"relativedir", false}, + {"lockorientation", false}, + {"maxfaceenemydist", false}, + {"stairsstate", false}, + {"script", false}, + {"prevscript", false}, + {"headicon", false}, + {"headiconteam", false}, + {"coversearchinterval", false}, + {"threatupdateinterval", false}, + {"canclimbladders", false}, + {"swimmer", false}, + {"space", false}, + {"doghandler", false}, + {"sharpturnlookaheaddist", false}, + {"postsharpturnlookaheaddist", false}, + {"sharpturntooclosetodestdist", false}, + {"usepathsmoothingvalues", false}, + {"pathlookaheaddist", false}, + {"maxturnspeed", false}, + {"sharpturn", false}, + {"disablesightandthreatupdate", false}, + {"team", false}, + {"threatbias", false}, + {"threatbiasgroup", false}, + {"node", false}, + {"prevnode", false}, + {"enemy", false}, + {"syncedmeleetarget", false}, + {"lastattacker", false}, + {"lastpusher", false}, + {"ignoreme", false}, + {"ignoreall", false}, + {"maxvisibledist", false}, + {"surprisedbymedistsq", false}, + {"attackeraccuracy", false}, + {"ignorerandombulletdamage", false}, + {"dodamagetoall", false}, + {"turretinvulnerability", false}, + {"useorcaavoidance", false}, + {"reciprocality", false}, + {"avoidanceboundshalfsize", false}, + {"onlygoodnearestnodes", false}, + {"playername", false}, + {"deathinvulnerabletime", false}, + {"criticalbulletdamagedist", false}, + {"attackercount", false}, + {"damagemultiplier", false}, + {"laststand", false}, + {"motiontrackerenabled", false}, + {"veh_speed", false}, + {"veh_pathspeed", false}, + {"veh_transmission", false}, + {"veh_pathdir", false}, + {"veh_pathtype", false}, + {"veh_topspeed", false}, + {"veh_brake", false}, + {"veh_throttle", false}, + }; + }; + + utils::concurrency::container data_; + + bool verify_entity(unsigned int id) + { + const auto type = game::scr_VarGlob->objectVariableValue[id].w.type; + return type == game::SCRIPT_ENTITY; + } + + std::optional get_entity_array(const entity_type type, const entity_team team) + { + const auto value = scripting::call("getentarray"); + if (!value.is()) + { + return {}; + } + + const auto all = value.as(); + + if (type == entity_type::type_any) + { + return {all}; + } + + if (type == entity_type::actor) + { + scripting::array result{}; + + for (unsigned int i = 0; i < all.size(); i++) + { + const auto raw = all[i].get_raw(); + if (raw.type != game::SCRIPT_OBJECT) + { + continue; + } + + if (!verify_entity(raw.u.uintValue)) + { + continue; + } + + const auto entity = all[i].as(); + + const auto classname_value = entity.get("classname"); + if (!classname_value.is()) + { + continue; + } + + const auto team_value = entity.get("team"); + if (!team_value.is()) + { + continue; + } + + const auto classname = classname_value.as(); + const auto team_ = team_value.as(); + if (strstr(classname.data(), "actor_") && (team == entity_team::team_any || team_ == team_names[team])) + { + result.push(entity); + } + } + + return result; + } + + if (type == entity_type::spawner && team == entity_team::team_any) + { + return scripting::call("getspawnerarray").as(); + } + + if (type == entity_type::spawner) + { + return scripting::call("getspawnerteamarray", {team_names[team]}).as(); + } + + if (type == entity_type::weapon) + { + return scripting::call("getweaponarray").as(); + } + + if (type == entity_type::node) + { + return scripting::call("getnodearray").as(); + } + + return {}; + } + + void update_entity_list() + { + data_.access([](data_t& data) + { + const auto now = std::chrono::high_resolution_clock::now(); + if (!data.force_update && (!data.auto_update || (now - data.last_call < data.interval))) + { + return; + } + + data.last_call = now; + data.force_update = false; + + const auto value = get_entity_array(data.filters.type, data.filters.team); + if (!value.has_value()) + { + return; + } + + data.entity_info.clear(); + + const auto array = value.value(); + + for (unsigned int i = 0; i < array.size(); i++) + { + const auto raw = array[i].get_raw(); + if (raw.type != game::SCRIPT_OBJECT) + { + continue; + } + + if (!verify_entity(raw.u.uintValue)) + { + continue; + } + + const auto entity = array[i].as(); + entity_info_t info{}; + + info.id = raw.u.uintValue; + info.num = entity.get_entity_reference().entnum; + + if (data.filters.filter_by_range) + { + const auto player = scripting::call("getentbynum", {0}).as(); + const auto distance = scripting::call("distance", {player.get("origin"), entity.get("origin")}).as(); + + if (distance > data.filters.range) + { + continue; + } + } + + auto match_count = 0; + for (const auto& field : data.selected_fields) + { + if (!field.second) + { + continue; + } + + try + { + const auto field_value = entity.get(field.first); + const auto value_string = field_value.to_string(); + info.fields[field.first] = value_string; + + for (const auto& filter : data.filters.fields) + { + if (field_value.is() && + strstr(field.first.data(), utils::string::to_lower(filter.first).data()) && + strstr(value_string.data(), filter.second.data())) + { + match_count++; + } + } + } + catch (...) + { + + } + } + + if (match_count == data.filters.fields.size()) + { + data.entity_info.push_back(info); + } + } + }); + } + + void teleport_to(unsigned int id) + { + scheduler::once([id]() + { + if (!verify_entity(id)) + { + return; + } + + const auto dest = scripting::entity{id}; + const auto value = scripting::call("getentbynum", {0}); + if (value.get_raw().type != game::SCRIPT_OBJECT) + { + return; + } + + const auto player = value.as(); + player.call("setorigin", {dest.get("origin")}); + }, scheduler::pipeline::server); + } + + void teleport_to_reverse(unsigned int id) + { + scheduler::once([id]() + { + if (!verify_entity(id)) + { + return; + } + + const auto dest = scripting::entity{id}; + const auto value = scripting::call("getentbynum", {0}); + if (value.get_raw().type != game::SCRIPT_OBJECT) + { + return; + } + + const auto player = value.as(); + dest.set("origin", player.get("origin")); + }, scheduler::pipeline::server); + } + + void delete_entity(unsigned int id) + { + scheduler::once([id]() + { + if (!verify_entity(id)) + { + return; + } + + const auto target = scripting::entity{id}; + target.call("delete"); + }, scheduler::pipeline::server); + } + + void on_frame() + { + if (!gui::enabled_menus["entity_list"]) + { + return; + } + + data_.access([](data_t& data) + { + ImGui::Begin("Entity list", &gui::enabled_menus["entity_list"]); + + if (ImGui::Button("Update list")) + { + data.force_update = true; + } + + ImGui::Checkbox("Auto update", &data.auto_update); + + auto interval = static_cast(data.interval.count()); + if (data.auto_update && ImGui::SliderInt("Interval", &interval, 0, 1000 * 30)) + { + data.interval = std::chrono::milliseconds(interval); + } + + ImGui::Separator(); + + if (ImGui::CollapsingHeader("Filters")) + { + ImGui::Checkbox("Filter by distance", &data.filters.filter_by_range); + + if (data.filters.filter_by_range) + { + if (ImGui::SliderFloat("range", &data.filters.range, 0.f, 10000.f)) + { + data.force_update = true; + } + } + + if (ImGui::TreeNode("Entity type")) + { + auto result = 0; + + result += ImGui::RadioButton("any", reinterpret_cast(&data.filters.type), entity_type::type_any); + result += ImGui::RadioButton("actor", reinterpret_cast(&data.filters.type), entity_type::actor); + result += ImGui::RadioButton("spawner", reinterpret_cast(&data.filters.type), entity_type::spawner); + result += ImGui::RadioButton("weapon", reinterpret_cast(&data.filters.type), entity_type::weapon); + result += ImGui::RadioButton("node", reinterpret_cast(&data.filters.type), entity_type::node); + + if (result) + { + data.force_update = true; + } + + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Entity team")) + { + auto result = 0; + + result += ImGui::RadioButton("any", reinterpret_cast(&data.filters.team), entity_team::team_any); + result += ImGui::RadioButton("neutral", reinterpret_cast(&data.filters.team), entity_team::neutral); + result += ImGui::RadioButton("allies", reinterpret_cast(&data.filters.team), entity_team::allies); + result += ImGui::RadioButton("axis", reinterpret_cast(&data.filters.team), entity_team::axis); + result += ImGui::RadioButton("team3", reinterpret_cast(&data.filters.team), entity_team::team3); + + if (result) + { + data.force_update = true; + } + + ImGui::TreePop(); + } + + ImGui::Text("Fields"); + + auto index = 0; + for (auto i = data.filters.fields.begin(); i != data.filters.fields.end(); ++i) + { + if (ImGui::TreeNode(utils::string::va("Filter #%i", index++))) + { + ImGui::InputText("name", &i->first); + ImGui::InputText("value", &i->second); + + if (ImGui::Button("Erase")) + { + data.filters.fields.erase(i); + --i; + } + + ImGui::TreePop(); + } + } + + if (ImGui::Button("Add field filter")) + { + data.filters.fields.push_back({}); + } + } + + ImGui::Separator(); + + for (const auto& info : data.entity_info) + { + if (ImGui::TreeNode(utils::string::va("Entity num %i id %i", info.num, info.id))) + { + ImGui::Text("Commands"); + + if (ImGui::Button("Teleport to")) + { + teleport_to(info.id); + data.force_update = true; + } + + if (ImGui::Button("Teleport to you")) + { + teleport_to_reverse(info.id); + data.force_update = true; + } + + if (info.num != 0 && ImGui::Button("Delete")) + { + delete_entity(info.id); + data.force_update = true; + } + + ImGui::Text("Fields"); + + for (const auto& field : info.fields) + { + if (field.second.empty()) + { + continue; + } + + if (ImGui::Button(field.first.data())) + { + utils::string::set_clipboard_data(field.first); + } + + ImGui::SameLine(); + + if (ImGui::Button(field.second.data())) + { + utils::string::set_clipboard_data(field.second); + } + } + + ImGui::TreePop(); + } + } + + ImGui::End(); + + ImGui::SetNextWindowSizeConstraints(ImVec2(500, 500), ImVec2(1000, 1000)); + ImGui::Begin("Selected fields"); + + static char field_filter[0x100]{}; + ImGui::InputText("field name", field_filter, IM_ARRAYSIZE(field_filter)); + for (auto& field : data.selected_fields) + { + if (strstr(field.first.data(), field_filter) && ImGui::Checkbox(field.first.data(), &field.second)) + { + data.force_update = true; + } + } + + ImGui::End(); + }); + } + } + + class component final : public component_interface + { + public: + void post_unpack() override + { + gui::on_frame(on_frame); + scheduler::loop(update_entity_list, scheduler::pipeline::server, 0ms); + } + }; +} + +REGISTER_COMPONENT(entity_list::component) diff --git a/src/client/component/gui.cpp b/src/client/component/gui.cpp index 3f19ce2a..dd9eb71d 100644 --- a/src/client/component/gui.cpp +++ b/src/client/component/gui.cpp @@ -5,16 +5,22 @@ #include "game/dvars.hpp" #include "scheduler.hpp" +#include "gui.hpp" #include #include +#include extern IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam); namespace gui { + std::unordered_map enabled_menus; + namespace { + utils::concurrency::container>> on_frame_callbacks; + ID3D11Device* device; ID3D11DeviceContext* device_context; bool initialized = false; @@ -47,12 +53,43 @@ namespace gui ImGui_ImplDX11_RenderDrawData(ImGui::GetDrawData()); } - void gui_draw() + void toggle_menu(const std::string& name) { - + enabled_menus[name] = !enabled_menus[name]; } - void on_frame() + void gui_draw() + { + on_frame_callbacks.access([](std::vector>& callbacks) + { + for (const auto& callback : callbacks) + { + callback(); + } + }); + + if (ImGui::BeginMainMenuBar()) + { + if (ImGui::BeginMenu("Windows")) + { + if (ImGui::MenuItem("Asset list")) + { + toggle_menu("asset_list"); + } + + if (ImGui::MenuItem("Entity list")) + { + toggle_menu("entity_list"); + } + + ImGui::EndMenu(); + } + + ImGui::EndMainMenuBar(); + } + } + + void gui_on_frame() { if (!initialized) { @@ -83,11 +120,19 @@ namespace gui return result; } - utils::hook::detour dxgi_swap_chain_present_hook; - void* dxgi_swap_chain_present_stub() + void dxgi_swap_chain_present_stub(utils::hook::assembler& a) { - on_frame(); - return dxgi_swap_chain_present_hook.invoke(); + a.pushad64(); + a.call_aligned(gui_on_frame); + a.popad64(); + + a.mov(r8d, esi); + a.mov(edx, r15d); + a.mov(rcx, rdi); + a.call_aligned(rbx); + a.mov(ecx, eax); + + a.jmp(0x7A14D1_b); } utils::hook::detour wnd_proc_hook; @@ -110,6 +155,12 @@ namespace gui return false; } + if (key == game::K_ESCAPE && down && toggled) + { + toggled = false; + return false; + } + return !toggled; } @@ -123,6 +174,19 @@ namespace gui return !toggled; } + void on_frame(const std::function& callback) + { + on_frame_callbacks.access([callback](std::vector>& callbacks) + { + callbacks.push_back(callback); + }); + } + + bool is_menu_open(const std::string& name) + { + return enabled_menus[name]; + } + class component final : public component_interface { public: @@ -138,7 +202,7 @@ namespace gui void post_unpack() override { - dxgi_swap_chain_present_hook.create(0x7A13A0_b, dxgi_swap_chain_present_stub); + utils::hook::jump(0x7A14C4_b, utils::hook::assemble(dxgi_swap_chain_present_stub), true); wnd_proc_hook.create(0x650F10_b, wnd_proc_stub); } diff --git a/src/client/component/gui.hpp b/src/client/component/gui.hpp index 81edac10..e570de33 100644 --- a/src/client/component/gui.hpp +++ b/src/client/component/gui.hpp @@ -2,7 +2,12 @@ namespace gui { + extern std::unordered_map enabled_menus; + bool gui_key_event(const int local_client_num, const int key, const int down); bool gui_char_event(const int local_client_num, const int key); bool gui_mouse_event(const int local_client_num, int x, int y); + + void on_frame(const std::function& callback); + bool is_menu_open(const std::string& name); } \ No newline at end of file diff --git a/src/client/game/scripting/array.cpp b/src/client/game/scripting/array.cpp new file mode 100644 index 00000000..15b4bc43 --- /dev/null +++ b/src/client/game/scripting/array.cpp @@ -0,0 +1,339 @@ +#include +#include "array.hpp" +#include "script_value.hpp" +#include "execution.hpp" + +namespace scripting +{ + array_value::array_value(unsigned int parent_id, unsigned int id) + : id_(id) + , parent_id_(parent_id) + { + if (!this->id_) + { + return; + } + + const auto value = game::scr_VarGlob->childVariableValue[this->id_ + 0xA000 * (this->parent_id_ & 3)]; + game::VariableValue variable; + variable.u = value.u.u; + variable.type = (game::scriptType_e)value.type; + + this->value_ = variable; + } + + void array_value::operator=(const script_value& _value) + { + if (!this->id_) + { + return; + } + + const auto value = _value.get_raw(); + + const auto variable = &game::scr_VarGlob->childVariableValue[this->id_ + 0xA000 * (this->parent_id_ & 3)]; + game::AddRefToValue(value.type, value.u); + game::RemoveRefToValue(variable->type, variable->u.u); + + variable->type = (char)value.type; + variable->u.u = value.u; + + this->value_ = value; + } + + array::array(const unsigned int id) + : id_(id) + { + this->add(); + } + + array::array(const array& other) : array(other.id_) + { + } + + array::array(array&& other) noexcept + { + this->id_ = other.id_; + other.id_ = 0; + } + + array::array() + { + this->id_ = make_array(); + } + + array::array(std::vector values) + { + this->id_ = make_array(); + + for (const auto& value : values) + { + this->push(value); + } + } + + array::array(std::unordered_map values) + { + this->id_ = make_array(); + + for (const auto& value : values) + { + this->set(value.first, value.second); + } + } + + array::~array() + { + this->release(); + } + + array& array::operator=(const array& other) + { + if (&other != this) + { + this->release(); + this->id_ = other.id_; + this->add(); + } + + return *this; + } + + array& array::operator=(array&& other) noexcept + { + if (&other != this) + { + this->release(); + this->id_ = other.id_; + other.id_ = 0; + } + + return *this; + } + + void array::add() const + { + if (this->id_) + { + game::AddRefToValue(game::SCRIPT_OBJECT, {static_cast(this->id_)}); + } + } + + void array::release() const + { + if (this->id_) + { + game::RemoveRefToValue(game::SCRIPT_OBJECT, {static_cast(this->id_)}); + } + } + + std::vector array::get_keys() const + { + std::vector result; + + const auto offset = 0xA000 * (this->id_ & 3); + auto current = game::scr_VarGlob->objectVariableChildren[this->id_].firstChild; + + for (auto i = offset + current; current; i = offset + current) + { + const auto var = game::scr_VarGlob->childVariableValue[i]; + + if (var.type == game::SCRIPT_NONE) + { + current = var.nextSibling; + continue; + } + + const auto string_value = (game::scr_string_t)((unsigned __int8)var.name_lo + (var.k.keys.name_hi << 8)); + const auto* str = game::SL_ConvertToString(string_value); + + script_value key; + if (string_value < 0x40000 && str) + { + key = str; + } + else + { + key = (string_value - 0x800000) & 0xFFFFFF; + } + + result.push_back(key); + + current = var.nextSibling; + } + + return result; + } + + unsigned int array::size() const + { + return game::scr_VarGlob->objectVariableValue[this->id_].u.f.next; + } + + unsigned int array::push(script_value value) const + { + this->set(this->size(), value); + return this->size(); + } + + void array::erase(const unsigned int index) const + { + const auto variable_id = game::FindVariable(this->id_, (index - 0x800000) & 0xFFFFFF); + if (variable_id) + { + game::RemoveVariableValue(this->id_, variable_id); + } + } + + void array::erase(const std::string& key) const + { + const auto string_value = game::SL_GetString(key.data(), 0); + const auto variable_id = game::FindVariable(this->id_, string_value); + if (variable_id) + { + game::RemoveVariableValue(this->id_, variable_id); + } + } + + script_value array::pop() const + { + const auto value = this->get(this->size() - 1); + this->erase(this->size() - 1); + return value; + } + + script_value array::get(const script_value& key) const + { + if (key.is()) + { + return this->get(key.as()); + } + else + { + return this->get(key.as()); + } + + return {}; + } + + script_value array::get(const std::string& key) const + { + const auto string_value = game::SL_GetString(key.data(), 0); + const auto variable_id = game::FindVariable(this->id_, string_value); + + if (!variable_id) + { + return {}; + } + + const auto value = game::scr_VarGlob->childVariableValue[variable_id + 0xA000 * (this->id_ & 3)]; + game::VariableValue variable; + variable.u = value.u.u; + variable.type = (game::scriptType_e)value.type; + + return variable; + } + + script_value array::get(const unsigned int index) const + { + const auto variable_id = game::FindVariable(this->id_, (index - 0x800000) & 0xFFFFFF); + + if (!variable_id) + { + return {}; + } + + const auto value = game::scr_VarGlob->childVariableValue[variable_id + 0xA000 * (this->id_ & 3)]; + game::VariableValue variable; + variable.u = value.u.u; + variable.type = (game::scriptType_e)value.type; + + return variable; + } + + void array::set(const script_value& key, const script_value& value) const + { + if (key.is()) + { + this->set(key.as(), value); + } + else + { + this->set(key.as(), value); + } + } + + void array::set(const std::string& key, const script_value& _value) const + { + const auto value = _value.get_raw(); + + const auto string_value = game::SL_GetString(key.data(), 0); + const auto variable_id = this->get_value_id(key); + + if (!variable_id) + { + return; + } + + const auto variable = &game::scr_VarGlob->childVariableValue[variable_id + 0xA000 * (this->id_ & 3)]; + + game::AddRefToValue(value.type, value.u); + game::RemoveRefToValue(variable->type, variable->u.u); + + variable->type = (char)value.type; + variable->u.u = value.u; + } + + void array::set(const unsigned int index, const script_value& _value) const + { + const auto value = _value.get_raw(); + const auto variable_id = this->get_value_id(index); + + if (!variable_id) + { + return; + } + + const auto variable = &game::scr_VarGlob->childVariableValue[variable_id + 0xA000 * (this->id_ & 3)]; + + game::AddRefToValue(value.type, value.u); + game::RemoveRefToValue(variable->type, variable->u.u); + + variable->type = (char)value.type; + variable->u.u = value.u; + } + + unsigned int array::get_entity_id() const + { + return this->id_; + } + + unsigned int array::get_value_id(const std::string& key) const + { + const auto string_value = game::SL_GetString(key.data(), 0); + const auto variable_id = game::FindVariable(this->id_, string_value); + + if (!variable_id) + { + return game::GetNewVariable(this->id_, string_value); + } + + return variable_id; + } + + unsigned int array::get_value_id(const unsigned int index) const + { + const auto variable_id = game::FindVariable(this->id_, (index - 0x800000) & 0xFFFFFF); + if (!variable_id) + { + return game::GetNewArrayVariable(this->id_, index); + } + + return variable_id; + } + + entity array::get_raw() const + { + return entity(this->id_); + } +} diff --git a/src/client/game/scripting/array.hpp b/src/client/game/scripting/array.hpp new file mode 100644 index 00000000..adc88d50 --- /dev/null +++ b/src/client/game/scripting/array.hpp @@ -0,0 +1,87 @@ +#pragma once +#include "game/game.hpp" +#include "script_value.hpp" + +namespace scripting +{ + class array_value : public script_value + { + public: + array_value(unsigned int, unsigned int); + void operator=(const script_value&); + private: + unsigned int id_; + unsigned int parent_id_; + }; + + class array final + { + public: + array(); + array(const unsigned int); + + array(std::vector); + array(std::unordered_map); + + array(const array& other); + array(array&& other) noexcept; + + ~array(); + + array& operator=(const array& other); + array& operator=(array&& other) noexcept; + + std::vector get_keys() const; + unsigned int size() const; + + unsigned int push(script_value) const; + void erase(const unsigned int) const; + void erase(const std::string&) const; + script_value pop() const; + + script_value get(const script_value&) const; + script_value get(const std::string&) const; + script_value get(const unsigned int) const; + + void set(const script_value&, const script_value&) const; + void set(const std::string&, const script_value&) const; + void set(const unsigned int, const script_value&) const; + + unsigned int get_entity_id() const; + + unsigned int get_value_id(const std::string&) const; + unsigned int get_value_id(const unsigned int) const; + + entity get_raw() const; + + array_value operator[](const int index) const + { + return {this->id_, this->get_value_id(index)}; + } + + array_value operator[](const std::string& key) const + { + return {this->id_, this->get_value_id(key)}; + } + + template + array_value operator[](const script_value& key) const + { + if (key.is()) + { + return { this->id_, this->get_value_id(key.as()) }; + } + + if (key.is()) + { + return { this->id_, this->get_value_id(key.as()) }; + } + } + + private: + void add() const; + void release() const; + + unsigned int id_; + }; +} diff --git a/src/client/game/scripting/execution.cpp b/src/client/game/scripting/execution.cpp index 45b10d10..c944541f 100644 --- a/src/client/game/scripting/execution.cpp +++ b/src/client/game/scripting/execution.cpp @@ -235,4 +235,15 @@ namespace scripting return get_custom_field(entity, field); } + + unsigned int make_array() + { + unsigned int index = 0; + const auto variable = game::AllocVariable(&index); + variable->w.type = game::SCRIPT_ARRAY; + variable->u.f.prev = 0; + variable->u.f.next = 0; + + return index; + } } diff --git a/src/client/game/scripting/execution.hpp b/src/client/game/scripting/execution.hpp index 03f0f652..6b39ddfb 100644 --- a/src/client/game/scripting/execution.hpp +++ b/src/client/game/scripting/execution.hpp @@ -1,6 +1,7 @@ #pragma once #include "game/game.hpp" #include "entity.hpp" +#include "array.hpp" #include "script_value.hpp" namespace scripting @@ -35,4 +36,6 @@ namespace scripting script_value get_entity_field(const entity& entity, const std::string& field); void notify(const entity& entity, const std::string& event, const std::vector& arguments); + + unsigned int make_array(); } diff --git a/src/client/game/scripting/script_value.cpp b/src/client/game/scripting/script_value.cpp index 5fd70461..5b1f6be7 100644 --- a/src/client/game/scripting/script_value.cpp +++ b/src/client/game/scripting/script_value.cpp @@ -1,6 +1,9 @@ #include #include "script_value.hpp" #include "entity.hpp" +#include "array.hpp" + +#include namespace scripting { @@ -78,6 +81,15 @@ namespace scripting this->value_ = variable; } + script_value::script_value(const array& value) + { + game::VariableValue variable{}; + variable.type = game::SCRIPT_OBJECT; + variable.u.pointerValue = value.get_entity_id(); + + this->value_ = variable; + } + script_value::script_value(const vector& value) { game::VariableValue variable{}; @@ -250,6 +262,30 @@ namespace scripting return entity(this->get_raw().u.pointerValue); } + /*************************************************************** + * Array + **************************************************************/ + + template <> + bool script_value::is() const + { + if (this->get_raw().type != game::SCRIPT_OBJECT) + { + return false; + } + + const auto id = this->get_raw().u.uintValue; + const auto type = game::scr_VarGlob->objectVariableValue[id].w.type; + + return type == game::SCRIPT_ARRAY; + } + + template <> + array script_value::get() const + { + return array(this->get_raw().u.uintValue); + } + /*************************************************************** * Vector **************************************************************/ @@ -274,4 +310,34 @@ namespace scripting { return this->value_.get(); } + + std::string script_value::to_string() const + { + if (this->is()) + { + return utils::string::va("%i", this->as()); + } + + if (this->is()) + { + return utils::string::va("%f", this->as()); + } + + if (this->is()) + { + return this->as(); + } + + if (this->is()) + { + const auto vec = this->as(); + return utils::string::va("(%g, %g, %g)", + vec.get_x(), + vec.get_y(), + vec.get_z() + ); + } + + return {}; + } } diff --git a/src/client/game/scripting/script_value.hpp b/src/client/game/scripting/script_value.hpp index df8a95b6..7cee8b17 100644 --- a/src/client/game/scripting/script_value.hpp +++ b/src/client/game/scripting/script_value.hpp @@ -6,6 +6,7 @@ namespace scripting { class entity; + class array; class script_value { @@ -24,6 +25,7 @@ namespace scripting script_value(const std::string& value); script_value(const entity& value); + script_value(const array& value); script_value(const vector& value); @@ -43,10 +45,13 @@ namespace scripting const game::VariableValue& get_raw() const; + std::string to_string() const; + + variable_value value_{}; + private: template T get() const; - variable_value value_{}; }; } diff --git a/src/client/game/structs.hpp b/src/client/game/structs.hpp index 82d8f2c0..3eaf0d67 100644 --- a/src/client/game/structs.hpp +++ b/src/client/game/structs.hpp @@ -676,6 +676,7 @@ namespace game SCRIPT_END = 8, SCRIPT_FUNCTION = 9, SCRIPT_STRUCT = 19, + SCRIPT_ENTITY = 21, SCRIPT_ARRAY = 22, }; diff --git a/src/client/game/symbols.hpp b/src/client/game/symbols.hpp index 7fbaf1b8..fea2f668 100644 --- a/src/client/game/symbols.hpp +++ b/src/client/game/symbols.hpp @@ -9,6 +9,7 @@ namespace game WEAK symbol AddRefToValue{0x5C0EB0}; WEAK symbol AddRefToObject{0x5C0EA0}; WEAK symbol AllocThread{0x5C1200}; + WEAK symbol AllocVariable{0x5C1260}; WEAK symbol RemoveRefToValue{0x5C29B0}; WEAK symbol RemoveRefToObject{0x5C28A0}; @@ -58,6 +59,11 @@ namespace game WEAK symbol FindVariable{0x5C1D50}; WEAK symbol FindEntityId{0x5C1C50}; WEAK symbol GetEntityFieldValue{0x5C6100}; + WEAK symbol GetVariable{0x5C2690}; + WEAK symbol GetNewVariable{0x5C22B0}; + WEAK symbol GetNewArrayVariable{0x5C2130}; + WEAK symbol SetNewVariableValue{0x5C5EA0}; + WEAK symbol RemoveVariableValue{0x5C2A50}; WEAK symbol G_GetWeaponForName{0x51B260}; WEAK symbol @@ -80,6 +86,7 @@ namespace game WEAK symbol Scr_GetEntityIdRef{0x5C56C0}; WEAK symbol Scr_SetObjectField{0x512190}; WEAK symbol Scr_NotifyId{0x5C8240}; + WEAK symbol Scr_GetSelf{0x5C57C0}; WEAK symbol VM_Execute{0x5C8DB0}; diff --git a/src/common/utils/string.cpp b/src/common/utils/string.cpp index 653ecff2..3f90744c 100644 --- a/src/common/utils/string.cpp +++ b/src/common/utils/string.cpp @@ -105,6 +105,22 @@ namespace utils::string return {}; } + void set_clipboard_data(const std::string& text) + { + const auto len = text.size() + 1; + const auto mem = GlobalAlloc(GMEM_MOVEABLE, len); + + memcpy(GlobalLock(mem), text.data(), len); + GlobalUnlock(mem); + + if (OpenClipboard(nullptr)) + { + EmptyClipboard(); + SetClipboardData(CF_TEXT, mem); + CloseClipboard(); + } + } + void strip(const char* in, char* out, int max) { if (!in || !out) return; diff --git a/src/common/utils/string.hpp b/src/common/utils/string.hpp index 20fa4827..924e5441 100644 --- a/src/common/utils/string.hpp +++ b/src/common/utils/string.hpp @@ -90,6 +90,7 @@ namespace utils::string std::string dump_hex(const std::string& data, const std::string& separator = " "); std::string get_clipboard_data(); + void set_clipboard_data(const std::string& text); void strip(const char* in, char* out, int max);