generalize send_function_call a bit
This commit is contained in:
+57
-11
@@ -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 string& suffix) const {
|
||||
S_ExecuteCode_Footer_GC_B2 footer;
|
||||
FooterT footer;
|
||||
footer.num_relocations = this->relocation_deltas.size();
|
||||
footer.unused1.clear(0);
|
||||
footer.entrypoint_addr_offset = this->entrypoint_offset_offset;
|
||||
@@ -57,7 +71,7 @@ string CompiledFunctionCode::generate_client_command(
|
||||
|
||||
footer.relocations_offset = w.size();
|
||||
for (uint16_t delta : this->relocation_deltas) {
|
||||
w.put_u16b(delta);
|
||||
w.put<U16T>(delta);
|
||||
}
|
||||
if (this->relocation_deltas.size() & 1) {
|
||||
w.put_u16(0);
|
||||
@@ -67,13 +81,37 @@ string CompiledFunctionCode::generate_client_command(
|
||||
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(
|
||||
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
|
||||
(void)arch;
|
||||
(void)directory;
|
||||
(void)name;
|
||||
(void)text;
|
||||
throw runtime_error("PowerPC assembler is not available");
|
||||
throw runtime_error("function compiler is not available");
|
||||
|
||||
#else
|
||||
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());
|
||||
ret->arch = arch;
|
||||
ret->name = name;
|
||||
ret->index = 0;
|
||||
|
||||
auto assembled = PPC32Emulator::assemble(text, get_include);
|
||||
ret->code = move(assembled.code);
|
||||
ret->label_offsets = move(assembled.label_offsets);
|
||||
if (arch == CompiledFunctionCode::Architecture::POWERPC) {
|
||||
auto assembled = PPC32Emulator::assemble(text, get_include);
|
||||
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;
|
||||
for (const auto& it : ret->label_offsets) {
|
||||
@@ -149,7 +192,8 @@ FunctionCodeIndex::FunctionCodeIndex(const string& directory) {
|
||||
try {
|
||||
string path = directory + "/" + filename;
|
||||
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 (!this->index_to_function.emplace(code->index, code).second) {
|
||||
throw runtime_error(string_printf(
|
||||
@@ -162,9 +206,11 @@ FunctionCodeIndex::FunctionCodeIndex(const string& directory) {
|
||||
this->name_to_patch_function.emplace(name, code);
|
||||
}
|
||||
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 {
|
||||
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) {
|
||||
|
||||
@@ -20,6 +20,11 @@ bool function_compiler_available();
|
||||
// PPC32 because I haven't written an appropriate x86 assembler yet.
|
||||
|
||||
struct CompiledFunctionCode {
|
||||
enum class Architecture {
|
||||
POWERPC = 0,
|
||||
X86,
|
||||
};
|
||||
Architecture arch;
|
||||
std::string code;
|
||||
std::vector<uint16_t> relocation_deltas;
|
||||
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 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(
|
||||
const std::unordered_map<std::string, uint32_t>& label_writes = {},
|
||||
const std::string& suffix = "") const;
|
||||
};
|
||||
|
||||
const char* name_for_architecture(CompiledFunctionCode::Architecture arch);
|
||||
|
||||
std::shared_ptr<CompiledFunctionCode> compile_function_code(
|
||||
CompiledFunctionCode::Architecture arch,
|
||||
const std::string& directory,
|
||||
const std::string& name,
|
||||
const std::string& text);
|
||||
|
||||
+22
-15
@@ -217,9 +217,6 @@ void send_function_call(
|
||||
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::DOES_NOT_SUPPORT_SEND_FUNCTION_CALL) {
|
||||
throw logic_error("client does not support function calls");
|
||||
}
|
||||
@@ -230,29 +227,39 @@ void send_function_call(
|
||||
string data;
|
||||
uint32_t index = 0;
|
||||
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);
|
||||
index = code->index;
|
||||
|
||||
if (c->flags & Client::Flag::ENCRYPTED_SEND_FUNCTION_CALL) {
|
||||
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;
|
||||
w.put_u32b(data.size());
|
||||
w.put_u32b(key);
|
||||
if (code->is_big_endian()) {
|
||||
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
|
||||
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);
|
||||
while (!compressed_r.eof()) {
|
||||
w.put_u32b(compressed_r.get_u32b() ^ crypt.next());
|
||||
if (code->is_big_endian()) {
|
||||
// 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());
|
||||
|
||||
Reference in New Issue
Block a user