add AccurateKillCount patch
This commit is contained in:
@@ -405,18 +405,20 @@ FunctionCodeIndex::FunctionCodeIndex(const string& directory) {
|
||||
}
|
||||
|
||||
shared_ptr<const Menu> FunctionCodeIndex::patch_switches_menu(
|
||||
uint32_t specific_version, const std::unordered_set<std::string>& auto_patches_enabled) const {
|
||||
uint32_t specific_version,
|
||||
const std::unordered_set<std::string>& server_auto_patches_enabled,
|
||||
const std::unordered_set<std::string>& client_auto_patches_enabled) const {
|
||||
auto suffix = std::format("-{:08X}", specific_version);
|
||||
|
||||
auto ret = make_shared<Menu>(MenuID::PATCH_SWITCHES, "Patches");
|
||||
ret->items.emplace_back(PatchesMenuItemID::GO_BACK, "Go back", "Return to the\nmain menu", 0);
|
||||
for (const auto& it : this->name_and_specific_version_to_patch_function) {
|
||||
const auto& fn = it.second;
|
||||
if (fn->hide_from_patches_menu || !it.first.ends_with(suffix)) {
|
||||
if (fn->hide_from_patches_menu || !it.first.ends_with(suffix) || server_auto_patches_enabled.count(fn->short_name)) {
|
||||
continue;
|
||||
}
|
||||
string name;
|
||||
name.push_back(auto_patches_enabled.count(fn->short_name) ? '*' : '-');
|
||||
name.push_back(client_auto_patches_enabled.count(fn->short_name) ? '*' : '-');
|
||||
name += fn->long_name.empty() ? fn->short_name : fn->long_name;
|
||||
ret->items.emplace_back(fn->menu_item_id, name, fn->description, MenuItem::Flag::REQUIRES_SEND_FUNCTION_CALL_RUNS_CODE);
|
||||
}
|
||||
|
||||
@@ -61,7 +61,10 @@ struct FunctionCodeIndex {
|
||||
// Key here is e.g. "PATCHNAME-SPECIFICVERSION", with the latter in hex
|
||||
std::map<std::string, std::shared_ptr<CompiledFunctionCode>> name_and_specific_version_to_patch_function;
|
||||
|
||||
std::shared_ptr<const Menu> patch_switches_menu(uint32_t specific_version, const std::unordered_set<std::string>& auto_patches_enabled) const;
|
||||
std::shared_ptr<const Menu> patch_switches_menu(
|
||||
uint32_t specific_version,
|
||||
const std::unordered_set<std::string>& server_auto_patches_enabled,
|
||||
const std::unordered_set<std::string>& client_auto_patches_enabled) const;
|
||||
bool patch_menu_empty(uint32_t specific_version) const;
|
||||
|
||||
std::shared_ptr<const CompiledFunctionCode> get_patch(const std::string& name, uint32_t specific_version) const;
|
||||
|
||||
+16
-5
@@ -216,17 +216,21 @@ std::string ItemNameIndex::describe_item(const ItemData& item, uint8_t flags) co
|
||||
}
|
||||
|
||||
} else if (!name_only) { // Not S-rank (extended name bits not set)
|
||||
size_t num_bonuses = 3;
|
||||
|
||||
if (item.data1[10] & 0x80) {
|
||||
ret_tokens.emplace_back(std::format("K:{}", item.get_kill_count()));
|
||||
num_bonuses = 2;
|
||||
}
|
||||
|
||||
parray<int8_t, 5> bonuses(0);
|
||||
for (size_t x = 0; x < 3; x++) {
|
||||
for (size_t x = 0; x < num_bonuses; x++) {
|
||||
uint8_t which = item.data1[6 + 2 * x];
|
||||
uint8_t value = item.data1[7 + 2 * x];
|
||||
if (which == 0) {
|
||||
continue;
|
||||
}
|
||||
if (which & 0x80) {
|
||||
uint16_t kill_count = ((which << 8) & 0x7F00) | (value & 0xFF);
|
||||
ret_tokens.emplace_back(std::format("K:{}", kill_count));
|
||||
} else if (which > 5) {
|
||||
if (which > 5) {
|
||||
ret_tokens.emplace_back(std::format("!PC:{:02X}{:02X}", which, value));
|
||||
} else {
|
||||
bonuses[which - 1] = value;
|
||||
@@ -263,6 +267,10 @@ std::string ItemNameIndex::describe_item(const ItemData& item, uint8_t flags) co
|
||||
ret_tokens.emplace_back(std::format("!MD:{:04X}", modifier));
|
||||
}
|
||||
|
||||
if (item.data1[10] & 0x80) {
|
||||
ret_tokens.emplace_back(std::format("K:{}", item.get_kill_count()));
|
||||
}
|
||||
|
||||
} else if (!name_only) { // Armor/shields
|
||||
if (item.data1[5] > 0) {
|
||||
if (item.data1[5] == 1) {
|
||||
@@ -527,6 +535,9 @@ ItemData ItemNameIndex::parse_item_description_phase(const std::string& descript
|
||||
ret.data1w[4] = phosg::bswap16(0x8000 | (char_indexes[4] & 0x1F) | ((char_indexes[3] & 0x1F) << 5) | ((char_indexes[2] & 0x1F) << 10));
|
||||
ret.data1w[5] = phosg::bswap16(0x8000 | (char_indexes[7] & 0x1F) | ((char_indexes[6] & 0x1F) << 5) | ((char_indexes[5] & 0x1F) << 10));
|
||||
|
||||
} else if (token.starts_with("k:")) {
|
||||
ret.set_kill_count(stoul(token.substr(2), nullptr, 0));
|
||||
|
||||
} else {
|
||||
auto p_tokens = phosg::split(token, '/');
|
||||
if (p_tokens.size() > 5) {
|
||||
|
||||
+15
-6
@@ -232,7 +232,8 @@ static asio::awaitable<void> send_auto_patches_if_needed(shared_ptr<Client> c) {
|
||||
auto s = c->require_server_state();
|
||||
|
||||
if (c->login->account->auto_patches_enabled.empty() &&
|
||||
((c->version() != Version::BB_V4) || s->bb_required_patches.empty())) {
|
||||
((c->version() != Version::BB_V4) || s->bb_required_patches.empty()) &&
|
||||
s->auto_patches.empty()) {
|
||||
c->set_flag(Client::Flag::HAS_AUTO_PATCHES);
|
||||
}
|
||||
|
||||
@@ -240,11 +241,11 @@ static asio::awaitable<void> send_auto_patches_if_needed(shared_ptr<Client> c) {
|
||||
c->set_flag(Client::Flag::HAS_AUTO_PATCHES);
|
||||
co_await prepare_client_for_patches(c);
|
||||
|
||||
vector<shared_ptr<const CompiledFunctionCode>> functions_to_send;
|
||||
unordered_set<shared_ptr<const CompiledFunctionCode>> functions_to_send;
|
||||
if (c->version() == Version::BB_V4) {
|
||||
for (const auto& patch_name : s->bb_required_patches) {
|
||||
try {
|
||||
functions_to_send.emplace_back(s->function_code_index->get_patch(patch_name, c->specific_version));
|
||||
functions_to_send.emplace(s->function_code_index->get_patch(patch_name, c->specific_version));
|
||||
} catch (const out_of_range&) {
|
||||
string message = std::format(
|
||||
"Your client is not compatible with a\nrequired patch on this server.\n\nClient version: {:08X}\nPatch name: {}", c->specific_version, patch_name);
|
||||
@@ -254,9 +255,17 @@ static asio::awaitable<void> send_auto_patches_if_needed(shared_ptr<Client> c) {
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const auto& patch_name : s->auto_patches) {
|
||||
try {
|
||||
functions_to_send.emplace(s->function_code_index->get_patch(patch_name, c->specific_version));
|
||||
} catch (const out_of_range&) {
|
||||
c->log.warning_f("Server has auto patch {} enabled, but it is not available for specific_version {:08X}",
|
||||
patch_name, c->specific_version);
|
||||
}
|
||||
}
|
||||
for (const auto& patch_name : c->login->account->auto_patches_enabled) {
|
||||
try {
|
||||
functions_to_send.emplace_back(s->function_code_index->get_patch(patch_name, c->specific_version));
|
||||
functions_to_send.emplace(s->function_code_index->get_patch(patch_name, c->specific_version));
|
||||
} catch (const out_of_range&) {
|
||||
c->log.warning_f("Client has auto patch {} enabled, but it is not available for specific_version {:08X}",
|
||||
patch_name, c->specific_version);
|
||||
@@ -2528,7 +2537,7 @@ static asio::awaitable<void> on_10_main_menu(shared_ptr<Client> c, uint32_t item
|
||||
// don't send them from this mennu, because we need to know the
|
||||
// client's specific_version before sending the menu.
|
||||
co_await prepare_client_for_patches(c);
|
||||
send_menu(c, c->require_server_state()->function_code_index->patch_switches_menu(c->specific_version, c->login->account->auto_patches_enabled));
|
||||
send_menu(c, c->require_server_state()->function_code_index->patch_switches_menu(c->specific_version, s->auto_patches, c->login->account->auto_patches_enabled));
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -2863,7 +2872,7 @@ static asio::awaitable<void> on_10_patch_switches(shared_ptr<Client> c, uint32_t
|
||||
c->login->account->auto_patches_enabled.erase(fn->short_name);
|
||||
}
|
||||
c->login->account->save();
|
||||
send_menu(c, s->function_code_index->patch_switches_menu(c->specific_version, c->login->account->auto_patches_enabled));
|
||||
send_menu(c, s->function_code_index->patch_switches_menu(c->specific_version, s->auto_patches, c->login->account->auto_patches_enabled));
|
||||
}
|
||||
co_return;
|
||||
}
|
||||
|
||||
+2
-1
@@ -453,7 +453,8 @@ asio::awaitable<C_ExecuteCodeResult_B3> send_function_call(
|
||||
co_return co_await promise->get();
|
||||
}
|
||||
|
||||
asio::awaitable<void> send_function_call_multi(shared_ptr<Client> c, vector<shared_ptr<const CompiledFunctionCode>> codes) {
|
||||
asio::awaitable<void> send_function_call_multi(
|
||||
shared_ptr<Client> c, unordered_set<shared_ptr<const CompiledFunctionCode>> codes) {
|
||||
if (codes.empty()) {
|
||||
co_return;
|
||||
}
|
||||
|
||||
+1
-1
@@ -186,7 +186,7 @@ asio::awaitable<C_ExecuteCodeResult_B3> send_function_call(
|
||||
bool ignore_actually_runs_code_flag = false);
|
||||
asio::awaitable<void> send_function_call_multi(
|
||||
std::shared_ptr<Client> c,
|
||||
std::vector<std::shared_ptr<const CompiledFunctionCode>> codes);
|
||||
std::unordered_set<std::shared_ptr<const CompiledFunctionCode>> codes);
|
||||
asio::awaitable<bool> send_protected_command(std::shared_ptr<Client> c, const void* data, size_t size, bool echo_to_lobby);
|
||||
asio::awaitable<void> send_dol_file(std::shared_ptr<Client> c, std::shared_ptr<DOLFileIndex::File> dol);
|
||||
|
||||
|
||||
+8
-1
@@ -1302,7 +1302,14 @@ void ServerState::load_config_early() {
|
||||
this->bb_required_patches.clear();
|
||||
try {
|
||||
for (const auto& it : this->config_json->get_list("BBRequiredPatches")) {
|
||||
this->bb_required_patches.emplace_back(it->as_string());
|
||||
this->bb_required_patches.emplace(it->as_string());
|
||||
}
|
||||
} catch (const out_of_range&) {
|
||||
}
|
||||
this->auto_patches.clear();
|
||||
try {
|
||||
for (const auto& it : this->config_json->get_list("AutoPatches")) {
|
||||
this->auto_patches.emplace(it->as_string());
|
||||
}
|
||||
} catch (const out_of_range&) {
|
||||
}
|
||||
|
||||
+2
-1
@@ -219,7 +219,8 @@ struct ServerState : public std::enable_shared_from_this<ServerState> {
|
||||
std::array<std::shared_ptr<const MapState::RareEnemyRates>, 4> rare_enemy_rates_by_difficulty;
|
||||
std::shared_ptr<const MapState::RareEnemyRates> rare_enemy_rates_challenge;
|
||||
std::array<std::array<size_t, 4>, 3> min_levels_v4; // Indexed as [episode][difficulty]
|
||||
std::vector<std::string> bb_required_patches;
|
||||
std::unordered_set<std::string> bb_required_patches;
|
||||
std::unordered_set<std::string> auto_patches;
|
||||
CheatFlags cheat_flags;
|
||||
|
||||
struct QuestF960Result {
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
.meta name="Kill count fix"
|
||||
.meta description="Fixes client-side\nkill counts when\nmultiple enemies are\nkilled on the same\nframe"
|
||||
.meta hide_from_patches_menu
|
||||
|
||||
.versions 3OJ2 3OJ3 3OJ4 3OJ5 3OE0 3OE1 3OE2 3OP0
|
||||
|
||||
entry_ptr:
|
||||
reloc0:
|
||||
.offsetof start
|
||||
start:
|
||||
.include WriteCodeBlocksGC
|
||||
|
||||
.data <VERS 0x8012D2D4 0x8012D518 0x8012D550 0x8012D4B0 0x8012D578 0x8012D578 0x8012D4C0 0x8012D698>
|
||||
.deltaof TItemWeapon_SealedJSword_count_kill, TItemWeapon_SealedJSword_count_kill_end
|
||||
.address <VERS 0x8012D2D4 0x8012D518 0x8012D550 0x8012D4B0 0x8012D578 0x8012D578 0x8012D4C0 0x8012D698>
|
||||
TItemWeapon_SealedJSword_count_kill: # [std] (TItemWeapon_SealedJSword* this @ r3) -> void
|
||||
lwz r4, [r3 + 0xF0] # r4 = this->owner_player
|
||||
lha r5, [r4 + 0x11A] # r5 = this->owner_player->num_kills_since_map_load
|
||||
lha r6, [r3 + 0x1F8] # r6 = this->last_owner_player_kill_count
|
||||
lhz r7, [r3 + 0xE8] # r7 = this->kill_count
|
||||
cmp r6, r5
|
||||
bge TItemWeapon_SealedJSword_count_kill_skip_update
|
||||
lwz r8, [r3 + 0xDC]
|
||||
andi. r8, r8, 0x100
|
||||
beq TItemWeapon_SealedJSword_count_kill_skip_incr # if (!(flags & 0x100)) don't incr kill count
|
||||
sub r8, r5, r6
|
||||
add r7, r7, r8
|
||||
sth [r3 + 0xE8], r7
|
||||
TItemWeapon_SealedJSword_count_kill_skip_incr:
|
||||
sth [r3 + 0x1F8], r5
|
||||
TItemWeapon_SealedJSword_count_kill_skip_update:
|
||||
cmplwi r7, 23000
|
||||
blt TItemWeapon_SealedJSword_count_kill_skip_set_flag
|
||||
lwz r8, [r3 + 0xDC]
|
||||
ori r8, r8, 0x200
|
||||
stw [r3 + 0xDC], r8
|
||||
TItemWeapon_SealedJSword_count_kill_skip_set_flag:
|
||||
blr
|
||||
TItemWeapon_SealedJSword_count_kill_end:
|
||||
|
||||
.data 0x00000000
|
||||
.data 0x00000000
|
||||
@@ -0,0 +1,36 @@
|
||||
.meta name="Kill count fix"
|
||||
.meta description="Fixes client-side\nkill counts when\nmultiple enemies are\nkilled on the same\nframe"
|
||||
.meta hide_from_patches_menu
|
||||
|
||||
.versions 4OJB 4OJD 4OJU 4OED 4OEU 4OPD 4OPU
|
||||
|
||||
entry_ptr:
|
||||
reloc0:
|
||||
.offsetof start
|
||||
start:
|
||||
.include WriteCodeBlocksXB
|
||||
|
||||
.data <VERS 0x00197610 0x001977A0 0x00197920 0x00197880 0x00197810 0x001978A0 0x00197840>
|
||||
.deltaof TItemWeapon_SealedJSword_count_kill, TItemWeapon_SealedJSword_count_kill_end
|
||||
.address <VERS 0x00197610 0x001977A0 0x00197920 0x00197880 0x00197810 0x001978A0 0x00197840>
|
||||
TItemWeapon_SealedJSword_count_kill:
|
||||
mov eax, [ecx + 0xF0]
|
||||
movsx eax, word [eax + 0x11A]
|
||||
movsx edx, word [ecx + 0x1F8]
|
||||
sub edx, eax
|
||||
jge TItemWeapon_SealedJSword_count_kill_skip_update
|
||||
test dword [ecx + 0xDC], 0x100
|
||||
jz TItemWeapon_SealedJSword_count_kill_skip_incr
|
||||
sub [ecx + 0xE8], dx
|
||||
TItemWeapon_SealedJSword_count_kill_skip_incr:
|
||||
mov [ecx + 0x1F8], ax
|
||||
TItemWeapon_SealedJSword_count_kill_skip_update:
|
||||
cmp word [ecx + 0xE8], 23000
|
||||
jb TItemWeapon_SealedJSword_count_kill_skip_set_flag
|
||||
or dword [ecx + 0xDC], 0x200
|
||||
TItemWeapon_SealedJSword_count_kill_skip_set_flag:
|
||||
ret
|
||||
TItemWeapon_SealedJSword_count_kill_end:
|
||||
|
||||
.data 0x00000000
|
||||
.data 0x00000000
|
||||
@@ -0,0 +1,86 @@
|
||||
.meta name="Kill count fix"
|
||||
.meta description="Fixes client-side\nkill counts when\nmultiple enemies are\nkilled on the same\nframe"
|
||||
.meta hide_from_patches_menu
|
||||
|
||||
entry_ptr:
|
||||
reloc0:
|
||||
.offsetof start
|
||||
start:
|
||||
.include WriteCodeBlocksBB
|
||||
|
||||
|
||||
|
||||
.data 0x005E32C8
|
||||
.deltaof TItemUnitUnsealable_count_kill, TItemUnitUnsealable_count_kill_end
|
||||
.address 0x005E32C8
|
||||
TItemUnitUnsealable_count_kill: # [std] (TItemUnitUnsealable* this @ ecx) -> void
|
||||
mov eax, [ecx + 0xF8]
|
||||
movsx eax, word [eax + 0x11A] # eax = this->owner_player->num_kills_since_map_load
|
||||
movsx edx, word [ecx + 0x1E4] # edx = this->last_owner_player_kill_count
|
||||
sub edx, eax # edx = this->last_owner_player_kill_count - this->owner_player->num_kills_since_map_load (edx should be 0 or negative)
|
||||
jge TItemUnitUnsealable_count_kill_skip_update
|
||||
test dword [ecx + 0xDC], 0x100
|
||||
jz TItemUnitUnsealable_count_kill_skip_incr # if (!(this->flags & 0x100)) don't incr kill count
|
||||
sub [ecx + 0xE8], dx # this->kill_count -= edx
|
||||
TItemUnitUnsealable_count_kill_skip_incr:
|
||||
mov [ecx + 0x1E4], ax # this->last_owner_player_kill_count = this->owner_player->num_kills_since_map_load
|
||||
TItemUnitUnsealable_count_kill_skip_update:
|
||||
cmp word [ecx + 0xE8], 20000
|
||||
jb TItemUnitUnsealable_count_kill_skip_set_flag
|
||||
or dword [ecx + 0xDC], 0x200
|
||||
TItemUnitUnsealable_count_kill_skip_set_flag:
|
||||
jmp 0x005E2C34
|
||||
TItemUnitUnsealable_count_kill_end:
|
||||
|
||||
|
||||
|
||||
.data 0x005F3EFC
|
||||
.deltaof TItemWeapon_LameDArgent_count_kill, TItemWeapon_LameDArgent_count_kill_end
|
||||
.address 0x005F3EFC
|
||||
TItemWeapon_LameDArgent_count_kill:
|
||||
mov eax, [ecx + 0xF8]
|
||||
movsx eax, word [eax + 0x11A]
|
||||
movsx edx, word [ecx + 0x240]
|
||||
sub edx, eax
|
||||
jge TItemWeapon_LameDArgent_count_kill_skip_update
|
||||
test dword [ecx + 0xDC], 0x100
|
||||
jz TItemWeapon_LameDArgent_count_kill_skip_incr
|
||||
sub [ecx + 0xE8], dx
|
||||
TItemWeapon_LameDArgent_count_kill_skip_incr:
|
||||
mov [ecx + 0x240], ax
|
||||
TItemWeapon_LameDArgent_count_kill_skip_update:
|
||||
cmp word [ecx + 0xE8], 10000
|
||||
jb TItemWeapon_LameDArgent_count_kill_skip_set_flag
|
||||
or dword [ecx + 0xDC], 0x200
|
||||
TItemWeapon_LameDArgent_count_kill_skip_set_flag:
|
||||
ret
|
||||
TItemWeapon_LameDArgent_count_kill_end:
|
||||
|
||||
|
||||
|
||||
.data 0x005FCA74
|
||||
.deltaof TItemWeapon_SealedJSword_count_kill, TItemWeapon_SealedJSword_count_kill_end
|
||||
.address 0x005FCA74
|
||||
TItemWeapon_SealedJSword_count_kill:
|
||||
mov eax, [ecx + 0xF8]
|
||||
movsx eax, word [eax + 0x11A]
|
||||
movsx edx, word [ecx + 0x240]
|
||||
sub edx, eax
|
||||
jge TItemWeapon_SealedJSword_count_kill_skip_update
|
||||
test dword [ecx + 0xDC], 0x100
|
||||
jz TItemWeapon_SealedJSword_count_kill_skip_incr
|
||||
sub [ecx + 0xE8], dx
|
||||
TItemWeapon_SealedJSword_count_kill_skip_incr:
|
||||
mov [ecx + 0x240], ax
|
||||
TItemWeapon_SealedJSword_count_kill_skip_update:
|
||||
cmp word [ecx + 0xE8], 23000
|
||||
jb TItemWeapon_SealedJSword_count_kill_skip_set_flag
|
||||
or dword [ecx + 0xDC], 0x200
|
||||
TItemWeapon_SealedJSword_count_kill_skip_set_flag:
|
||||
ret
|
||||
TItemWeapon_SealedJSword_count_kill_end:
|
||||
|
||||
|
||||
|
||||
.data 0x00000000
|
||||
.data 0x00000000
|
||||
@@ -826,12 +826,21 @@
|
||||
// Client functions listed here are always enabled as auto patches for BB
|
||||
// clients. For example, you can add "StackLimits" here if you've edited the
|
||||
// StackLimits patch and the ItemStackLimits field in this file, and want the
|
||||
// limits to take effect on BB clients.
|
||||
// limits to take effect on BB clients. If a client connects using a client
|
||||
// version that isn't compatible with one of these patches, the client will
|
||||
// be disconnected.
|
||||
"BBRequiredPatches": [
|
||||
// You will probably want to uncomment the following line if you want to
|
||||
// use items that were unreleased on the original Sega servers.
|
||||
// "ClearUnreleasedItemList",
|
||||
],
|
||||
// Client functions listed here are automatically sent to all clients. If a
|
||||
// client connects using a client version that isn't compatible with some of
|
||||
// these patches, the incompatible patches are skipped and the client is
|
||||
// allowed to connect. This option applies to all PSO versions, not only BB.
|
||||
"AutoPatches": [
|
||||
// "AccurateKillCount",
|
||||
],
|
||||
|
||||
// Whether to retain server drop tables when game leaders change. The client
|
||||
// reloads its drop tables when the leader joins a game or returns to Pioneer
|
||||
|
||||
Reference in New Issue
Block a user