implement send_function_call
This commit is contained in:
@@ -0,0 +1,141 @@
|
||||
#include "FunctionCompiler.hh"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <stdexcept>
|
||||
#include <phosg/Filesystem.hh>
|
||||
|
||||
#ifdef HAVE_RESOURCE_FILE
|
||||
#include <resource_file/Emulators/PPC32Emulator.hh>
|
||||
#endif
|
||||
|
||||
#include "CommandFormats.hh"
|
||||
|
||||
using namespace std;
|
||||
|
||||
|
||||
|
||||
bool function_compiler_available() {
|
||||
#ifndef HAVE_RESOURCE_FILE
|
||||
return false;
|
||||
#else
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
|
||||
std::string CompiledFunctionCode::generate_client_command(
|
||||
const std::unordered_map<std::string, uint32_t>& label_writes,
|
||||
const std::string& suffix) const {
|
||||
S_ExecuteCode_Footer_GC_B2 footer;
|
||||
footer.num_relocations = this->relocation_deltas.size();
|
||||
footer.unused1.clear();
|
||||
footer.entrypoint_addr_offset = this->entrypoint_offset_offset;
|
||||
footer.unused2.clear();
|
||||
|
||||
StringWriter w;
|
||||
if (!label_writes.empty()) {
|
||||
string modified_code = this->code;
|
||||
for (const auto& it : label_writes) {
|
||||
size_t offset = this->label_offsets.at(it.first);
|
||||
if (offset > modified_code.size() - 4) {
|
||||
throw runtime_error("label out of range");
|
||||
}
|
||||
*reinterpret_cast<be_uint32_t*>(modified_code.data() + offset) = it.second;
|
||||
}
|
||||
w.write(modified_code);
|
||||
} else {
|
||||
w.write(this->code);
|
||||
}
|
||||
w.write(suffix);
|
||||
while (w.size() & 3) {
|
||||
w.put_u8(0);
|
||||
}
|
||||
|
||||
footer.relocations_offset = w.size();
|
||||
for (uint16_t delta : this->relocation_deltas) {
|
||||
w.put_u16b(delta);
|
||||
}
|
||||
if (this->relocation_deltas.size() & 1) {
|
||||
w.put_u16(0);
|
||||
}
|
||||
|
||||
w.put(footer);
|
||||
return move(w.str());
|
||||
}
|
||||
|
||||
shared_ptr<CompiledFunctionCode> compile_function_code(const std::string& text) {
|
||||
#ifndef HAVE_RESOURCE_FILE
|
||||
(void)text;
|
||||
throw runtime_error("PowerPC assembler is not available");
|
||||
|
||||
#else
|
||||
auto assembled = PPC32Emulator::assemble(text);
|
||||
|
||||
shared_ptr<CompiledFunctionCode> ret(new CompiledFunctionCode());
|
||||
ret->code = move(assembled.code);
|
||||
ret->label_offsets = move(assembled.label_offsets);
|
||||
ret->index = 0xFF;
|
||||
|
||||
set<uint32_t> reloc_indexes;
|
||||
for (const auto& it : ret->label_offsets) {
|
||||
if (starts_with(it.first, "reloc")) {
|
||||
reloc_indexes.emplace(it.second / 4);
|
||||
} else if (starts_with(it.first, "newserv_index_")) {
|
||||
ret->index = stoul(it.first.substr(14), nullptr, 16);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
ret->entrypoint_offset_offset = ret->label_offsets.at("entry_ptr");
|
||||
} catch (const out_of_range&) {
|
||||
throw runtime_error("code does not contain entry_ptr label");
|
||||
}
|
||||
|
||||
uint32_t prev_index = 0;
|
||||
for (const auto& it : reloc_indexes) {
|
||||
uint32_t delta = it - prev_index;
|
||||
if (delta > 0xFFFF) {
|
||||
throw runtime_error("relocation delta too far away");
|
||||
}
|
||||
ret->relocation_deltas.emplace_back(delta);
|
||||
prev_index = it;
|
||||
}
|
||||
|
||||
return ret;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
|
||||
FunctionCodeIndex::FunctionCodeIndex(const std::string& directory) {
|
||||
this->index_to_function.resize(0x100);
|
||||
|
||||
if (!function_compiler_available()) {
|
||||
log(INFO, "Function compiler is not available");
|
||||
return;
|
||||
}
|
||||
|
||||
for (const auto& filename : list_directory(directory)) {
|
||||
if (!ends_with(filename, ".s")) {
|
||||
continue;
|
||||
}
|
||||
string name = filename.substr(0, filename.size() - 2);
|
||||
|
||||
try {
|
||||
string path = directory + "/" + filename;
|
||||
string text = load_file(path);
|
||||
auto code = compile_function_code(text);
|
||||
if (code->index < 0xFF) {
|
||||
this->index_to_function.at(code->index) = code;
|
||||
}
|
||||
this->name_to_function.emplace(name, code);
|
||||
log(WARNING, "Compiled function %s", name.c_str());
|
||||
|
||||
} catch (const exception& e) {
|
||||
log(WARNING, "Failed to compile function %s: %s", name.c_str(), e.what());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
#pragma once
|
||||
|
||||
#include <inttypes.h>
|
||||
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
|
||||
|
||||
|
||||
bool function_compiler_available();
|
||||
|
||||
|
||||
|
||||
// TODO: Support x86 function calls in the future. Currently we only support
|
||||
// PPC32 because I haven't written an appropriate x86 assembler yet.
|
||||
|
||||
struct CompiledFunctionCode {
|
||||
std::string code;
|
||||
std::vector<uint16_t> relocation_deltas;
|
||||
std::unordered_map<std::string, uint32_t> label_offsets;
|
||||
uint32_t entrypoint_offset_offset;
|
||||
uint8_t index; // 00-FE (FF = index not specified)
|
||||
|
||||
std::string generate_client_command(
|
||||
const std::unordered_map<std::string, uint32_t>& label_writes = {},
|
||||
const std::string& suffix = "") const;
|
||||
};
|
||||
|
||||
std::shared_ptr<CompiledFunctionCode> compile_function_code(const std::string& text);
|
||||
|
||||
|
||||
|
||||
struct FunctionCodeIndex {
|
||||
FunctionCodeIndex(const std::string& directory);
|
||||
|
||||
std::unordered_map<std::string, std::shared_ptr<CompiledFunctionCode>> name_to_function;
|
||||
std::vector<std::shared_ptr<CompiledFunctionCode>> index_to_function;
|
||||
};
|
||||
@@ -434,6 +434,9 @@ int main(int argc, char** argv) {
|
||||
log(INFO, "Collecting quest metadata");
|
||||
state->quest_index.reset(new QuestIndex("system/quests"));
|
||||
|
||||
log(INFO, "Compiling client functions");
|
||||
state->function_code_index.reset(new FunctionCodeIndex("system/ppc"));
|
||||
|
||||
shared_ptr<DNSServer> dns_server;
|
||||
if (state->dns_server_port) {
|
||||
log(INFO, "Starting DNS server");
|
||||
|
||||
+61
-3
@@ -22,6 +22,9 @@
|
||||
#include <phosg/Random.hh>
|
||||
#include <phosg/Strings.hh>
|
||||
#include <phosg/Time.hh>
|
||||
#ifdef HAVE_RESOURCE_FILE
|
||||
#include <resource_file/Emulators/PPC32Emulator.hh>
|
||||
#endif
|
||||
|
||||
#include "PSOProtocol.hh"
|
||||
#include "SendCommands.hh"
|
||||
@@ -397,9 +400,64 @@ static bool process_server_88(shared_ptr<ServerState>,
|
||||
static bool process_server_B2(shared_ptr<ServerState>,
|
||||
ProxyServer::LinkedSession& session, uint16_t, uint32_t flag, string& data) {
|
||||
if (session.save_files) {
|
||||
string output_filename = string_printf("code.bin.%" PRId64, now());
|
||||
string output_filename = string_printf("code.%" PRId64 ".bin", now());
|
||||
save_file(output_filename, data);
|
||||
session.log(INFO, "Wrote code from server to file %s", output_filename.c_str());
|
||||
|
||||
#ifdef HAVE_RESOURCE_FILE
|
||||
try {
|
||||
// Note: we copy header here because we might modify data later, which
|
||||
// would break the reference
|
||||
auto header = StringReader(data).get<S_ExecuteCode_B2>();
|
||||
|
||||
size_t footer_end_offset = header.code_size;
|
||||
size_t footer_offset = footer_end_offset - sizeof(S_ExecuteCode_Footer_GC_B2);
|
||||
size_t orig_size = data.size() - sizeof(header);
|
||||
if (data.size() < (sizeof(header) + footer_end_offset)) {
|
||||
data.resize((sizeof(header) + footer_end_offset), '\0');
|
||||
}
|
||||
|
||||
fprintf(stderr, "footer_offset = %08zX\n", footer_offset);
|
||||
print_data(stderr, data);
|
||||
|
||||
StringReader r(data.data() + sizeof(header), data.size() - sizeof(header));
|
||||
const auto& footer = r.pget<S_ExecuteCode_Footer_GC_B2>(footer_offset);
|
||||
|
||||
multimap<uint32_t, string> labels;
|
||||
r.go(footer.relocations_offset);
|
||||
uint32_t reloc_offset = 0;
|
||||
for (size_t x = 0; x < footer.num_relocations; x++) {
|
||||
reloc_offset += (r.get_u16b() * 4);
|
||||
labels.emplace(reloc_offset, string_printf("reloc%zu", x));
|
||||
}
|
||||
labels.emplace(footer.entrypoint_addr_offset.load(), "entry_ptr");
|
||||
labels.emplace(footer_offset, "footer");
|
||||
labels.emplace(r.pget_u32b(footer.entrypoint_addr_offset), "start");
|
||||
|
||||
for (const auto& it : labels) {
|
||||
fprintf(stderr, "label: %08" PRIX32 " => %s\n", it.first, it.second.c_str());
|
||||
}
|
||||
|
||||
string disassembly = PPC32Emulator::disassemble(
|
||||
&r.pget<uint8_t>(0, orig_size),
|
||||
orig_size,
|
||||
0,
|
||||
&labels);
|
||||
|
||||
output_filename = string_printf("code.%" PRId64 ".txt", now());
|
||||
{
|
||||
auto f = fopen_unique(output_filename, "wt");
|
||||
fprintf(f.get(), "// code_size = 0x%" PRIX32 "\n", header.code_size.load());
|
||||
fprintf(f.get(), "// checksum_addr = 0x%" PRIX32 "\n", header.checksum_start.load());
|
||||
fprintf(f.get(), "// checksum_size = 0x%" PRIX32 "\n", header.checksum_size.load());
|
||||
fwritex(f.get(), disassembly);
|
||||
}
|
||||
session.log(INFO, "Wrote disassembly to file %s", output_filename.c_str());
|
||||
|
||||
} catch (const exception& e) {
|
||||
session.log(INFO, "Failed to disassemble code from server: %s", e.what());
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
if (session.function_call_return_value >= 0) {
|
||||
@@ -417,7 +475,7 @@ static bool process_server_B2(shared_ptr<ServerState>,
|
||||
static bool process_server_E7(shared_ptr<ServerState>,
|
||||
ProxyServer::LinkedSession& session, uint16_t, uint32_t, string& data) {
|
||||
if (session.save_files) {
|
||||
string output_filename = string_printf("player.bin.%" PRId64, now());
|
||||
string output_filename = string_printf("player.%" PRId64 ".bin", now());
|
||||
save_file(output_filename, data);
|
||||
session.log(INFO, "Wrote player data to file %s", output_filename.c_str());
|
||||
}
|
||||
@@ -646,7 +704,7 @@ static bool process_server_gc_B8(shared_ptr<ServerState>,
|
||||
return true;
|
||||
}
|
||||
|
||||
string output_filename = string_printf("cardupdate.mnr.%" PRIu64, now());
|
||||
string output_filename = string_printf("cardupdate.%" PRIu64 ".mnr", now());
|
||||
save_file(output_filename, r.read(size));
|
||||
|
||||
session.log(INFO, "Wrote %zu bytes to %s", size, output_filename.c_str());
|
||||
|
||||
@@ -2057,7 +2057,7 @@ static process_command_t gc_handlers[0x100] = {
|
||||
process_quest_barrier, nullptr, nullptr, nullptr,
|
||||
|
||||
// B0
|
||||
nullptr, process_server_time_request, nullptr, nullptr,
|
||||
nullptr, process_server_time_request, nullptr, process_ignored_command,
|
||||
nullptr, nullptr, nullptr, process_ignored_command,
|
||||
process_ignored_command, nullptr, process_ep3_jukebox, nullptr,
|
||||
nullptr, nullptr, nullptr, nullptr,
|
||||
@@ -2146,8 +2146,10 @@ static process_command_t bb_handlers[0x100] = {
|
||||
process_quest_barrier, nullptr, nullptr, nullptr,
|
||||
|
||||
// B0
|
||||
nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr,
|
||||
nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr,
|
||||
nullptr, nullptr, nullptr, process_ignored_command,
|
||||
nullptr, nullptr, nullptr, nullptr,
|
||||
nullptr, nullptr, nullptr, nullptr,
|
||||
nullptr, nullptr, nullptr, nullptr,
|
||||
|
||||
// C0
|
||||
nullptr, process_create_game_bb, nullptr, nullptr,
|
||||
|
||||
@@ -93,6 +93,11 @@ void send_command(
|
||||
throw logic_error("unimplemented game version in send_command");
|
||||
}
|
||||
|
||||
// Most client versions I've seen have a receive buffer 0x7C00 bytes in size
|
||||
if (send_data.size() > 0x7C00) {
|
||||
throw runtime_error("outbound command too large");
|
||||
}
|
||||
|
||||
if (name_str) {
|
||||
string name_token;
|
||||
if (name_str[0]) {
|
||||
@@ -312,6 +317,38 @@ void send_update_client_config(shared_ptr<Client> c) {
|
||||
|
||||
|
||||
|
||||
void send_function_call(
|
||||
shared_ptr<Client> c,
|
||||
shared_ptr<CompiledFunctionCode> code,
|
||||
const std::unordered_map<std::string, uint32_t>& label_writes,
|
||||
const std::string& suffix,
|
||||
uint32_t checksum_addr,
|
||||
uint32_t checksum_size) {
|
||||
if (c->version != GameVersion::GC) {
|
||||
throw logic_error("cannot send function calls to non-GameCube clients");
|
||||
}
|
||||
if (c->flags & Client::Flag::EPISODE_3) {
|
||||
throw logic_error("cannot send function calls to Episode 3 clients");
|
||||
}
|
||||
|
||||
string data;
|
||||
uint8_t index = 0xFF;
|
||||
if (code.get()) {
|
||||
data = code->generate_client_command(label_writes, suffix);
|
||||
index = code->index;
|
||||
}
|
||||
|
||||
S_ExecuteCode_B2 header = {data.size(), checksum_addr, checksum_size};
|
||||
|
||||
StringWriter w;
|
||||
w.put(header);
|
||||
w.write(data);
|
||||
|
||||
send_command(c, 0xB2, index, w.str());
|
||||
}
|
||||
|
||||
|
||||
|
||||
void send_reconnect(shared_ptr<Client> c, uint32_t address, uint16_t port) {
|
||||
S_Reconnect_19 cmd = {address, port, 0};
|
||||
// On the patch server, 14 is the reconnect command, but it works exactly the
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
#include "Quest.hh"
|
||||
#include "Text.hh"
|
||||
#include "CommandFormats.hh"
|
||||
#include "FunctionCompiler.hh"
|
||||
|
||||
|
||||
|
||||
@@ -104,6 +105,14 @@ void send_server_init(std::shared_ptr<ServerState> s, std::shared_ptr<Client> c,
|
||||
bool initial_connection);
|
||||
void send_update_client_config(std::shared_ptr<Client> c);
|
||||
|
||||
void send_function_call(
|
||||
std::shared_ptr<Client> c,
|
||||
std::shared_ptr<CompiledFunctionCode> code,
|
||||
const std::unordered_map<std::string, uint32_t>& label_writes = {},
|
||||
const std::string& suffix = "",
|
||||
uint32_t checksum_addr = 0,
|
||||
uint32_t checksum_size = 0);
|
||||
|
||||
void send_reconnect(std::shared_ptr<Client> c, uint32_t address, uint16_t port);
|
||||
void send_pc_gc_split_reconnect(std::shared_ptr<Client> c, uint32_t address,
|
||||
uint16_t pc_port, uint16_t gc_port);
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#include "Lobby.hh"
|
||||
#include "Menu.hh"
|
||||
#include "Quest.hh"
|
||||
#include "FunctionCompiler.hh"
|
||||
|
||||
|
||||
|
||||
@@ -46,6 +47,7 @@ struct ServerState {
|
||||
bool allow_unregistered_users;
|
||||
RunShellBehavior run_shell_behavior;
|
||||
std::vector<std::shared_ptr<const PSOBBEncryption::KeyFile>> bb_private_keys;
|
||||
std::shared_ptr<const FunctionCodeIndex> function_code_index;
|
||||
std::shared_ptr<const Ep3DataIndex> ep3_data_index;
|
||||
std::shared_ptr<const QuestIndex> quest_index;
|
||||
std::shared_ptr<const LevelTable> level_table;
|
||||
|
||||
Reference in New Issue
Block a user