generalize send_function_call a bit

This commit is contained in:
Martin Michelsen
2022-08-11 00:23:49 -07:00
parent 8bff95052c
commit a6442c6208
3 changed files with 93 additions and 26 deletions
+57 -11
View File
@@ -27,10 +27,24 @@ bool function_compiler_available() {
string CompiledFunctionCode::generate_client_command( const char* name_for_architecture(CompiledFunctionCode::Architecture arch) {
switch (arch) {
case CompiledFunctionCode::Architecture::POWERPC:
return "PowerPC";
case CompiledFunctionCode::Architecture::X86:
return "x86";
default:
throw logic_error("invalid architecture");
}
}
template <typename FooterT, typename U16T>
string CompiledFunctionCode::generate_client_command_t(
const unordered_map<string, uint32_t>& label_writes, const unordered_map<string, uint32_t>& label_writes,
const string& suffix) const { const string& suffix) const {
S_ExecuteCode_Footer_GC_B2 footer; FooterT footer;
footer.num_relocations = this->relocation_deltas.size(); footer.num_relocations = this->relocation_deltas.size();
footer.unused1.clear(0); footer.unused1.clear(0);
footer.entrypoint_addr_offset = this->entrypoint_offset_offset; footer.entrypoint_addr_offset = this->entrypoint_offset_offset;
@@ -57,7 +71,7 @@ string CompiledFunctionCode::generate_client_command(
footer.relocations_offset = w.size(); footer.relocations_offset = w.size();
for (uint16_t delta : this->relocation_deltas) { for (uint16_t delta : this->relocation_deltas) {
w.put_u16b(delta); w.put<U16T>(delta);
} }
if (this->relocation_deltas.size() & 1) { if (this->relocation_deltas.size() & 1) {
w.put_u16(0); w.put_u16(0);
@@ -67,13 +81,37 @@ string CompiledFunctionCode::generate_client_command(
return move(w.str()); return move(w.str());
} }
string CompiledFunctionCode::generate_client_command(
const unordered_map<string, uint32_t>& label_writes,
const string& suffix) const {
if (this->arch == Architecture::POWERPC) {
return this->generate_client_command_t<S_ExecuteCode_Footer_GC_B2, be_uint16_t>(
label_writes, suffix);
} else if (this->arch == Architecture::X86) {
return this->generate_client_command_t<S_ExecuteCode_Footer_PC_XB_BB_B2, le_uint16_t>(
label_writes, suffix);
} else {
throw logic_error("invalid architecture");
}
}
bool CompiledFunctionCode::is_big_endian() const {
return this->arch == Architecture::POWERPC;
}
shared_ptr<CompiledFunctionCode> compile_function_code( shared_ptr<CompiledFunctionCode> compile_function_code(
const string& directory, const string& name, const string& text) { CompiledFunctionCode::Architecture arch,
const string& directory,
const string& name,
const string& text) {
#ifndef HAVE_RESOURCE_FILE #ifndef HAVE_RESOURCE_FILE
(void)arch;
(void)directory; (void)directory;
(void)name; (void)name;
(void)text; (void)text;
throw runtime_error("PowerPC assembler is not available"); throw runtime_error("function compiler is not available");
#else #else
std::unordered_set<string> get_include_stack; // For mutual recursion detection std::unordered_set<string> get_include_stack; // For mutual recursion detection
@@ -94,12 +132,17 @@ shared_ptr<CompiledFunctionCode> compile_function_code(
}; };
shared_ptr<CompiledFunctionCode> ret(new CompiledFunctionCode()); shared_ptr<CompiledFunctionCode> ret(new CompiledFunctionCode());
ret->arch = arch;
ret->name = name; ret->name = name;
ret->index = 0; ret->index = 0;
auto assembled = PPC32Emulator::assemble(text, get_include); if (arch == CompiledFunctionCode::Architecture::POWERPC) {
ret->code = move(assembled.code); auto assembled = PPC32Emulator::assemble(text, get_include);
ret->label_offsets = move(assembled.label_offsets); ret->code = move(assembled.code);
ret->label_offsets = move(assembled.label_offsets);
} else if (arch == CompiledFunctionCode::Architecture::X86) {
throw runtime_error("x86 assembler is not implemented");
}
set<uint32_t> reloc_indexes; set<uint32_t> reloc_indexes;
for (const auto& it : ret->label_offsets) { for (const auto& it : ret->label_offsets) {
@@ -149,7 +192,8 @@ FunctionCodeIndex::FunctionCodeIndex(const string& directory) {
try { try {
string path = directory + "/" + filename; string path = directory + "/" + filename;
string text = load_file(path); string text = load_file(path);
auto code = compile_function_code(directory, name, text); auto code = compile_function_code(
CompiledFunctionCode::Architecture::POWERPC, directory, name, text);
if (code->index != 0) { if (code->index != 0) {
if (!this->index_to_function.emplace(code->index, code).second) { if (!this->index_to_function.emplace(code->index, code).second) {
throw runtime_error(string_printf( throw runtime_error(string_printf(
@@ -162,9 +206,11 @@ FunctionCodeIndex::FunctionCodeIndex(const string& directory) {
this->name_to_patch_function.emplace(name, code); this->name_to_patch_function.emplace(name, code);
} }
if (code->index) { if (code->index) {
function_compiler_log.info("Compiled function %02X => %s", code->index, name.c_str()); function_compiler_log.info("Compiled function %02X => %s (%s)",
code->index, name.c_str(), name_for_architecture(code->arch));
} else { } else {
function_compiler_log.info("Compiled function %s", name.c_str()); function_compiler_log.info("Compiled function %s (%s)",
name.c_str(), name_for_architecture(code->arch));
} }
} catch (const exception& e) { } catch (const exception& e) {
+14
View File
@@ -20,6 +20,11 @@ bool function_compiler_available();
// PPC32 because I haven't written an appropriate x86 assembler yet. // PPC32 because I haven't written an appropriate x86 assembler yet.
struct CompiledFunctionCode { struct CompiledFunctionCode {
enum class Architecture {
POWERPC = 0,
X86,
};
Architecture arch;
std::string code; std::string code;
std::vector<uint16_t> relocation_deltas; std::vector<uint16_t> relocation_deltas;
std::unordered_map<std::string, uint32_t> label_offsets; std::unordered_map<std::string, uint32_t> label_offsets;
@@ -28,12 +33,21 @@ struct CompiledFunctionCode {
uint32_t index; // 0 = unused (not registered in index_to_function) uint32_t index; // 0 = unused (not registered in index_to_function)
uint32_t menu_item_id; uint32_t menu_item_id;
bool is_big_endian() const;
template <typename FooterT, typename U16T>
std::string generate_client_command_t(
const std::unordered_map<std::string, uint32_t>& label_writes,
const std::string& suffix) const;
std::string generate_client_command( std::string generate_client_command(
const std::unordered_map<std::string, uint32_t>& label_writes = {}, const std::unordered_map<std::string, uint32_t>& label_writes = {},
const std::string& suffix = "") const; const std::string& suffix = "") const;
}; };
const char* name_for_architecture(CompiledFunctionCode::Architecture arch);
std::shared_ptr<CompiledFunctionCode> compile_function_code( std::shared_ptr<CompiledFunctionCode> compile_function_code(
CompiledFunctionCode::Architecture arch,
const std::string& directory, const std::string& directory,
const std::string& name, const std::string& name,
const std::string& text); const std::string& text);
+22 -15
View File
@@ -217,9 +217,6 @@ void send_function_call(
const std::string& suffix, const std::string& suffix,
uint32_t checksum_addr, uint32_t checksum_addr,
uint32_t checksum_size) { 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::DOES_NOT_SUPPORT_SEND_FUNCTION_CALL) { if (c->flags & Client::Flag::DOES_NOT_SUPPORT_SEND_FUNCTION_CALL) {
throw logic_error("client does not support function calls"); throw logic_error("client does not support function calls");
} }
@@ -230,29 +227,39 @@ void send_function_call(
string data; string data;
uint32_t index = 0; uint32_t index = 0;
if (code.get()) { if (code.get()) {
// TODO: If we end up supporting B2 on non-GC platforms, we have to rework
// generate_client_command to be able to generate little-endian footers.
data = code->generate_client_command(label_writes, suffix); data = code->generate_client_command(label_writes, suffix);
index = code->index; index = code->index;
if (c->flags & Client::Flag::ENCRYPTED_SEND_FUNCTION_CALL) { if (c->flags & Client::Flag::ENCRYPTED_SEND_FUNCTION_CALL) {
uint32_t key = random_object<uint32_t>(); uint32_t key = random_object<uint32_t>();
// This format was probably never used on any little-endian system, but we
// implement the way it would probably work there if it was used.
StringWriter w; StringWriter w;
w.put_u32b(data.size()); if (code->is_big_endian()) {
w.put_u32b(key); w.put_u32b(data.size());
w.put_u32b(key);
} else {
w.put_u32l(data.size());
w.put_u32l(key);
}
data = prs_compress(data);
// Round size up to a multiple of 4 for encryption // Round size up to a multiple of 4 for encryption
data.resize((data.size() + 3) & ~3); data.resize((data.size() + 3) & ~3);
// For this format, the code section is decrypted without byteswapping on
// the client (GameCube), which is big-endian, so we have to treat the data
// the same way here (hence we can't just use crypt.encrypt).
data = prs_compress(data);
StringReader compressed_r(data);
PSOV2Encryption crypt(key); PSOV2Encryption crypt(key);
while (!compressed_r.eof()) { if (code->is_big_endian()) {
w.put_u32b(compressed_r.get_u32b() ^ crypt.next()); // The code section is decrypted without byteswapping on the client.
// Since PowerPC systems (including the GameCube) are usually
// big-endian, we have to treat the data the same way here (hence we
// can't just use crypt.encrypt).
StringReader compressed_r(data);
while (!compressed_r.eof()) {
w.put_u32b(compressed_r.get_u32b() ^ crypt.next());
}
} else {
crypt.encrypt(data.data(), data.size());
} }
data = move(w.str()); data = move(w.str());