#include "Scripts.hpp" #include "core/backend/FiberPool.hpp" #include "core/backend/ScriptMgr.hpp" #include "core/frontend/Notifications.hpp" #include "game/gta/data/ScriptNames.hpp" #include "game/gta/data/StackSizes.hpp" #include "game/gta/Scripts.hpp" #include "game/gta/Natives.hpp" #include "game/pointers/Pointers.hpp" #include "types/rage/atArray.hpp" #include "types/script/GtaThread.hpp" #include "types/script/scrProgram.hpp" #include "types/script/CGameScriptHandlerNetComponent.hpp" #include "types/network/CNetGamePlayer.hpp" #include "misc/cpp/imgui_stdlib.h" namespace YimMenu::Submenus { static constexpr std::pair stackSizes[] = { {"MICRO", static_cast(eStackSizes::MICRO)}, {"MINI", static_cast(eStackSizes::MINI)}, {"DEFAULT", static_cast(eStackSizes::DEFAULT)}, {"SPECIAL_ABILITY", static_cast(eStackSizes::SPECIAL_ABILITY)}, {"FRIEND", static_cast(eStackSizes::FRIEND)}, {"SHOP", static_cast(eStackSizes::SHOP)}, {"CELLPHONE", static_cast(eStackSizes::CELLPHONE)}, {"VEHICLE_SPAWN", static_cast(eStackSizes::VEHICLE_SPAWN)}, {"CAR_MOD_SHOP", static_cast(eStackSizes::CAR_MOD_SHOP)}, {"PAUSE_MENU_SCRIPT", static_cast(eStackSizes::PAUSE_MENU_SCRIPT)}, {"APP_INTERNET", static_cast(eStackSizes::APP_INTERNET)}, {"MULTIPLAYER_MISSION", static_cast(eStackSizes::MULTIPLAYER_MISSION)}, {"CONTACTS_APP", static_cast(eStackSizes::CONTACTS_APP)}, {"INTERACTION_MENU", static_cast(eStackSizes::INTERACTION_MENU)}, {"SCRIPT_XML", static_cast(eStackSizes::SCRIPT_XML)}, {"PROPERTY_INT", static_cast(eStackSizes::PROPERTY_INT)}, {"ACTIVITY_CREATOR_INT", static_cast(eStackSizes::ACTIVITY_CREATOR_INT)}, {"SMPL_INTERIOR", static_cast(eStackSizes::SMPL_INTERIOR)}, {"WAREHOUSE", static_cast(eStackSizes::WAREHOUSE)}, {"IE_DELIVERY", static_cast(eStackSizes::IE_DELIVERY)}, {"SHOP_CONTROLLER", static_cast(eStackSizes::SHOP_CONTROLLER)}, {"AM_MP_YACHT", static_cast(eStackSizes::AM_MP_YACHT)}, {"INGAMEHUD", static_cast(eStackSizes::INGAMEHUD)}, {"TRANSITION", static_cast(eStackSizes::TRANSITION)}, {"FMMC_LAUNCHER", static_cast(eStackSizes::FMMC_LAUNCHER)}, {"MULTIPLAYER_FREEMODE", static_cast(eStackSizes::MULTIPLAYER_FREEMODE)}, {"MISSION", static_cast(eStackSizes::MISSION)}, {"MP_LAUNCH_SCRIPT", static_cast(eStackSizes::MP_LAUNCH_SCRIPT)} }; static void RenderBytecode(rage::scrProgram* program) { constexpr int bytesPerRow = 16; const std::uint32_t codeSize = program->GetFullCodeSize(); const std::uint32_t totalRows = (codeSize + bytesPerRow - 1) / bytesPerRow; static bool shouldJump = false; static float targetScroll = -1.0f; static char offsetInput[9] = ""; ImGui::SetNextItemWidth(150); ImGui::InputText("##jumpoffset", offsetInput, IM_ARRAYSIZE(offsetInput)); ImGui::SameLine(); if (ImGui::Button("Jump to Offset")) { char* end = nullptr; std::uint32_t offset = strtoul(offsetInput, &end, 0); if (end != offsetInput && offset < codeSize) { std::uint32_t row = offset / bytesPerRow; targetScroll = row * ImGui::GetFrameHeightWithSpacing(); shouldJump = true; } } ImGui::BeginChild("##bytecode", ImVec2(610, 400), ImGuiChildFlags_Borders, ImGuiWindowFlags_HorizontalScrollbar); if (shouldJump && targetScroll >= 0.0f) { ImGui::SetScrollY(targetScroll); shouldJump = false; } ImGuiListClipper clipper; clipper.Begin(totalRows); while (clipper.Step()) { for (int row = clipper.DisplayStart; row < clipper.DisplayEnd; ++row) { std::uint32_t offset = row * bytesPerRow; ImGui::Text("%08X: ", offset); ImGui::SameLine(80); for (int i = 0; i < bytesPerRow; ++i) { std::uint32_t index = offset + i; if (index >= codeSize) break; if (auto byte = program->GetCodeAddress(index)) { char hexStr[3]; snprintf(hexStr, sizeof(hexStr), "%02X", *byte); ImGui::SetNextItemWidth(24); ImGui::PushID(index); if (ImGui::InputText("##byte", hexStr, sizeof(hexStr), ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_EnterReturnsTrue)) { char* end = nullptr; std::uint8_t newVal = static_cast(strtoul(hexStr, &end, 16)); if (end != hexStr) *byte = newVal; // this will conflict with the ScriptPatches class, but still useful to keep for quick testings } ImGui::PopID(); if (ImGui::IsItemActive() && ImGui::IsItemHovered()) ImGui::SetTooltip("Press ENTER to write."); if (i < bytesPerRow - 1) ImGui::SameLine(); } } } } clipper.End(); ImGui::EndChild(); } std::shared_ptr BuildScriptsMenu() { auto menu = std::make_unique("Scripts"); auto threads = std::make_unique("Threads"); auto script = std::make_unique("Start Script"); threads->AddItem(std::make_unique([] { static rage::scrThread* curThread = nullptr; static rage::scrProgram* curProgram = nullptr; if (!Pointers.ScriptThreads || Pointers.ScriptThreads->size() == 0) { curThread = nullptr; curProgram = nullptr; return ImGui::TextDisabled("None"); } if (ImGui::BeginCombo("Thread", curThread ? curThread->m_ScriptName : "(Select)")) { for (auto thread : *Pointers.ScriptThreads) { if (thread) { if (thread->m_Context.m_State == rage::scrThread::State::KILLED || thread->m_Context.m_StackSize == 0) continue; ImGui::PushID(thread->m_Context.m_ThreadId); if (ImGui::Selectable(thread->m_ScriptName, curThread == thread)) { curThread = thread; curProgram = Scripts::FindScriptProgram(thread->m_ScriptHash); } if (curThread == thread) ImGui::SetItemDefaultFocus(); ImGui::PopID(); } } ImGui::EndCombo(); } if (!curThread || !curProgram || !curProgram->IsValid() || curProgram->m_RefCount == 0) { curThread = nullptr; curProgram = nullptr; return; } ImGui::Combo("State", (int*)&curThread->m_Context.m_State, "Idle\0Running\0Killed\0Paused\0Unk4\0"); if (curThread->m_Context.m_State == rage::scrThread::State::KILLED) { ImGui::Text("Exit Reason: %s", curThread->m_ErrorMessage); } else { if (ImGui::Button("Kill")) { curThread->Kill(); curThread->m_Context.m_State = rage::scrThread::State::KILLED; } ImGui::SameLine(); if (ImGui::Button("Log Labels")) { FiberPool::Push([] { for (int i = 0; i < curProgram->m_StringsCount; i++) { if (auto str = curProgram->GetString(i)) { if (HUD::DOES_TEXT_LABEL_EXIST(str)) { LOGF(INFO, "{} - {} (0x{:X}): {}", i, str, Joaat(str), HUD::GET_FILENAME_FOR_AUDIO_CONVERSATION(str)); } } } }); } if (ImGui::TreeNode("Info")) { if (auto netComponent = reinterpret_cast(curThread)->m_NetComponent) { if (auto host = netComponent->GetHost()) { ImGui::Text("Host: %s", host->GetName()); } ImGui::SameLine(); ImGui::BeginDisabled(netComponent->IsLocalPlayerHost()); if (ImGui::SmallButton("Take Control")) { FiberPool::Push([] { Scripts::ForceScriptHost(curThread); }); } ImGui::EndDisabled(); } ImGui::BeginGroup(); ImGui::Text("Thread ID: %d", curThread->m_Context.m_ThreadId); ImGui::Text("Stack Size: %d", curThread->m_Context.m_StackSize); ImGui::Text("Stack Pointer: 0x%X", curThread->m_Context.m_StackPointer); ImGui::Text("Program Counter: 0x%X", curThread->m_Context.m_ProgramCounter); // This is not really accurate (always points to the WAIT) ImGui::Text("Code Size: %d", curProgram->m_CodeSize); ImGui::EndGroup(); ImGui::SameLine(); ImGui::BeginGroup(); ImGui::Text("Arg Count: %d", curProgram->m_ArgCount); ImGui::Text("Local Count: %d", curProgram->m_LocalCount); ImGui::Text("Global Count: %d", curProgram->m_GlobalCount); ImGui::Text("Native Count: %d", curProgram->m_NativeCount); ImGui::Text("String Count: %d", curProgram->m_StringsCount); ImGui::EndGroup(); ImGui::TreePop(); } RenderBytecode(curProgram); } })); script->AddItem(std::make_unique([] { static std::string scriptSearch = ""; static std::string scriptName = ""; static std::string stackSizeName = stackSizes[0].first; static int stackSize = 0; static std::int64_t* args = nullptr; static int argCount = 0; static int previousArgCount = 0; static bool pauseAfterStarting = false; ImGui::InputTextWithHint("Script Name", "Search", &scriptSearch); if (ImGui::BeginCombo("Stack Size", stackSizeName.c_str())) { for (auto& p : stackSizes) { if (ImGui::Selectable(std::format("{} ({})", p.first, (int)p.second).data(), stackSize == p.second)) { stackSizeName = p.first; stackSize = p.second; } if (p.second == stackSize) ImGui::SetItemDefaultFocus(); } ImGui::EndCombo(); } if (ImGui::InputInt("Arg Count", &argCount)) { if (argCount < 0) // should clamp this to a max value? argCount = 0; } if (argCount != previousArgCount) { std::int64_t* temp = nullptr; if (argCount > 0) { temp = new std::int64_t[argCount]{0}; if (args) std::memcpy(temp, args, sizeof(int64_t) * std::min(argCount, previousArgCount)); } delete[] args; args = temp; previousArgCount = argCount; } for (int i = 0; i < argCount; i++) { ImGui::Text("Arg[%d]", i); ImGui::SameLine(); ImGui::PushID(i); ImGui::InputScalar("##arg", ImGuiDataType_S64, &args[i]); ImGui::PopID(); } if (ImGui::BeginListBox("##scripts")) { std::string lowerSearch = scriptSearch; std::transform(lowerSearch.begin(), lowerSearch.end(), lowerSearch.begin(), ::tolower); for (auto& script : scriptNames) { std::string lowerScript = script; std::transform(lowerScript.begin(), lowerScript.end(), lowerScript.begin(), ::tolower); if (lowerScript.find(lowerSearch) != std::string::npos) { if (ImGui::Selectable(script, scriptName == script)) { scriptSearch = script; scriptName = script; } } } ImGui::EndListBox(); } ImGui::Checkbox("Pause After Starting", &pauseAfterStarting); if (ImGui::Button("Start Script")) { FiberPool::Push([] { auto hash = Joaat(scriptName); if (!SCRIPT::DOES_SCRIPT_WITH_NAME_HASH_EXIST(hash)) { Notifications::Show("Start Script", "Script does not exist.", NotificationType::Error); return; } if (SCRIPT::GET_NUMBER_OF_THREADS_RUNNING_THE_SCRIPT_WITH_THIS_HASH(hash) > 0) { Notifications::Show("Start Script", "Script is already running.", NotificationType::Error); return; } if (MISC::GET_NUMBER_OF_FREE_STACKS_OF_THIS_SIZE(stackSize) == 0) { Notifications::Show("Start Script", "No free stack.", NotificationType::Error); return; } while (!SCRIPT::HAS_SCRIPT_WITH_NAME_HASH_LOADED(hash)) { SCRIPT::REQUEST_SCRIPT_WITH_NAME_HASH(hash); ScriptMgr::Yield(); } int id = 0; if (args && argCount > 0) { id = BUILTIN::START_NEW_SCRIPT_WITH_NAME_HASH_AND_ARGS(hash, args, argCount, stackSize); } else { id = BUILTIN::START_NEW_SCRIPT_WITH_NAME_HASH(hash, stackSize); } if (pauseAfterStarting) { if (auto thread = Scripts::FindScriptThreadByID(id)) thread->m_Context.m_State = rage::scrThread::State::PAUSED; } SCRIPT::SET_SCRIPT_WITH_NAME_HASH_AS_NO_LONGER_NEEDED(hash); Notifications::Show("Start Script", std::format("Started script with ID {}.", id), NotificationType::Success); }); } })); menu->AddItem(std::make_shared([] { ImGui::BeginTabBar("Scripts"); })); menu->AddItem(std::move(threads)); menu->AddItem(std::move(script)); menu->AddItem(std::make_shared([] { ImGui::EndTabBar(); })); return menu; } }