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
+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);