clean up patch enable quest logic

This commit is contained in:
Martin Michelsen
2024-08-15 20:23:10 -07:00
parent 7d775a38d1
commit 4426476a15
24 changed files with 108 additions and 81 deletions
+2 -4
View File
@@ -85,10 +85,8 @@ void Client::Config::set_flags_for_version(Version version, int64_t sub_version)
case 0x20: // DCNTE, possibly also DCv1 JP
case 0x21: // DCv1 US
this->set_flag(Flag::NO_D6);
break;
case 0x22: // DCv1 EU 50Hz (presumably)
case 0x23: // DCv1 EU 60Hz (presumably)
case 0x22: // DCv1 EU, 12/2000, and 01/2001, at 50Hz (presumably)
case 0x23: // DCv1 EU, 12/2000, and 01/2001, at 60Hz (presumably)
this->set_flag(Flag::NO_D6);
break;
case 0x25: // DCv2 JP
+2 -1
View File
@@ -1671,7 +1671,8 @@ struct C_LoginV1_DC_PC_V3_90 {
// 92 (C->S): Register (DC)
struct C_RegisterV1_DC_92 {
parray<uint8_t, 0x0C> unknown_a1;
parray<uint8_t, 0x08> unknown_a1;
le_uint32_t sub_version;
uint8_t is_extended = 0; // TODO: This is a guess
uint8_t language = 0; // TODO: This is a guess; verify it
parray<uint8_t, 2> unknown_a3;
+5 -1
View File
@@ -1246,7 +1246,11 @@ Action a_assemble_quest_script(
string result = assemble_quest_script(text, include_dir);
bool compress = !args.get<bool>("decompressed");
if (compress) {
result = prs_compress_optimal(result);
if (args.get<bool>("optimal")) {
result = prs_compress_optimal(result);
} else {
result = prs_compress(result);
}
}
write_output_data(args, result.data(), result.size(), compress ? "bin" : "bind");
});
+1 -1
View File
@@ -349,7 +349,7 @@ void PatchServer::on_listen_accept(struct evconnlistener* listener, evutil_socke
c->channel.context_obj = this;
this->channel_to_client.emplace(&c->channel, c);
server_log.info("Patch client connected: U-%" PRIX64 " on fd %d via %d (%s)",
server_log.info("Patch client connected: C-%" PRIX64 " on fd %d via %d (%s)",
c->id, fd, listen_fd, listening_socket->addr_str.c_str());
this->send_server_init(c);
+1 -4
View File
@@ -459,15 +459,12 @@ bool Quest::has_version_any_language(Version v) const {
return ((it != this->versions.end()) && ((it->first & 0xFF00) == k));
}
shared_ptr<const VersionedQuest> Quest::version(Version v, uint8_t language, bool allow_language_fallback) const {
shared_ptr<const VersionedQuest> Quest::version(Version v, uint8_t language) const {
// Return the requested version, if it exists
try {
return this->versions.at(this->versions_key(v, language));
} catch (const out_of_range&) {
}
if (!allow_language_fallback) {
return nullptr;
}
// Return the English version, if it exists
try {
+1 -1
View File
@@ -123,7 +123,7 @@ public:
void add_version(std::shared_ptr<const VersionedQuest> vq);
bool has_version(Version v, uint8_t language) const;
bool has_version_any_language(Version v) const;
std::shared_ptr<const VersionedQuest> version(Version v, uint8_t language, bool allow_language_fallback = true) const;
std::shared_ptr<const VersionedQuest> version(Version v, uint8_t language) const;
static uint32_t versions_key(Version v, uint8_t language);
+23 -7
View File
@@ -514,7 +514,7 @@ static const QuestScriptOpcodeDefinition opcode_defs[] = {
{0x00EE, "pl_add_meseta2", {INT32}, F_V1_V4 | F_ARGS},
{0x00EF, "sync_register2", {INT32, REG32}, F_V1_V2},
{0x00EF, "sync_register2", {REG, INT32}, F_V3_V4 | F_ARGS},
{0x00F0, "send_regwork", {INT32, REG32}, F_V1_V2},
{0x00F0, "send_regwork", {REG32, REG32}, F_V1_V2},
{0x00F1, "leti_fixed_camera", {{REG32_SET_FIXED, 6}}, F_V2},
{0x00F1, "leti_fixed_camera", {{REG_SET_FIXED, 6}}, F_V3_V4},
{0x00F2, "default_camera_pos1", {}, F_V2_V4},
@@ -2214,6 +2214,14 @@ std::string assemble_quest_script(const std::string& text, const std::string& in
} else if (phosg::starts_with(line, ".zero ")) {
size_t size = stoull(line.substr(6), nullptr, 0);
code_w.extend_by(size, 0x00);
} else if (phosg::starts_with(line, ".zero_until ")) {
size_t size = stoull(line.substr(12), nullptr, 0);
code_w.extend_to(size, 0x00);
} else if (phosg::starts_with(line, ".align ")) {
size_t alignment = stoull(line.substr(7), nullptr, 0);
while (code_w.size() % alignment) {
code_w.put_u8(0);
}
} else if (phosg::starts_with(line, ".include_bin ")) {
string filename = line.substr(13);
phosg::strip_whitespace(filename);
@@ -2286,10 +2294,10 @@ std::string assemble_quest_script(const std::string& text, const std::string& in
phosg::strip_leading_whitespace(arg);
try {
auto add_cstr = [&](const string& text) -> void {
auto add_cstr = [&](const string& text, bool bin) -> void {
switch (quest_version) {
case Version::DC_NTE:
code_w.write(tt_utf8_to_sega_sjis(text));
code_w.write(bin ? text : tt_utf8_to_sega_sjis(text));
code_w.put_u8(0);
break;
case Version::DC_V1_11_2000_PROTOTYPE:
@@ -2300,13 +2308,13 @@ std::string assemble_quest_script(const std::string& text, const std::string& in
case Version::GC_EP3_NTE:
case Version::GC_EP3:
case Version::XB_V3:
code_w.write(quest_language ? tt_utf8_to_8859(text) : tt_utf8_to_sega_sjis(text));
code_w.write(bin ? text : (quest_language ? tt_utf8_to_8859(text) : tt_utf8_to_sega_sjis(text)));
code_w.put_u8(0);
break;
case Version::PC_NTE:
case Version::PC_V2:
case Version::BB_V4:
code_w.write(tt_utf8_to_utf16(text));
code_w.write(bin ? text : tt_utf8_to_utf16(text));
code_w.put_u16(0);
break;
default:
@@ -2374,7 +2382,11 @@ std::string assemble_quest_script(const std::string& text, const std::string& in
if (write_as_str) {
if (arg[0] == '\"') {
code_w.put_u8(0x4E); // arg_pushs
add_cstr(phosg::parse_data_string(arg));
if (phosg::starts_with(arg, "bin:")) {
add_cstr(phosg::parse_data_string(arg.substr(4)), true);
} else {
add_cstr(phosg::parse_data_string(arg), false);
}
} else {
throw runtime_error("invalid argument syntax");
}
@@ -2460,7 +2472,11 @@ std::string assemble_quest_script(const std::string& text, const std::string& in
code_w.put_u32l(stof(arg, nullptr));
break;
case Type::CSTRING:
add_cstr(phosg::parse_data_string(arg));
if (phosg::starts_with(arg, "bin:")) {
add_cstr(phosg::parse_data_string(arg.substr(4)), true);
} else {
add_cstr(phosg::parse_data_string(arg), false);
}
break;
default:
throw logic_error("unknown argument type");
+17 -26
View File
@@ -405,28 +405,15 @@ void on_login_complete(shared_ptr<Client> c) {
auto s = c->require_server_state();
if (c->config.check_flag(Client::Flag::CAN_RECEIVE_ENABLE_B2_QUEST) &&
!c->config.check_flag(Client::Flag::HAS_SEND_FUNCTION_CALL) &&
(s->enable_send_function_call_quest_num >= 0)) {
auto q = s->default_quest_index->get(s->enable_send_function_call_quest_num);
!c->config.check_flag(Client::Flag::HAS_SEND_FUNCTION_CALL)) {
shared_ptr<const Quest> q;
try {
int64_t quest_num = s->enable_send_function_call_quest_numbers.at(c->config.specific_version);
q = s->default_quest_index->get(quest_num);
} catch (const out_of_range&) {
}
if (q) {
uint8_t q_language;
switch (c->sub_version) {
case 0x39:
q_language = 0; // Japanese (JP Plus v1.5)
break;
case 0x42:
case 0x43:
q_language = 2; // German (EU Ep3)
break;
case 0x41:
q_language = 4; // Spanish (US Ep3)
break;
case 0x36:
case 0x3A:
default:
q_language = 1; // English (US Plus v1.2 + customizations)
}
auto vq = q->version(is_ep3(c->version()) ? Version::GC_V3 : c->version(), q_language, false);
auto vq = q->version(is_ep3(c->version()) ? Version::GC_V3 : c->version(), 1);
if (vq) {
c->config.set_flag(Client::Flag::HAS_SEND_FUNCTION_CALL);
c->config.set_flag(Client::Flag::SEND_FUNCTION_CALL_NO_CACHE_PATCH);
@@ -440,7 +427,9 @@ void on_login_complete(shared_ptr<Client> c) {
send_open_quest_file(c, bin_filename, bin_filename, xb_filename, vq->quest_number, QuestFileType::ONLINE, vq->bin_contents);
send_open_quest_file(c, dat_filename, dat_filename, xb_filename, vq->quest_number, QuestFileType::ONLINE, vq->dat_contents);
send_command(c, 0xAC, 0x00);
if (!is_v1_or_v2(c->version())) {
send_command(c, 0xAC, 0x00);
}
}
}
}
@@ -555,7 +544,8 @@ 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) {
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) {
@@ -709,14 +699,15 @@ static void on_90_DC(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
static void on_92_DC(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
const auto& cmd = check_size_t<C_RegisterV1_DC_92>(data);
c->channel.language = cmd.language;
// It appears that in response to 90 01, the DCv1 prototype sends 93 rather
// than 92, so we use the presence of a 92 command to determine that the
// client is actually DCv1 and not the prototype.
// It appears that in response to 90 01, 11/2000 sends 93 rather than 92, so
// we use the presence of a 92 command to determine that the 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;
}
set_console_client_flags(c, cmd.sub_version);
c->log.info("Game version changed to DC_V1");
send_command(c, 0x92, 0x01);
}
+14 -1
View File
@@ -794,7 +794,20 @@ void ServerState::load_config_early() {
this->default_rare_notifs_enabled_v3_v4 = this->default_rare_notifs_enabled_v1_v2;
this->default_rare_notifs_enabled_v1_v2 = this->config_json->get_bool("RareNotificationsEnabledByDefaultV1V2", this->default_rare_notifs_enabled_v1_v2);
this->default_rare_notifs_enabled_v3_v4 = this->config_json->get_bool("RareNotificationsEnabledByDefaultV3V4", this->default_rare_notifs_enabled_v3_v4);
this->enable_send_function_call_quest_num = this->config_json->get_int("EnableSendFunctionCallQuestNumber", -1);
this->enable_send_function_call_quest_numbers.clear();
try {
for (const auto& it : this->config_json->get_dict("EnableSendFunctionCallQuestNumbers")) {
if (it.first.size() != 4) {
throw runtime_error(phosg::string_printf(
"specific_version %s in EnableSendFunctionCallQuestNumbers is not a 4-byte string",
it.first.c_str()));
}
uint32_t specific_version = phosg::StringReader(it.first).get_u32b();
int64_t quest_num = it.second->as_int();
this->enable_send_function_call_quest_numbers.emplace(specific_version, quest_num);
}
} catch (const out_of_range&) {
}
this->enable_v3_v4_protected_subcommands = this->config_json->get_bool("EnableV3V4ProtectedSubcommands", false);
this->catch_handler_exceptions = this->config_json->get_bool("CatchHandlerExceptions", true);
+1 -1
View File
@@ -120,7 +120,7 @@ struct ServerState : public std::enable_shared_from_this<ServerState> {
std::unordered_map<uint16_t, IntegralExpression> quest_flag_rewrites_v4;
std::unordered_map<std::string, std::pair<uint8_t, uint32_t>> quest_counter_fields; // For $qfread command
uint64_t persistent_game_idle_timeout_usecs = 0;
int64_t enable_send_function_call_quest_num = -1;
std::unordered_map<uint32_t, int64_t> enable_send_function_call_quest_numbers;
bool enable_v3_v4_protected_subcommands = false;
bool catch_handler_exceptions = true;
bool ep3_infinite_meseta = false;
+13 -3
View File
@@ -235,11 +235,21 @@ 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 SPECIFIC_VERSION_DC_NTE; // 1OJ1
return SPECIFIC_VERSION_DC_NTE; // 1OJ1 (NTE)
case Version::DC_V1_11_2000_PROTOTYPE:
return SPECIFIC_VERSION_DC_11_2000_PROTOTYPE; // 1OJ2
return SPECIFIC_VERSION_DC_11_2000_PROTOTYPE; // 1OJ2 (11/2000)
case Version::DC_V1:
return SPECIFIC_VERSION_DC_V1_INDETERMINATE; // 1___; need to send VersionDetectDC (but can't on V1; rip)
switch (sub_version) {
case 0x20:
return SPECIFIC_VERSION_DC_V1_JP; // 1OJF (1OJ1 and 1OJ2 use 0x20 as well, but are detected without using sub_version)
case 0x21:
return SPECIFIC_VERSION_DC_V1_US; // 1OEF
case 0x22:
case 0x23:
return SPECIFIC_VERSION_DC_V1_EU_INDETERMINATE; // 1OPF, 10J3 (12/2000), or 1OJ4 (01/2001)
default:
return SPECIFIC_VERSION_DC_V1_INDETERMINATE;
}
case Version::DC_V2:
return SPECIFIC_VERSION_DC_V2_INDETERMINATE; // 2___; need to send VersionDetectDC
case Version::PC_V2:
+3
View File
@@ -142,6 +142,9 @@ inline bool uses_utf16(Version version) {
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_JP = 0x314F4A46; // 1OJF
constexpr uint32_t SPECIFIC_VERSION_DC_V1_US = 0x314F4546; // 1OEF
constexpr uint32_t SPECIFIC_VERSION_DC_V1_EU_INDETERMINATE = 0x314F5000; // 1OP_
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
+12 -18
View File
@@ -1059,24 +1059,18 @@
// (100 on v1, 200 on other versions, or 999 on Episode 3).
"NotifyServerForMaxLevelAchieved": false,
// If this number is nonnegative, it specifies a quest which is automatically
// sent to clients using Episodes 1&2 Plus USA when they connect. This is
// intended to be used for enabling server-side patches at connection time.
// This is not enabled by default because it has not yet been tested on real
// hardware, and it also increases the loading time considerably - the player
// has to wait for the initial server connection, then wait for the quest to
// load, then wait for the client to leave the "game", before even getting to
// the welcome message.
// To enable this feature, set this value to 88500. This is the number of the
// quest in system/quests/hidden that implements the patch.
// This quest is not intended to be localized since it should not contain any
// user-visible text, so the server uses the language field to determine
// which quest to send based on the client's version:
// - US Plus v1.2 + customizations: English
// - JP Plus v1.5: Japanese
// - US Ep3: Spanish
// - EU Ep3: German
"EnableSendFunctionCallQuestNumber": -1,
// This setting allows the server to enable server patching for versions that
// don't natively support it. This is not enabled by default because it has
// not yet been tested on real hardware, and it also increases the loading
// time considerably - the player has to wait for the initial server
// connection, then wait for the quest to load, then wait for the client to
// leave the "game", before even getting to the welcome message.
"EnableSendFunctionCallQuestNumbers": {
// "3OE2": 88530, // US Plus (v1.2) + customizations
// "3OJ5": 88531, // JP Plus (v1.5)
// "3SE0": 88532, // US Ep3
// "3SP0": 88533, // EU Ep3
},
// Whether to enable protected subcommands on GC and Xbox. This enables the
// infinite HP cheat to also automatically revive players and clear conditions
@@ -1,5 +1,5 @@
.version GC_V3
.quest_num 88500
.quest_num 88530
.language 1
.episode Episode1
.name "GC v1.2 USA patch enabler"
@@ -55,5 +55,5 @@ copy_done:
ret
code:
.include_native q88500-gc.s
.include_native q8853x-gc.s
code_end:
@@ -1,5 +1,5 @@
.version GC_V3
.quest_num 88500
.quest_num 88531
.language 0
.episode Episode1
.name "GC v1.5 JP patch enabler"
@@ -7,7 +7,7 @@
.long_desc ""
start:
// This script is identical to q88500-gc-e.bin.txt, except the addresses are
// This script is identical to q88530-gc-e.bin.txt, except the addresses are
// changed to be suitable for JP v1.5.
leti r3, 0x80004000
@@ -45,5 +45,5 @@ copy_done:
ret
code:
.include_native q88500-gc.s
.include_native q8853x-gc.s
code_end:
BIN
View File
Binary file not shown.
@@ -1,5 +1,5 @@
.version GC_EP3
.quest_num 88500
.quest_num 88532
.language 1
.episode Episode1
.name "GC Ep3 USA patch enabler"
@@ -7,7 +7,7 @@
.long_desc ""
start:
// This script is identical to q88500-gc-e.bin.txt, except the addresses are
// This script is identical to q88530-gc-e.bin.txt, except the addresses are
// changed to be suitable for US Ep3.
leti r3, 0x80004000
@@ -45,5 +45,5 @@ copy_done:
ret
code:
.include_native q88500-gc.s
.include_native q8853x-gc.s
code_end:
BIN
View File
Binary file not shown.
@@ -1,5 +1,5 @@
.version GC_EP3
.quest_num 88500
.quest_num 88533
.language 1
.episode Episode1
.name "GC Ep3 EU patch enabler"
@@ -7,7 +7,7 @@
.long_desc ""
start:
// This script is identical to q88500-gc-e.bin.txt, except the addresses are
// This script is identical to q88530-gc-e.bin.txt, except the addresses are
// changed to be suitable for EU Ep3.
leti r3, 0x80004000
@@ -45,5 +45,5 @@ copy_done:
ret
code:
.include_native q88500-gc.s
.include_native q8853x-gc.s
code_end:
BIN
View File
Binary file not shown.
+1 -1
View File
@@ -8,7 +8,7 @@ if [ -z "$EXECUTABLE" ]; then
fi
echo "... assemble system/quests/retrieval/q058-gc-e.bin.txt"
$EXECUTABLE assemble-quest-script system/quests/retrieval/q058-gc-e.bin.txt tests/q058-gc-e-test.bin
$EXECUTABLE assemble-quest-script --optimal system/quests/retrieval/q058-gc-e.bin.txt tests/q058-gc-e-test.bin
diff tests/q058-gc-e-test.bin tests/q058-gc-e.bin
echo "... clean up"
+1 -1
View File
@@ -49,7 +49,7 @@
"HTTPListen": [],
"BannedIPV4Ranges": [],
"Episode3BehaviorFlags": 0xFA,
"EnableSendFunctionCallQuestNumber": -1,
"EnableSendFunctionCallQuestNumbers": {},
"EnableV3V4ProtectedSubcommands": false,
"Episode3InfiniteMeseta": false,