Skip to content

Commit

Permalink
feat: Syringe support
Browse files Browse the repository at this point in the history
Remove old hook system with Syringe compatible DEFINE_HOOK-style hook
definitions. Provide a fallback detour implementation to use the hooks
if Syringe is not available.

Remove almost all use of original storage system. Instead use static
instance for game specific global data.
  • Loading branch information
shmocz committed Sep 12, 2024
1 parent 4acb841 commit 1af2a45
Show file tree
Hide file tree
Showing 16 changed files with 459 additions and 901 deletions.
77 changes: 3 additions & 74 deletions src/commands_builtin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,29 +19,8 @@

using ra2yrcpp::command::get_cmd;

/// Adds two unsigned integers, and returns result in EAX
struct TestProgram : Xbyak::CodeGenerator {
TestProgram() {
mov(eax, ptr[esp + 1 * 0x4]);
add(eax, ptr[esp + 2 * 0x4]);
entry_size = getSize();
ret();
}

auto get_code() { return getCode<i32 __cdecl (*)(const i32, const i32)>(); }

std::size_t entry_size;
};

static void test_cb(hook::Hook*, void* data, X86Regs*) {
auto I = static_cast<ra2yrcpp::InstrumentationService*>(data);
std::string s("0xbeefdead");
I->store_value<vecu8>("test_key", s.begin(), s.end());
}

// TODO(shmocz): ditch the old hook/cb test functions to use the common
// functions
std::map<std::string, ra2yrcpp::cmd_t::handler_t> get_commands_nn() {
std::map<std::string, ra2yrcpp::cmd_t::handler_t>
ra2yrcpp::commands_builtin::get_commands() {
return {
get_cmd<ra2yrproto::commands::StoreValue>([](auto* Q) {
// NB: ensure correct radix
Expand Down Expand Up @@ -75,55 +54,5 @@ std::map<std::string, ra2yrcpp::cmd_t::handler_t> get_commands_nn() {
c.set_value(ra2yrcpp::to_string(
*reinterpret_cast<vecu8*>(Q->I()->get_value(c.key(), false))));
}),
get_cmd<ra2yrproto::commands::HookableCommand>([](auto* Q) {
static TestProgram t;
auto t_addr = t.get_code();
t_addr(3, 3);

auto& res = Q->command_data();
res.set_address_test_function(reinterpret_cast<u64>(t_addr));
res.set_address_test_callback(reinterpret_cast<u64>(&test_cb));
res.set_code_size(t.entry_size);
}),
get_cmd<ra2yrproto::commands::AddCallback>([](auto* Q) {
auto& a = Q->command_data();
hook::HookCallback CB{.func = reinterpret_cast<hook::Hook::hook_cb_t>(
a.callback_address()),
.user_data = Q->I()};
Q->I()
->hooks()
.at(static_cast<std::uintptr_t>(a.hook_address()))
.add_callback(CB);
}),
get_cmd<ra2yrproto::commands::CreateHooks>([](auto* Q) {
// TODO(shmocz): put these to utility function and share code with
// Hook code.
#ifdef _WIN32
auto P = process::get_current_process();
std::vector<process::thread_id_t> ns(Q->I()->get_connection_threads());

auto& a = Q->command_data();

// suspend threads?
if (!a.no_suspend_threads()) {
ns.push_back(process::get_current_tid());
P.suspend_threads(ns);
}

// create hooks
for (auto& h : a.hooks()) {
Q->I()->create_hook(h.name(),
static_cast<std::uintptr_t>(h.address()),
h.code_length());
}
if (!a.no_suspend_threads()) {
P.resume_threads(ns);
}
#endif
})};
}

std::map<std::string, ra2yrcpp::command::iservice_cmd::handler_t>
ra2yrcpp::commands_builtin::get_commands() {
return get_commands_nn();
};
}
62 changes: 12 additions & 50 deletions src/commands_yr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@

#include "command/is_command.hpp"
#include "config.hpp"
#include "errors.hpp"
#include "hooks_yr.hpp"
#include "logging.hpp"
#include "protocol/helpers.hpp"
Expand All @@ -25,8 +24,6 @@ using ra2yrcpp::command::get_async_cmd;
using ra2yrcpp::command::get_cmd;
using ra2yrcpp::command::message_result;

using ra2yrcpp::hooks_yr::ensure_storage_value;
using ra2yrcpp::hooks_yr::get_data;
using ra2yrcpp::hooks_yr::get_gameloop_command;

// TODO(shmocz): don't allow deploying of already deployed object
Expand Down Expand Up @@ -96,66 +93,32 @@ auto unit_command() {
});
}

auto create_callbacks() {
return get_cmd<ra2yrproto::commands::CreateCallbacks>([](auto* Q) {
auto [lk_s, s] = Q->I()->aq_storage();
// Create main game data structure
// TODO(shmocz): initialize elsewhere
ra2yrcpp::hooks_yr::init_callbacks(get_data(Q->I()));
auto cbs = ra2yrcpp::hooks_yr::get_callbacks(Q->I());
auto [lk, hhooks] = Q->I()->aq_hooks();
for (auto& [k, v] : *cbs) {
auto target = v->target();
auto h = std::find_if(hhooks->begin(), hhooks->end(), [&](auto& a) {
return (a.second.name() == target);
});
if (h == hhooks->end()) {
throw std::runtime_error(fmt::format("No such hook {}", target));
}

const std::string hook_name = k;
auto& tmp_cbs = h->second.callbacks();
// TODO(shmocz): throw standard exception
if (std::find_if(tmp_cbs.begin(), tmp_cbs.end(), [&hook_name](auto& a) {
return a.name == hook_name;
}) != tmp_cbs.end()) {
throw std::runtime_error(fmt::format(
"Hook {} already has a callback {}", target, hook_name));
}

iprintf("add callback, target={} cb={}", target, hook_name);
auto cb = v.get();
cb->add_to_hook(&h->second, Q->I());
}
});
}

auto get_game_state() {
return get_cmd<ra2yrproto::commands::GetGameState>([](auto* Q) {
Q->I()->lock_storage();
auto* D = get_data(Q->I());
auto& M = ra2yrcpp::hooks_yr::MainData::get();
M.lock();
auto* D = M.data();
// Unpause game if single-step mode.
if (D->cfg.single_step() && D->game_paused.get()) {
Q->I()->unlock_storage();
M.unlock();
D->game_paused.wait(true);
Q->I()->lock_storage();
M.lock();
}

Q->command_data().mutable_state()->CopyFrom(D->sv.game_state());
if (D->cfg.single_step()) {
D->game_paused.store(false);
}
Q->I()->unlock_storage();
M.unlock();
});
}

auto inspect_configuration() {
return get_cmd<ra2yrproto::commands::InspectConfiguration>([](auto* Q) {
auto [mut, s] = Q->I()->aq_storage();
auto [mut, M] = ra2yrcpp::hooks_yr::MainData::acquire();
auto& res = Q->command_data();
auto* cfg = &ra2yrcpp::hooks_yr::get_data(Q->I())->cfg;
cfg->MergeFrom(Q->command_data().config());
res.mutable_config()->CopyFrom(*cfg);
M->update_configuration(res.config());
res.mutable_config()->CopyFrom(M->data()->cfg);
});
}

Expand Down Expand Up @@ -294,7 +257,7 @@ static void convert_map_data(ra2yrproto::ra2yr::MapDataSoA* dst,
/// Read a protobuf message from storage determined by command argument type.
auto read_value() {
return get_cmd<ra2yrproto::commands::ReadValue>([](auto* Q) {
auto [mut, s] = Q->I()->aq_storage();
auto [mut, M] = ra2yrcpp::hooks_yr::MainData::acquire();
auto& A = Q->command_data();
// find the first field that's been set
auto sf = ra2yrcpp::protocol::find_set_fields(A.data());
Expand All @@ -306,10 +269,10 @@ auto read_value() {

if (fld->name() == "map_data_soa") {
convert_map_data(D->mutable_map_data_soa(),
get_data(Q->I())->sv.mutable_map_data());
M->data()->sv.mutable_map_data());
} else {
// TODO(shmocz): use oneof
ra2yrcpp::protocol::copy_field(D, &get_data(Q->I())->sv, fld);
ra2yrcpp::protocol::copy_field(D, &M->data()->sv, fld);
}
});
}
Expand All @@ -321,7 +284,6 @@ commands_yr::get_commands() {
return {
cmd::click_event(), //
cmd::unit_command(), //
cmd::create_callbacks(), //
cmd::get_game_state(), //
cmd::inspect_configuration(), //
cmd::mission_clicked(), //
Expand Down
Loading

0 comments on commit 1af2a45

Please sign in to comment.