organize system/client-functions

This commit is contained in:
Martin Michelsen
2024-04-20 10:08:00 -07:00
parent c95b158e4e
commit 741456d1da
253 changed files with 247 additions and 130 deletions
+41 -13
View File
@@ -365,23 +365,51 @@ Like quests, Episode 3 card definitions, maps, and quests are cached in memory.
## Memory patches, client functions, and DOL files
Everything in this section requires resource_dasm to be installed, so newserv can use the assemblers and disassemblers from its libresource_file library. If resource_dasm is not installed, newserv will still build and run, but these features will not be available.
*Everything in this section requires resource_dasm to be installed, so newserv can use the assemblers and disassemblers from its libresource_file library. If resource_dasm is not installed, newserv will still build and run, but these features will not be available.*
In addition, these features are only supported for the following game versions:
* PSO GameCube Episodes 1&2 Trial Edition
* PSO GameCube Episodes 1&2 JP, USA, and EU but not Plus
* PSO GameCube Episodes 1&2 Plus JP v1.4 but not v1.5
* PSO GameCube Episode 3 Trial Edition
* PSO GameCube Episode 3 JP
* PSO GameCube Episode 3 USA (experimental; must be manually enabled in config.json)
* PSO Xbox (all versions)
* PSO BB
You can put assembly files in the system/client-functions directory with filenames like PatchName.VERS.patch.s and they will appear in the Patches menu for clients that support client functions. Client functions are written in SH-4, PowerPC, or x86 assembly and are compiled when newserv is started. The assembly system's features are documented in the comments in system/client-functions/System/WriteMemory.ppc.s.
The VERS token in client function filenames refers to the specific version of the game that the client function applies to. Some versions do not support receiving client functions at all. The specific versions are:
| Game | VERS | Supported |
|-------------------|------|-----------|
| PSO DC NTE | 1OJ1 | No |
| PSO DC 11/2000 | 1OJ2 | No |
| PSO DC 12/2000 | 1OJ3 | No |
| PSO DC 01/2001 | 1OJ4 | No |
| PSO DC v1 JP | 1OJF | No |
| PSO DC v1 US | 1OEF | No |
| PSO DC v1 EU | 1OPF | No |
| PSO DC 08/2001 | 2OJ5 | Yes |
| PSO DC v2 JP | 2OJF | Yes |
| PSO DC v2 US | 2OEF | Yes |
| PSO DC v2 EU | 2OPF | Yes |
| PSO PC (v2) | 2OJW | No |
| PSO GC NTE | 3OJT | Yes |
| PSO GC v1.2 JP | 3OJ2 | Yes |
| PSO GC v1.3 JP | 3OJ3 | Yes |
| PSO GC v1.4 JP | 3OJ4 | Yes |
| PSO GC v1.5 JP | 3OJ5 | No |
| PSO GC v1.0 US | 3OE0 | Yes |
| PSO GC v1.1 US | 3OE1 | Yes |
| PSO GC v1.2 US | 3OE2 | No |
| PSO GC v1.0 EU | 3OP0 | Yes |
| PSO GC Ep3 NTE | 3SJT | Yes |
| PSO GC Ep3 JP | 3SJ0 | Yes |
| PSO GC Ep3 US | 3SE0 | No |
| PSO GC Ep3 EU | 3SP0 | No |
| PSO Xbox Beta | 4OJB | Yes |
| PSO Xbox JP Disc | 4OJD | Yes |
| PSO Xbox JP TU | 4OJU | Yes |
| PSO Xbox US Disc | 4OED | Yes |
| PSO Xbox US TU | 4OEU | Yes |
| PSO Xbox EU Disc | 4OPD | Yes |
| PSO Xbox EU TU | 4OPU | Yes |
| PSO BB JP 1.25.13 | 51OC | Yes |
| PSO BB Tethealla | 51OC | Yes |
*Note: newserv uses the shorter GameCube versioning convention, where discs labeled DOL-XXXX-0-0Y are version 1.Y. The PSO community seems to use the convention 1.0Y in some places instead, but these are the same version. For example, the version that newserv calls v1.4 is the same as v1.04, and is labeled DOL-GPOJ-0-04 on the underside of the disc.*
You can put memory patches in the system/client-functions directory with filenames like PatchName.patch.s and they will appear in the Patches menu for PSO GC, XB, and BB clients that support patching. Memory patches are written in PowerPC or x86 assembly and are compiled when newserv is started. The assembly system's features are documented in the comments in system/client-functions/WriteMemory.ppc.s.
newserv comes with a set of patches for GC Episodes 1&2 based on AR codes originally made by Ralf at GC-Forever and Aleron Ives. Many of them were originally posted in [this thread](https://www.gc-forever.com/forums/viewtopic.php?f=38&t=2050).
newserv comes with a set of patches for some of the above versions, based on AR codes originally made by Ralf at GC-Forever and Aleron Ives. Many of them were originally posted in [this thread](https://www.gc-forever.com/forums/viewtopic.php?f=38&t=2050).
You can also put DOL files in the system/dol directory, and they will appear in the Programs menu for GC clients. Selecting a DOL file there will load the file into the GameCube's memory and run it, just like the old homebrew loaders (PSUL and PSOload) did. For this to work, ReadMemoryWord.ppc.s, WriteMemory.ppc.s, and RunDOL.ppc.s must be present in the system/client-functions directory. This has been tested on Dolphin but not on a real GameCube, so results may vary.
+94 -71
View File
@@ -120,12 +120,14 @@ bool CompiledFunctionCode::is_big_endian() const {
shared_ptr<CompiledFunctionCode> compile_function_code(
CompiledFunctionCode::Architecture arch,
const string& directory,
const string& function_directory,
const string& system_directory,
const string& name,
const string& text) {
#ifndef HAVE_RESOURCE_FILE
(void)arch;
(void)directory;
(void)function_directory;
(void)system_directory;
(void)name;
(void)text;
throw runtime_error("function compiler is not available");
@@ -154,7 +156,11 @@ shared_ptr<CompiledFunctionCode> compile_function_code(
throw runtime_error("unknown architecture");
}
string asm_filename = string_printf("%s/%s.%s.inc.s", directory.c_str(), name.c_str(), arch_name_token);
// Look in the function directory first, then the system directory
string asm_filename = string_printf("%s/%s.%s.inc.s", function_directory.c_str(), name.c_str(), arch_name_token);
if (!isfile(asm_filename)) {
asm_filename = string_printf("%s/%s.%s.inc.s", system_directory.c_str(), name.c_str(), arch_name_token);
}
if (isfile(asm_filename)) {
if (!get_include_stack.emplace(name).second) {
throw runtime_error("mutual recursion between includes: " + name);
@@ -176,7 +182,12 @@ shared_ptr<CompiledFunctionCode> compile_function_code(
get_include_stack.erase(name);
return ret.code;
}
string bin_filename = directory + "/" + name + ".inc.bin";
string bin_filename = function_directory + "/" + name + ".inc.bin";
if (isfile(bin_filename)) {
return load_file(bin_filename);
}
bin_filename = system_directory + "/" + name + ".inc.bin";
if (isfile(bin_filename)) {
return load_file(bin_filename);
}
@@ -245,83 +256,95 @@ FunctionCodeIndex::FunctionCodeIndex(const string& directory) {
return;
}
uint32_t next_menu_item_id = 0;
for (const auto& filename : list_directory_sorted(directory)) {
try {
if (!ends_with(filename, ".s")) {
continue;
}
string system_dir_path = ends_with(directory, "/") ? (directory + "System") : (directory + "/System");
string name = filename.substr(0, filename.size() - 2);
if (ends_with(name, ".inc")) {
continue;
}
uint32_t next_menu_item_id = 1;
for (const auto& subdir_name : list_directory_sorted(directory)) {
string subdir_path = ends_with(directory, "/") ? (directory + subdir_name) : (directory + "/" + subdir_name);
if (!isdir(subdir_path)) {
function_compiler_log.warning("Skipping %s (not a directory)", subdir_name.c_str());
continue;
}
bool is_patch = ends_with(name, ".patch");
if (is_patch) {
name.resize(name.size() - 6);
}
for (const auto& filename : list_directory_sorted(subdir_path)) {
try {
if (!ends_with(filename, ".s")) {
continue;
}
// Figure out the version or specific_version
CompiledFunctionCode::Architecture arch = CompiledFunctionCode::Architecture::UNKNOWN;
uint32_t specific_version = 0;
string short_name = name;
if (ends_with(name, ".ppc")) {
arch = CompiledFunctionCode::Architecture::POWERPC;
name.resize(name.size() - 4);
short_name = name;
} else if (ends_with(name, ".x86")) {
arch = CompiledFunctionCode::Architecture::X86;
name.resize(name.size() - 4);
short_name = name;
} else if (ends_with(name, ".sh4")) {
arch = CompiledFunctionCode::Architecture::SH4;
name.resize(name.size() - 4);
short_name = name;
} else if (is_patch && (name.size() >= 5) && (name[name.size() - 5] == '.')) {
specific_version = (name[name.size() - 4] << 24) | (name[name.size() - 3] << 16) | (name[name.size() - 2] << 8) | name[name.size() - 1];
if (specific_version_is_gc(specific_version)) {
string name = filename.substr(0, filename.size() - 2);
if (ends_with(name, ".inc")) {
continue;
}
bool is_patch = ends_with(name, ".patch");
if (is_patch) {
name.resize(name.size() - 6);
}
// Figure out the version or specific_version
CompiledFunctionCode::Architecture arch = CompiledFunctionCode::Architecture::UNKNOWN;
uint32_t specific_version = 0;
string short_name = name;
if (ends_with(name, ".ppc")) {
arch = CompiledFunctionCode::Architecture::POWERPC;
} else if (specific_version_is_xb(specific_version) || specific_version_is_bb(specific_version)) {
name.resize(name.size() - 4);
short_name = name;
} else if (ends_with(name, ".x86")) {
arch = CompiledFunctionCode::Architecture::X86;
} else {
throw runtime_error("unable to determine architecture from specific_version");
name.resize(name.size() - 4);
short_name = name;
} else if (ends_with(name, ".sh4")) {
arch = CompiledFunctionCode::Architecture::SH4;
name.resize(name.size() - 4);
short_name = name;
} else if (is_patch && (name.size() >= 5) && (name[name.size() - 5] == '.')) {
specific_version = (name[name.size() - 4] << 24) | (name[name.size() - 3] << 16) | (name[name.size() - 2] << 8) | name[name.size() - 1];
if (specific_version_is_dc(specific_version)) {
arch = CompiledFunctionCode::Architecture::SH4;
} else if (specific_version_is_gc(specific_version)) {
arch = CompiledFunctionCode::Architecture::POWERPC;
} else if (specific_version_is_xb(specific_version) || specific_version_is_bb(specific_version)) {
arch = CompiledFunctionCode::Architecture::X86;
} else {
throw runtime_error("unable to determine architecture from specific_version");
}
short_name = name.substr(0, name.size() - 5);
}
short_name = name.substr(0, name.size() - 5);
}
if (arch == CompiledFunctionCode::Architecture::UNKNOWN) {
throw runtime_error("unable to determine architecture");
}
string path = directory + "/" + filename;
string text = load_file(path);
auto code = compile_function_code(arch, directory, name, text);
if (code->index != 0) {
if (!this->index_to_function.emplace(code->index, code).second) {
throw runtime_error(string_printf(
"duplicate function index: %08" PRIX32, code->index));
if (arch == CompiledFunctionCode::Architecture::UNKNOWN) {
throw runtime_error("unable to determine architecture");
}
}
code->specific_version = specific_version;
code->source_path = path;
code->short_name = short_name;
this->name_to_function.emplace(name, code);
if (is_patch) {
code->menu_item_id = next_menu_item_id++;
this->menu_item_id_and_specific_version_to_patch_function.emplace(
static_cast<uint64_t>(code->menu_item_id) << 32 | specific_version, code);
this->name_and_specific_version_to_patch_function.emplace(
string_printf("%s-%08" PRIX32, short_name.c_str(), specific_version), code);
}
string index_prefix = code->index ? string_printf("%02X => ", code->index) : "";
string patch_prefix = is_patch ? string_printf("[%08" PRIX32 "/%08" PRIX32 "] ", code->menu_item_id, code->specific_version) : "";
function_compiler_log.info("Compiled function %s%s%s (%s)",
index_prefix.c_str(), patch_prefix.c_str(), name.c_str(), name_for_architecture(code->arch));
string path = subdir_path + "/" + filename;
string text = load_file(path);
auto code = compile_function_code(arch, subdir_path, system_dir_path, name, text);
if (code->index != 0) {
if (!this->index_to_function.emplace(code->index, code).second) {
throw runtime_error(string_printf(
"duplicate function index: %08" PRIX32, code->index));
}
}
code->specific_version = specific_version;
code->source_path = path;
code->short_name = short_name;
this->name_to_function.emplace(name, code);
if (is_patch) {
code->menu_item_id = next_menu_item_id++;
this->menu_item_id_and_specific_version_to_patch_function.emplace(
static_cast<uint64_t>(code->menu_item_id) << 32 | specific_version, code);
this->name_and_specific_version_to_patch_function.emplace(
string_printf("%s-%08" PRIX32, short_name.c_str(), specific_version), code);
}
} catch (const exception& e) {
function_compiler_log.warning("Failed to compile function %s: %s", filename.c_str(), e.what());
string index_prefix = code->index ? string_printf("%02X => ", code->index) : "";
string patch_prefix = is_patch ? string_printf("[%08" PRIX32 "/%08" PRIX32 "] ", code->menu_item_id, code->specific_version) : "";
function_compiler_log.info("Compiled function %s%s%s (%s)",
index_prefix.c_str(), patch_prefix.c_str(), name.c_str(), name_for_architecture(code->arch));
} catch (const exception& e) {
function_compiler_log.warning("Failed to compile function %s: %s", filename.c_str(), e.what());
}
}
}
}
+1
View File
@@ -1221,6 +1221,7 @@ static HandlerResult C_GXB_61(shared_ptr<ProxyServer::LinkedSession> ses, uint16
if (is_ep3(ses->version()) && (ses->version() != Version::GC_EP3_NTE)) {
ses->log.info("Version changed to GC_EP3_NTE");
ses->set_version(Version::GC_EP3_NTE);
ses->config.specific_version = SPECIFIC_VERSION_GC_EP3_NTE;
}
pd = &check_size_t<C_CharacterData_V3_61_98>(data, 0xFFFF);
}
+11
View File
@@ -277,6 +277,7 @@ void ProxyServer::UnlinkedSession::on_input(Channel& ch, uint16_t command, uint3
if (command == 0x8B) {
ses->channel.version = Version::DC_NTE;
ses->log.info("Version changed to DC_NTE");
ses->config.specific_version = SPECIFIC_VERSION_DC_NTE;
const auto& cmd = check_size_t<C_Login_DCNTE_8B>(data, sizeof(C_LoginExtended_DCNTE_8B));
ses->login = s->account_index->from_dc_nte_credentials(cmd.serial_number.decode(), cmd.access_key.decode(), false);
ses->sub_version = cmd.sub_version;
@@ -286,6 +287,9 @@ void ProxyServer::UnlinkedSession::on_input(Channel& ch, uint16_t command, uint3
} else if (command == 0x93) { // 11/2000 proto through DC V1
ses->channel.version = Version::DC_V1;
ses->log.info("Version changed to DC_V1");
if (specific_version_is_indeterminate(ses->config.specific_version)) {
ses->config.specific_version = SPECIFIC_VERSION_DC_V1_INDETERMINATE;
}
const auto& cmd = check_size_t<C_LoginV1_DC_93>(data);
ses->login = s->account_index->from_dc_credentials(
stoul(cmd.serial_number.decode(), nullptr, 16), cmd.access_key.decode(), cmd.name.decode(), false);
@@ -298,11 +302,15 @@ void ProxyServer::UnlinkedSession::on_input(Channel& ch, uint16_t command, uint3
if (cmd.sub_version >= 0x30) {
ses->log.info("Version changed to GC_NTE");
ses->channel.version = Version::GC_NTE;
ses->config.specific_version = SPECIFIC_VERSION_GC_NTE;
ses->login = s->account_index->from_gc_credentials(
stoul(cmd.serial_number.decode(), nullptr, 16), cmd.access_key.decode(), nullptr, cmd.name.decode(), false);
} else { // DC V2
ses->log.info("Version changed to DC_V2");
ses->channel.version = Version::DC_V2;
if (specific_version_is_indeterminate(ses->config.specific_version)) {
ses->config.specific_version = SPECIFIC_VERSION_DC_V2_INDETERMINATE;
}
ses->login = s->account_index->from_dc_credentials(
stoul(cmd.serial_number.decode(), nullptr, 16), cmd.access_key.decode(), cmd.name.decode(), false);
}
@@ -345,6 +353,9 @@ void ProxyServer::UnlinkedSession::on_input(Channel& ch, uint16_t command, uint3
if (cmd.sub_version >= 0x40) {
ses->log.info("Version changed to GC_EP3");
ses->channel.version = Version::GC_EP3;
if (specific_version_is_indeterminate(ses->config.specific_version)) {
ses->config.specific_version = SPECIFIC_VERSION_GC_EP3_INDETERMINATE;
}
}
} else {
throw runtime_error("command is not 9D or 9E");
+28 -7
View File
@@ -341,9 +341,9 @@ static void send_main_menu(shared_ptr<Client> c) {
if (!s->is_replay) {
if (!s->function_code_index->patch_menu_empty(c->config.specific_version)) {
main_menu->items.emplace_back(MainMenuItemID::PATCHES, "Patches",
"Change game\nbehaviors", MenuItem::Flag::INVISIBLE_ON_DC | MenuItem::Flag::INVISIBLE_ON_PC | MenuItem::Flag::REQUIRES_SEND_FUNCTION_CALL);
"Change game\nbehaviors", MenuItem::Flag::INVISIBLE_ON_PC | MenuItem::Flag::REQUIRES_SEND_FUNCTION_CALL);
main_menu->items.emplace_back(MainMenuItemID::PATCH_SWITCHES, "Patch switches",
"Automatically\napply patches every\ntime you connect", MenuItem::Flag::INVISIBLE_ON_DC | MenuItem::Flag::INVISIBLE_ON_PC | MenuItem::Flag::REQUIRES_SEND_FUNCTION_CALL);
"Automatically\napply patches every\ntime you connect", MenuItem::Flag::INVISIBLE_ON_PC | MenuItem::Flag::REQUIRES_SEND_FUNCTION_CALL);
}
if (!s->dol_file_index->empty()) {
main_menu->items.emplace_back(MainMenuItemID::PROGRAMS, "Programs",
@@ -480,22 +480,32 @@ static void set_console_client_flags(shared_ptr<Client> c, uint32_t sub_version)
if (sub_version <= 0x24) {
c->channel.version = Version::DC_V1;
c->log.info("Game version changed to DC_V1");
if (specific_version_is_indeterminate(c->config.specific_version) || c->config.specific_version == SPECIFIC_VERSION_DC_11_2000_PROTOTYPE) {
c->config.specific_version = SPECIFIC_VERSION_DC_V1_INDETERMINATE;
}
} else if (sub_version <= 0x28) {
c->channel.version = Version::DC_V2;
c->log.info("Game version changed to DC_V2");
if (specific_version_is_indeterminate(c->config.specific_version)) {
c->config.specific_version = SPECIFIC_VERSION_DC_V2_INDETERMINATE;
}
} else if (is_v3(c->version())) {
c->channel.version = Version::GC_NTE;
c->log.info("Game version changed to GC_NTE");
c->config.specific_version = SPECIFIC_VERSION_GC_NTE;
}
} else {
if (sub_version >= 0x40 && !is_ep3(c->version())) {
c->channel.version = Version::GC_EP3;
c->log.info("Game version changed to GC_EP3");
if (specific_version_is_indeterminate(c->config.specific_version)) {
c->config.specific_version = SPECIFIC_VERSION_GC_EP3_INDETERMINATE;
}
}
}
c->config.set_flags_for_version(c->version(), sub_version);
c->sub_version = sub_version;
if (c->config.specific_version == default_specific_version_for_version(c->version(), -1)) {
if (specific_version_is_indeterminate(c->config.specific_version)) {
c->config.specific_version = default_specific_version_for_version(c->version(), sub_version);
}
}
@@ -534,6 +544,7 @@ static void on_88_DCNTE(shared_ptr<Client> c, uint16_t, uint32_t, string& data)
auto s = c->require_server_state();
c->channel.version = Version::DC_NTE;
c->config.specific_version = SPECIFIC_VERSION_DC_NTE;
c->config.set_flags_for_version(c->version(), -1);
c->log.info("Game version changed to DC_NTE");
@@ -560,6 +571,7 @@ static void on_8B_DCNTE(shared_ptr<Client> c, uint16_t, uint32_t, string& data)
c->channel.version = Version::DC_NTE;
c->channel.language = cmd.language;
c->config.set_flags_for_version(c->version(), -1);
c->config.specific_version = SPECIFIC_VERSION_DC_NTE;
c->log.info("Game version changed to DC_NTE");
try {
@@ -592,6 +604,9 @@ static void on_90_DC(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
c->channel.version = Version::DC_V1;
c->config.set_flags_for_version(c->version(), -1);
if (specific_version_is_indeterminate(c->config.specific_version) || c->config.specific_version == SPECIFIC_VERSION_DC_11_2000_PROTOTYPE) {
c->config.specific_version = SPECIFIC_VERSION_DC_V1_INDETERMINATE;
}
c->log.info("Game version changed to DC_V1");
string serial_number_str = cmd.serial_number.decode();
@@ -624,6 +639,9 @@ static void on_92_DC(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
// client is actually DCv1 and not the prototype.
c->config.set_flag(Client::Flag::CHECKED_FOR_DC_V1_PROTOTYPE);
c->channel.version = Version::DC_V1;
if (specific_version_is_indeterminate(c->config.specific_version) || c->config.specific_version == SPECIFIC_VERSION_DC_11_2000_PROTOTYPE) {
c->config.specific_version = SPECIFIC_VERSION_DC_V1_INDETERMINATE;
}
c->log.info("Game version changed to DC_V1");
send_command(c, 0x92, 0x01);
}
@@ -680,6 +698,9 @@ static void on_93_DC(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
send_command(c, 0x90, 0x01);
c->config.set_flag(Client::Flag::CHECKED_FOR_DC_V1_PROTOTYPE);
c->channel.version = Version::DC_V1_11_2000_PROTOTYPE;
if (specific_version_is_indeterminate(c->config.specific_version)) {
c->config.specific_version = SPECIFIC_VERSION_DC_11_2000_PROTOTYPE;
}
c->log.info("Game version changed to DC_V1_11_2000_PROTOTYPE (will be changed to V1 if 92 is received)");
} else {
on_login_complete(c);
@@ -3006,16 +3027,16 @@ static void on_61_98(shared_ptr<Client> c, uint16_t command, uint32_t flag, stri
const auto* cmd3 = &check_size_t<C_CharacterData_Ep3_61_98>(data);
c->ep3_config = make_shared<Episode3::PlayerConfig>(cmd3->ep3_config);
cmd = reinterpret_cast<const C_CharacterData_V3_61_98*>(cmd3);
if (c->config.specific_version == 0x00000000) {
c->config.specific_version = 0x33534A30; // 3SJ0
if (specific_version_is_indeterminate(c->config.specific_version)) {
c->config.specific_version = SPECIFIC_VERSION_GC_EP3_JP; // 3SJ0
}
} else {
if (is_ep3(c->version())) {
c->channel.version = Version::GC_EP3_NTE;
c->log.info("Game version changed to GC_EP3_NTE");
c->config.clear_flag(Client::Flag::ENCRYPTED_SEND_FUNCTION_CALL);
if (c->config.specific_version == default_specific_version_for_version(Version::GC_EP3, -1)) {
c->config.specific_version = 0x33534A54; // 3SJT
if (specific_version_is_indeterminate(c->config.specific_version)) {
c->config.specific_version = SPECIFIC_VERSION_GC_EP3_NTE;
}
c->convert_account_to_temporary_if_nte();
}
+35 -18
View File
@@ -207,56 +207,73 @@ uint32_t default_specific_version_for_version(Version version, int64_t sub_versi
// VersionDetectDC, VersionDetectGC, or VersionDetectXB call.
switch (version) {
case Version::DC_NTE:
return 0x314F4A31;
return SPECIFIC_VERSION_DC_NTE;
case Version::DC_V1_11_2000_PROTOTYPE:
return 0x314F4A32;
return SPECIFIC_VERSION_DC_11_2000_PROTOTYPE;
case Version::DC_V1:
return 0x00000000; // Need to send VersionDetectDC (but can't on V1; rip)
return SPECIFIC_VERSION_DC_V1_INDETERMINATE; // Need to send VersionDetectDC (but can't on V1; rip)
case Version::DC_V2:
return SPECIFIC_VERSION_DC_V2_INDETERMINATE; // Need to send VersionDetectDC
case Version::PC_V2:
return SPECIFIC_VERSION_PC_V2;
case Version::GC_NTE:
return 0x334F4A54; // 3OJT
return SPECIFIC_VERSION_GC_NTE; // 3OJT
case Version::GC_V3:
switch (sub_version) {
case 0x32: // GC Ep1&2 EU 50Hz
case 0x33: // GC Ep1&2 EU 60Hz
return 0x334F5030; // 3OP0
return SPECIFIC_VERSION_GC_V3_EU; // 3OP0
case 0x36: // GC Ep1&2 US v1.2 (Plus)
return 0x334F4532; // 3OE2
case 0x39: // GC Ep1&2 JP v1.5 (Plus)
return 0x334F4A35; // 3OJ5
return SPECIFIC_VERSION_GC_V3_US_12; // 3OE2
case 0x34: // GC Ep1&2 JP v1.3
return 0x334F4A33; // 3OJ3
return SPECIFIC_VERSION_GC_V3_JP_13; // 3OJ3
case 0x35: // GC Ep1&2 JP v1.4 (Plus)
return 0x334F4A34; // 3OJ4
return SPECIFIC_VERSION_GC_V3_JP_14; // 3OJ4
case 0x39: // GC Ep1&2 JP v1.5 (Plus)
return SPECIFIC_VERSION_GC_V3_JP_15; // 3OJ5
case -1: // Initial check (before sub_version recognition)
case 0x30: // GC Ep1&2 GameJam demo, GC Ep1&2 Trial Edition, GC Ep1&2 JP v1.2, at least one version of PSO XB
case 0x31: // GC Ep1&2 US v1.0, GC US v1.1, XB US
default:
return 0x00000000; // Need to send VersionDetectGC
return SPECIFIC_VERSION_GC_V3_INDETERMINATE; // Need to send VersionDetectGC
}
throw logic_error("this should be impossible");
case Version::GC_EP3_NTE:
return 0x33534A54; // 3SJT
return SPECIFIC_VERSION_GC_EP3_NTE; // 3SJT
case Version::GC_EP3:
switch (sub_version) {
case 0x41: // GC Ep3 US
return 0x33534530; // 3SE0
return SPECIFIC_VERSION_GC_EP3_US; // 3SE0
case 0x42: // GC Ep3 EU 50Hz
case 0x43: // GC Ep3 EU 60Hz
return 0x33535030; // 3SP0
return SPECIFIC_VERSION_GC_EP3_EU; // 3SP0
case -1: // Initial check (before sub_version recognition)
case 0x40: // GC Ep3 trial and GC Ep3 JP
default:
return 0x00000000; // Need to send VersionDetectGC
return SPECIFIC_VERSION_GC_EP3_INDETERMINATE; // Need to send VersionDetectGC
}
case Version::XB_V3:
return 0x344F0000;
return SPECIFIC_VERSION_XB_V3_INDETERMINATE;
case Version::BB_V4:
return 0x354F3030;
return SPECIFIC_VERSION_BB_V4_INDETERMINATE;
default:
return 0x00000000;
return SPECIFIC_VERSION_INDETERMINATE;
}
}
bool specific_version_is_indeterminate(uint32_t specific_version) {
return ((specific_version & 0x000000FF) == 0);
}
bool specific_version_is_dc(uint32_t specific_version) {
// All v1 and v2 specific_versions are DC except 324F4A57 (2OJW), which is PC
uint8_t major_version = specific_version >> 24;
if (major_version < 0x31 || major_version > 0x32) {
return false;
}
return (specific_version != 0x324F4A57);
}
bool specific_version_is_gc(uint32_t specific_version) {
// GC specific_versions are 3GRV, where G is [OE], R is [JEP], V is [0-9T]
if ((specific_version & 0xFF000000) != 0x33000000) {
+23
View File
@@ -140,7 +140,30 @@ inline bool uses_utf16(Version version) {
(version == Version::BB_V4);
}
constexpr uint32_t SPECIFIC_VERSION_DC_NTE = 0x314F4A31; // 1OJ1
constexpr uint32_t SPECIFIC_VERSION_DC_11_2000_PROTOTYPE = 0x314F4A32; // 1OJ2
constexpr uint32_t SPECIFIC_VERSION_DC_V1_INDETERMINATE = 0x31000000; // 1___
constexpr uint32_t SPECIFIC_VERSION_DC_V2_INDETERMINATE = 0x32000000; // 2___
constexpr uint32_t SPECIFIC_VERSION_PC_V2 = 0x324F4A57; // 2OJW
constexpr uint32_t SPECIFIC_VERSION_GC_NTE = 0x334F4A54; // 3OJT
constexpr uint32_t SPECIFIC_VERSION_GC_V3_EU = 0x334F5030; // 3OP0
constexpr uint32_t SPECIFIC_VERSION_GC_V3_US_12 = 0x334F4532; // 3OE2
constexpr uint32_t SPECIFIC_VERSION_GC_V3_JP_13 = 0x334F4A33; // 3OJ3
constexpr uint32_t SPECIFIC_VERSION_GC_V3_JP_14 = 0x334F4A34; // 3OJ4
constexpr uint32_t SPECIFIC_VERSION_GC_V3_JP_15 = 0x334F4A35; // 3OJ5
constexpr uint32_t SPECIFIC_VERSION_GC_V3_INDETERMINATE = 0x334F0000; // 3O__
constexpr uint32_t SPECIFIC_VERSION_GC_EP3_INDETERMINATE = 0x33534A00; // 3SJ_
constexpr uint32_t SPECIFIC_VERSION_GC_EP3_NTE = 0x33534A54; // 3SJT
constexpr uint32_t SPECIFIC_VERSION_GC_EP3_JP = 0x33534A30; // 3SJ0
constexpr uint32_t SPECIFIC_VERSION_GC_EP3_US = 0x33534530; // 3SE0
constexpr uint32_t SPECIFIC_VERSION_GC_EP3_EU = 0x33535030; // 3SP0
constexpr uint32_t SPECIFIC_VERSION_XB_V3_INDETERMINATE = 0x344F0000; // 4O__
constexpr uint32_t SPECIFIC_VERSION_BB_V4_INDETERMINATE = 0x35000000; // 5___
constexpr uint32_t SPECIFIC_VERSION_INDETERMINATE = 0x00000000; // ____
uint32_t default_specific_version_for_version(Version version, int64_t sub_version);
bool specific_version_is_indeterminate(uint32_t specific_version);
bool specific_version_is_dc(uint32_t specific_version);
bool specific_version_is_gc(uint32_t specific_version);
bool specific_version_is_xb(uint32_t specific_version);
bool specific_version_is_bb(uint32_t specific_version);

Some files were not shown because too many files have changed in this diff Show More