mirror of
https://github.com/YimMenu/YimMenuV2.git
synced 2026-10-12 02:08:09 +08:00
feat(debug): Script Hex Editor (#361)
This commit is contained in:
1 parent
26885b778d
commit
684f6ee567
5 files changed
+233
-50
No files matched your search
@@ -9,6 +9,7 @@
|
||||
#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"
|
||||
@@ -46,23 +47,100 @@ namespace YimMenu::Submenus
|
||||
{"MP_LAUNCH_SCRIPT", static_cast<int>(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<std::uint8_t>(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<Category> BuildScriptsMenu()
|
||||
{
|
||||
auto scripts = std::make_unique<Category>("Scripts");
|
||||
auto menu = std::make_unique<Category>("Scripts");
|
||||
|
||||
auto threads = std::make_unique<Group>("Threads");
|
||||
auto script = std::make_unique<Group>("Start Script");
|
||||
|
||||
static rage::scrThread* curThread = nullptr;
|
||||
static std::string scriptSearch = "";
|
||||
static std::string scriptName = "";
|
||||
static std::string stackSizeName = stackSizes[0].first;
|
||||
static int stackSize = 0;
|
||||
auto threads = std::make_unique<TabItem>("Threads");
|
||||
auto script = std::make_unique<TabItem>("Start Script");
|
||||
|
||||
threads->AddItem(std::make_unique<ImGuiItem>([] {
|
||||
static rage::scrThread* curThread = nullptr;
|
||||
static rage::scrProgram* curProgram = nullptr;
|
||||
|
||||
if (!Pointers.ScriptThreads || Pointers.ScriptThreads->size() == 0)
|
||||
{
|
||||
curThread = nullptr;
|
||||
curThread = nullptr;
|
||||
curProgram = nullptr;
|
||||
return ImGui::TextDisabled("None");
|
||||
}
|
||||
|
||||
@@ -79,7 +157,8 @@ namespace YimMenu::Submenus
|
||||
|
||||
if (ImGui::Selectable(thread->m_ScriptName, curThread == thread))
|
||||
{
|
||||
curThread = thread;
|
||||
curThread = thread;
|
||||
curProgram = Scripts::FindScriptProgram(thread->m_ScriptHash);
|
||||
}
|
||||
|
||||
if (curThread == thread)
|
||||
@@ -92,37 +171,92 @@ namespace YimMenu::Submenus
|
||||
ImGui::EndCombo();
|
||||
}
|
||||
|
||||
if (!curThread)
|
||||
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 (auto netComponent = reinterpret_cast<GtaThread*>(curThread)->m_NetComponent)
|
||||
if (curThread->m_Context.m_State == rage::scrThread::State::KILLED)
|
||||
{
|
||||
if (auto host = netComponent->GetHost())
|
||||
ImGui::Text("Exit Reason: %s", curThread->m_ErrorMessage);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (ImGui::Button("Kill"))
|
||||
{
|
||||
ImGui::Text("Host: %s", host->GetName());
|
||||
curThread->Kill();
|
||||
curThread->m_Context.m_State = rage::scrThread::State::KILLED;
|
||||
}
|
||||
ImGui::SameLine();
|
||||
ImGui::BeginDisabled(netComponent->IsLocalPlayerHost());
|
||||
if (ImGui::SmallButton("Take Control"))
|
||||
if (ImGui::Button("Log Labels"))
|
||||
{
|
||||
FiberPool::Push([] {
|
||||
Scripts::ForceScriptHost(curThread);
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
ImGui::EndDisabled();
|
||||
}
|
||||
|
||||
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%p", &curThread->m_Context.m_StackPointer);
|
||||
ImGui::Text("Program Counter: 0x%p", &curThread->m_Context.m_ProgramCounter); // This is not really accurate (always points to the WAIT)
|
||||
if (curThread->m_Context.m_State == rage::scrThread::State::KILLED)
|
||||
ImGui::Text("Exit Reason: %s", curThread->m_ErrorMessage);
|
||||
if (ImGui::TreeNode("Info"))
|
||||
{
|
||||
if (auto netComponent = reinterpret_cast<GtaThread*>(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<ImGuiItem>([] {
|
||||
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()))
|
||||
@@ -142,6 +276,33 @@ namespace YimMenu::Submenus
|
||||
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;
|
||||
@@ -163,6 +324,8 @@ namespace YimMenu::Submenus
|
||||
ImGui::EndListBox();
|
||||
}
|
||||
|
||||
ImGui::Checkbox("Pause After Starting", &pauseAfterStarting);
|
||||
|
||||
if (ImGui::Button("Start Script"))
|
||||
{
|
||||
FiberPool::Push([] {
|
||||
@@ -192,14 +355,36 @@ namespace YimMenu::Submenus
|
||||
ScriptMgr::Yield();
|
||||
}
|
||||
|
||||
BUILTIN::START_NEW_SCRIPT_WITH_NAME_HASH(hash, stackSize);
|
||||
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);
|
||||
});
|
||||
}
|
||||
}));
|
||||
|
||||
scripts->AddItem(std::move(threads));
|
||||
scripts->AddItem(std::move(script));
|
||||
return scripts;
|
||||
menu->AddItem(std::make_shared<ImGuiItem>([] {
|
||||
ImGui::BeginTabBar("Scripts");
|
||||
}));
|
||||
menu->AddItem(std::move(threads));
|
||||
menu->AddItem(std::move(script));
|
||||
menu->AddItem(std::make_shared<ImGuiItem>([] {
|
||||
ImGui::EndTabBar();
|
||||
}));
|
||||
return menu;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "Weapons.hpp"
|
||||
#include "core/backend/FiberPool.hpp"
|
||||
#include "core/backend/ScriptMgr.hpp"
|
||||
#include "game/backend/Self.hpp"
|
||||
#include "game/gta/data/Weapons.hpp"
|
||||
#include "game/gta/Natives.hpp"
|
||||
@@ -25,6 +26,9 @@ namespace YimMenu::Submenus
|
||||
|
||||
static bool init = [] {
|
||||
FiberPool::Push([] {
|
||||
while (Scripts::IsScriptActive("startup"_J))
|
||||
ScriptMgr::Yield();
|
||||
|
||||
if (auto id = Scripts::StartScript("mp_weapons"_J))
|
||||
{
|
||||
if (auto thread = Scripts::FindScriptThreadByID(id))
|
||||
@@ -53,9 +57,6 @@ namespace YimMenu::Submenus
|
||||
return true;
|
||||
}();
|
||||
|
||||
if (weaponDisplays.empty())
|
||||
return ImGui::TextDisabled("Loading...");
|
||||
|
||||
ImGui::BeginCombo("Weapons", selectedWeapon.c_str());
|
||||
if (ImGui::IsItemActive() && !ImGui::IsPopupOpen("##weaponspopup"))
|
||||
{
|
||||
|
||||
@@ -71,9 +71,6 @@ namespace YimMenu::Submenus
|
||||
return true;
|
||||
}();
|
||||
|
||||
if (vehicleNames.empty())
|
||||
return ImGui::TextDisabled("Loading...");
|
||||
|
||||
static char search[64];
|
||||
ImGui::SetNextItemWidth(300.f);
|
||||
ImGui::InputTextWithHint("Name", "Search", search, sizeof(search));
|
||||
@@ -105,6 +102,9 @@ namespace YimMenu::Submenus
|
||||
std::transform(lower.begin(), lower.end(), lower.begin(), ::tolower);
|
||||
for (int veh = 0; veh < vehicleNames.size(); veh++)
|
||||
{
|
||||
if (vehicleNames[veh].empty())
|
||||
continue;
|
||||
|
||||
auto hash = g_VehicleHashes[veh];
|
||||
auto name = vehicleNames[veh];
|
||||
auto lowerName = name;
|
||||
|
||||
@@ -117,20 +117,17 @@ namespace YimMenu::Submenus
|
||||
{
|
||||
if (auto NetComponent = reinterpret_cast<GtaThread*>(eventThread)->m_NetComponent)
|
||||
{
|
||||
if (auto eventProgram = Scripts::FindScriptProgram(randomEventScripts[(int)selectedEvent]))
|
||||
if (NetComponent->IsLocalPlayerHost())
|
||||
{
|
||||
if (NetComponent->IsLocalPlayerHost())
|
||||
{
|
||||
std::string ptrName = "SetFMContentScriptServerState" + std::to_string(selectedEvent);
|
||||
ScriptFunction setFMContentScriptServerState(randomEventScripts[(int)selectedEvent], ScriptPointer(ptrName, "5D ? ? ? 55 2E 00 5D").Add(1).Rip());
|
||||
setFMContentScriptServerState.Call<void>(3);
|
||||
}
|
||||
else
|
||||
{
|
||||
std::string ptrName = "SetFMContentScriptClientState" + std::to_string(selectedEvent);
|
||||
ScriptFunction setFMContentScriptClientState(randomEventScripts[(int)selectedEvent], ScriptPointer(ptrName, "5D ? ? ? 55 08 00 74").Add(1).Rip());
|
||||
setFMContentScriptClientState.Call<void>(3);
|
||||
}
|
||||
std::string ptrName = "SetFMContentScriptServerState" + std::to_string(selectedEvent);
|
||||
ScriptFunction setFMContentScriptServerState(randomEventScripts[(int)selectedEvent], ScriptPointer(ptrName, "5D ? ? ? 55 2E 00 5D").Add(1).Rip());
|
||||
setFMContentScriptServerState.Call<void>(3);
|
||||
}
|
||||
else
|
||||
{
|
||||
std::string ptrName = "SetFMContentScriptClientState" + std::to_string(selectedEvent);
|
||||
ScriptFunction setFMContentScriptClientState(randomEventScripts[(int)selectedEvent], ScriptPointer(ptrName, "5D ? ? ? 55 08 00 74").Add(1).Rip());
|
||||
setFMContentScriptClientState.Call<void>(3);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ namespace YimMenu::Scripts
|
||||
}
|
||||
|
||||
int id = 0;
|
||||
if (args)
|
||||
if (args && argCount > 0)
|
||||
{
|
||||
id = BUILTIN::START_NEW_SCRIPT_WITH_NAME_HASH_AND_ARGS(hash, args, argCount, static_cast<int>(stackSize));
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user