ScrapHacks/ScrapHacks/src/REPL.hpp

572 lines
17 KiB
C++

#pragma once
#include <Windows.h>
#include <DbgHelp.h>
#define ASMJIT_EMBED
#define ASMTK_EMBED
#include <regex>
#include <sstream>
#include <string>
#include <asmtk/asmtk.h>
#include "Scrapland.hpp"
#include "Util.hpp"
void DllUnload();
void unhook_d3d8();
void hook_d3d8();
struct Command;
typedef void(_cdecl *t_cmd_func)(Command*,vector<string>);
size_t assemble(vector<string> assembly,uint64_t base) {
using namespace asmjit;
using namespace asmtk;
char err_msg[1024];
Error err;
CodeInfo ci(ArchInfo::kIdX86);
ci.setBaseAddress(base);
CodeHolder code;
code.init(ci);
x86::Assembler a(&code);
AsmParser p(&a);
for (string line:assembly) {
if (err = p.parse((line+"\n").c_str())) {
snprintf(err_msg,1024,"PARSE ERROR: [%s] %08x (%s)\n",line.c_str(), err, DebugUtils::errorAsString(err));
scrap_log(ERR_COLOR,err_msg);
return 0;
}
}
if (err=code.flatten()) {
snprintf(err_msg,1024,"FLATTEN ERROR: %08x (%s)\n", err, DebugUtils::errorAsString(err));
scrap_log(ERR_COLOR,err_msg);
return 0;
}
if (err=code.resolveUnresolvedLinks()) {
snprintf(err_msg,1024,"RESOLVE ERROR: %08x (%s)\n", err, DebugUtils::errorAsString(err));
scrap_log(ERR_COLOR,err_msg);
return 0;
}
CodeBuffer& buffer = code.sectionById(0)->buffer();
if (base==0) {
return buffer.size();
}
MEMORY_BASIC_INFORMATION mbi;
if (VirtualQuery((void*)base, &mbi, sizeof(mbi)) == 0) {
scrap_log(ERR_COLOR, "ERROR: ");
scrap_log(ERR_COLOR, GetLastErrorAsString());
scrap_log(ERR_COLOR, "\n");
return 0;
};
if (!VirtualProtect(mbi.BaseAddress, mbi.RegionSize, PAGE_EXECUTE_READWRITE, &mbi.Protect))
{
scrap_log(ERR_COLOR, "ERROR: ");
scrap_log(ERR_COLOR, GetLastErrorAsString());
scrap_log(ERR_COLOR, "\n");
return 0;
};
memcpy((void*)base,buffer.data(),buffer.size());
scrap_log(INFO_COLOR,"Code:"+hexdump_s((void*)base,buffer.size()));
VirtualProtect(mbi.BaseAddress, mbi.RegionSize, mbi.Protect, NULL);
return buffer.size();
}
size_t asm_size(vector<string> assembly) {
return assemble(assembly,0);
}
struct Command {
t_cmd_func func;
string usage;
string doc;
map<string,Command*> subcommands;
Command(t_cmd_func func=nullptr,string usage="",string doc="",map<string,Command*> subcommands={}) {
this->func=func;
this->usage=usage;
this->doc=doc;
this->subcommands=subcommands;
}
Command(string usage="",string doc="",map<string,Command*> subcommands={}):
Command(nullptr,usage,doc,subcommands) {};
void add_subcommand(string name,Command *cmd) {
this->subcommands[name]=cmd;
}
void set_subcommands(const map<string,Command*> &subcommands) {
for (auto subcmd:subcommands) {
this->add_subcommand(subcmd.first,subcmd.second);
}
}
bool has_subcommand(string cmd) {
return this->subcommands.count(cmd)>0;
}
void exec(vector<string> args) {
if (args.size()>1) {
string cmd=args[0];
if (this->has_subcommand(cmd)) {
// matching subcommand found, strip first part and forward args
args.erase(args.begin());
return this->subcommands[cmd]->exec(args);
};
}
// args vector empty or no subcommand found, check if we have a func ptr and call it
if (this->func!=nullptr) {
this->func(this,args);
return;
}
scrap_log(ERR_COLOR, "Unknown command!\n");
return;
}
};
struct REPL {
map<string,Command*> commands;
REPL(map<string,Command*> commands) {
this->commands=commands;
}
bool has_command(string cmd) {
return this->commands.count(cmd)>0;
}
bool exec(vector<string> args) {
vector<tuple<string,Command*>> cmd_stack;
map<string,Command*> cmds=this->commands;
if (args.size()==0) {
return false;
}
while (cmds.count(args[0])) {
cmd_stack.push_back(make_tuple(args[0],cmds[args[0]]));
cmds=cmds[args[0]]->subcommands;
args.erase(args.begin());
if (args.empty()) break;
}
while (cmd_stack.size()) {
auto elem=cmd_stack.back();
string cmd=get<0>(elem);
Command* cmd_ptr=get<1>(elem);
cmd_stack.pop_back();
if (cmd_ptr->func!=nullptr) {
cmd_ptr->func(cmd_ptr,args);
return true;
}
args.insert(args.begin(),cmd);
}
return false;
}
string help(vector<string> args) {
map<string,Command*> cmds=this->commands;
string ret;
if (args.empty()) {
return this->show_commands(cmds);
}
pair<string,Command*> cmd=make_pair("",nullptr);
for (string part: args) {
if (cmds.count(part)==0) {
return "No help for (sub)command '"+part+"'";
}
cmd=make_pair(part,cmds[part]);
cmds=cmd.second->subcommands;
}
ret=cmd.first+": "+cmd.second->usage+"\n";
if (cmds.size()) {
ret+="Subcommands:\n"+this->show_commands(cmds,1)+"\n";
};
return ret;
}
string show_commands(map<string,Command*> cmds,size_t depth=0) {
string s;
for (auto cmd:cmds) {
for (size_t n=0;n<depth;++n) {
s+=" ";
}
s+=cmd.first+": "+cmd.second->doc+" ("+cmd.second->usage+")\n"+this->show_commands(cmd.second->subcommands,depth+1);
}
return s;
}
};
DWORD
get_protection(void *addr) {
MEMORY_BASIC_INFORMATION mbi;
VirtualQuery(addr, &mbi, sizeof(mbi));
return mbi.Protect;
}
void cmd_exec(Command* cmd, vector<string> args) {
void *addr;
MEMORY_BASIC_INFORMATION mbi;
if (args.size()<1) {
scrap_log(ERR_COLOR, cmd->usage);
scrap_log(ERR_COLOR, "\n");
}
try {
addr = (void *)stoull(args[0], 0, 16);
} catch (exception e) {
scrap_log(ERR_COLOR, "ERROR: ");
scrap_log(ERR_COLOR, e.what());
scrap_log(ERR_COLOR, "\n");
return;
}
if (VirtualQuery(addr, &mbi, sizeof(mbi)) == 0) {
scrap_log(ERR_COLOR, "ERROR: ");
scrap_log(ERR_COLOR, GetLastErrorAsString());
scrap_log(ERR_COLOR, "\n");
return;
};
if (!VirtualProtect(mbi.BaseAddress, mbi.RegionSize, PAGE_EXECUTE_READWRITE, &mbi.Protect))
{
scrap_log(ERR_COLOR, "ERROR: ");
scrap_log(ERR_COLOR, GetLastErrorAsString());
scrap_log(ERR_COLOR, "\n");
};
CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)addr,0,NULL,NULL);
}
void cmd_write(Command* cmd,vector<string> args) {
MEMORY_BASIC_INFORMATION mbi;
if (args.size()==0) {
scrap_log(ERR_COLOR, cmd->usage);
scrap_log(ERR_COLOR, "\n");
return;
}
uint8_t *buffer = nullptr;
vector<uint8_t> data;
try {
if (args.size()>1) {
buffer = (uint8_t *)stoull(args[0], 0, 16);
data = fromhex(args[1]);
} else {
data = fromhex(args[0]);
buffer = new uint8_t[data.size()];
if (buffer==nullptr) {
scrap_log(ERR_COLOR, "ERROR: ");
scrap_log(ERR_COLOR, "new[] failed");
scrap_log(ERR_COLOR, "\n");
return;
}
char ptr[255];
snprintf(ptr,255,"Buffer @ %p\n",buffer);
scrap_log(INFO_COLOR,ptr);
}
} catch (exception e) {
scrap_log(ERR_COLOR, "ERROR: ");
scrap_log(ERR_COLOR, e.what());
scrap_log(ERR_COLOR, "\n");
return;
}
if (VirtualQuery(buffer, &mbi, sizeof(mbi)) == 0) {
scrap_log(ERR_COLOR, "ERROR: ");
scrap_log(ERR_COLOR, GetLastErrorAsString());
scrap_log(ERR_COLOR, "\n");
return;
};
if (!VirtualProtect(mbi.BaseAddress, mbi.RegionSize, PAGE_EXECUTE_READWRITE, &mbi.Protect))
{
scrap_log(ERR_COLOR, "ERROR: ");
scrap_log(ERR_COLOR, GetLastErrorAsString());
scrap_log(ERR_COLOR, "\n");
};
size_t idx = 0;
for (uint8_t v : data) {
buffer[idx++] = v;
}
VirtualProtect(mbi.BaseAddress, mbi.RegionSize, mbi.Protect, NULL);
return;
}
void cmd_read(Command* cmd,vector<string> args) {
MEMORY_BASIC_INFORMATION mbi;
if (args.size()<1) {
scrap_log(ERR_COLOR, cmd->usage);
scrap_log(ERR_COLOR, "\n");
return;
}
uintptr_t addr = UINTPTR_MAX;
size_t size = 0xff;
unsigned char *buffer;
try {
addr = stoull(args[0], 0, 16);
if (args.size()>1) {
size = stoull(args[1]);
}
buffer = new unsigned char[size];
} catch (exception e) {
scrap_log(ERR_COLOR, "ERROR: ");
scrap_log(ERR_COLOR, e.what());
scrap_log(ERR_COLOR, "\n");
return;
}
void *mptr = reinterpret_cast<void *>(addr);
if (VirtualQuery(mptr, &mbi, sizeof(mbi)) == 0) {
scrap_log(ERR_COLOR, "ERROR: ");
scrap_log(ERR_COLOR, GetLastErrorAsString());
scrap_log(ERR_COLOR, "\n");
return;
};
if (!VirtualProtect(mbi.BaseAddress, mbi.RegionSize, PAGE_EXECUTE_READWRITE,
&mbi.Protect)) {
scrap_log(ERR_COLOR, "ERROR: ");
scrap_log(ERR_COLOR, GetLastErrorAsString());
scrap_log(ERR_COLOR, "\n");
return;
};
string hxd = hexdump_s(mptr, size);
scrap_log(INFO_COLOR, hxd.c_str());
VirtualProtect(mbi.BaseAddress, mbi.RegionSize, mbi.Protect, NULL);
if (buffer) {
free(buffer);
}
return;
}
void cmd_hook_dx8(Command* cmd,vector<string> args) {
hook_d3d8();
scrap_log(INFO_COLOR,"DX8 hooked!\n");
return;
}
void cmd_unhook_dx8(Command* cmd,vector<string> args) {
unhook_d3d8();
scrap_log(INFO_COLOR,"DX8 unhooked!\n");
return;
}
void cmd_dx8(Command* cmd,vector<string> args) {
if (args.size()!=1) {
scrap_log(ERR_COLOR, cmd->usage);
scrap_log(ERR_COLOR, "\n");
return;
}
if (args[0]=="zenable:true") {
use_z=true;
scrap_log(INFO_COLOR,"DX8 mode switched!\n");
return;
};
if (args[0]=="zenable:false") {
use_z=false;
scrap_log(INFO_COLOR,"DX8 mode switched!\n");
return;
};
if (args[0]=="fill:wire") {
fillmode=D3DFILLMODE::D3DFILL_WIREFRAME;
scrap_log(INFO_COLOR,"DX8 mode switched!\n");
return;
};
if (args[0]=="fill:solid") {
fillmode=D3DFILLMODE::D3DFILL_SOLID;
scrap_log(INFO_COLOR,"DX8 mode switched!\n");
return;
};
if (args[0]=="fill:point") {
fillmode=D3DFILLMODE::D3DFILL_POINT;
scrap_log(INFO_COLOR,"DX8 mode switched!\n");
return;
};
scrap_log(ERR_COLOR,"Invalid argument!\n");
return;
}
void cmd_dump_stack(Command* cmd, vector<string> args) {
void** stack=(void**)_AddressOfReturnAddress();
cout<<"ESP: "<<stack<<endl;
for (size_t n=0;n<0x100;++n) {
if (!addr_exists(stack[n])) {
continue;
}
char R=can_read(stack[n]) ? 'R' : ' ';
char W=can_write(stack[n]) ? 'W' : ' ';
char X=can_execute(stack[n]) ? 'X' : ' ';
cout<<"ESP[" << std::hex << setfill('0') << setw(2) << n <<"]: "<<stack[n]<<" "<<R<<W<<X;
if (can_read(stack[n])) {
cout<<" [ "<<hexdump_s(stack[n],0xf,true)<<"]"<<endl;
} else {
cout<<endl;
}
}
return;
}
void cmd_dump_py(Command* cmd,vector<string> args) {
stringstream out;
for (auto mod : Py) {
for (auto meth : mod.second.methods) {
out << mod.first << "." << meth.first << " @ "
<< meth.second->ml_meth << endl;
}
}
scrap_log(INFO_COLOR,out.str().c_str());
}
void cmd_dump_vars(Command* cmd, vector<string> args) {
stringstream out;
GameVar* var=ptr<GameVar>(P_VARS,0);
out << "GameVars:" << endl;
while (var!=nullptr) {
out<<var->name<< "[" <<std::hex <<(uint16_t)var->type <<","<< (uint16_t)var->subtype << std::dec << "]: " << var->desc<<" ("<<var->value<<", "<<var->default<<")"<<endl;
var=var->next;
}
scrap_log(INFO_COLOR,out.str().c_str());
}
void cmd_dump_ents(Command* cmd,vector<string> args) {
stringstream out;
out << "Entities:" << endl;
dump_ht(ptr<HashTable<Entity>>(P_WORLD, O_ENTS), &out);
out << "Entity Lists:" << endl;
dump_ht(ptr<HashTable<EntityList>>(P_WORLD, O_ENTLISTS), &out);
scrap_log(INFO_COLOR,out.str().c_str());
return;
}
void cmd_toggle_overlay(Command* cmd,vector<string> args) {
if (!hooked) {
scrap_log(INFO_COLOR,"DX8 not hooked, run '$dx8 hook' first!\n");
return;
}
overlay=!overlay;
if (overlay) {
scrap_log(INFO_COLOR,"Overlay enabled!\n");
} else {
scrap_log(INFO_COLOR,"Overlay disabled!\n");
}
}
void cmd_enable_overlay(Command* cmd,vector<string> args) {
if (!overlay) {
cmd_toggle_overlay(cmd,args);
}
}
void cmd_disable_overlay(Command* cmd,vector<string> args) {
if (overlay) {
cmd_toggle_overlay(cmd,args);
}
}
void cmd_print_alarm(Command* cmd,vector<string> args) {
stringstream out;
float *alarm = ptr<float>(P_WORLD, O_ALARM);
float *alarm_grow = ptr<float>(P_WORLD, O_ALARM_GROW);
out << "Alarm: " << alarm[0] << " + " << alarm_grow[0] << endl;
scrap_log(INFO_COLOR,out.str().c_str());
return;
}
void cmd_unload(Command* cmd,vector<string> args) {
scrap_log(INFO_COLOR,"Unloading ScrapHacks... bye!\n");
DllUnload();
}
void cmd_asm(Command* cmd, vector<string> args) {
string code;
uintptr_t buffer_addr;
bool has_addr=false;
if (args.size()<1) {
scrap_log(ERR_COLOR, cmd->usage);
return;
}
try {
buffer_addr=stoull(args[0], 0, 16);
has_addr=true;
} catch (exception e) {
// NOP
has_addr=false;
}
if (has_addr) {
// remove address from args
args.erase(args.begin());
}
for (string arg:args) {
code+=arg+" ";
};
size_t data_size=asm_size(split(code,';'));
if (!has_addr) {
buffer_addr = (uintptr_t)malloc(data_size);
if (buffer_addr==0) {
scrap_log(ERR_COLOR, "ERROR: ");
scrap_log(ERR_COLOR, "malloc() failed");
scrap_log(ERR_COLOR, "\n");
return;
}
char ptr[255];
snprintf(ptr,255,"Buffer @ %p\n",(void*)buffer_addr);
scrap_log(INFO_COLOR,ptr);
}
assemble(split(code,';'),buffer_addr);
}
void cmd_help(Command* cmd,vector<string> args);
static REPL* repl=new REPL(
{
{"mem",new Command("Usage: $mem (read|write)","Manipulate memory",{
{"read",new Command(cmd_read,"Usage: $mem read <addr> [size]","Read memory")},
{"write",new Command(cmd_write,"Usage: $mem write [addr] <data(hex)>","Write memory, if no address is specifiew we VirtualAlloc() a region")},
{"exec",new Command(cmd_exec,"Usage: $exec <addr>","Start a new thread at the specified address")},
{"asm",new Command(cmd_asm,"Usage: $asm [addr] <inst1>;<inst2>;...","Assemble instructions at address, if no address is given allocate memory and assemble code into that")},
{"stack",new Command(cmd_dump_stack,"Usage: $mem stack","Dump stack contents")},
})},
{"unload",new Command(cmd_unload,"Usage: $unload","Unload ScrapHacks")},
{"dx8",new Command(cmd_dx8,"Usage: $dx8 <subcommand>","Manipulate DirectX 8 functions and state",{
{"overlay",new Command("Usage: $dx8 overlay <subcommand>","Control DX8 overlay",{
{"toggle",new Command(cmd_toggle_overlay,"Usage: $dx8 overlay toggle","Toggle overlay")},
{"enable",new Command(cmd_enable_overlay,"Usage: $dx8 overlay enable","Enable overlay")},
{"disable",new Command(cmd_disable_overlay,"Usage: $dx8 overlay disable","Disable overlay")},
})},
{"hook",new Command(cmd_hook_dx8,"Usage: $dx8 hook","Enable DirectX 8 hook")},
{"unhook",new Command(cmd_unhook_dx8,"Usage: $dx8 hook","Disable DirectX 8 hook")}
})},
{"dump",new Command("Usage: $dump <subcommand>","Dump various data to the console",{
{"py",new Command(cmd_dump_py,"Usage: $dump py","Dump python module information")},
{"ents",new Command(cmd_dump_ents,"Usage: $dump ents","Dump entity information")},
{"alarm",new Command(cmd_print_alarm,"Usage: $dump alarm","Print alarm status")},
{"vars",new Command(cmd_dump_vars,"Usage: $dump vars","Print engine variables")},
})},
{"help",new Command(cmd_help,"Usage: $help [command]","Print help for ScrapHacks command")}
});
void cmd_help(Command* cmd,vector<string> args) {
scrap_log(INFO_COLOR,repl->help(args)+"\n");
};
void handle_command(const char *_cmd) {
scrap_log(ERR_COLOR, "$");
scrap_log(ERR_COLOR, _cmd);
scrap_log(ERR_COLOR, "\n");
cout << "CMD: '" << _cmd << "'" << endl;
repl->exec(split(string(_cmd), ' '));
return;
}