Brutal Peeps PC client memory patch
Add Brutal Peeps PC client memory patch
This commit is contained in:
@@ -153,6 +153,7 @@ public:
|
||||
uint8_t override_lobby_number = 0x80; // 80 = no override
|
||||
int64_t override_random_seed = -1;
|
||||
int8_t selected_brutal_peeps_tier = -1; // -1 = normal lobby/game; 1..11 = requested Brutal Peeps tier
|
||||
int8_t brutal_peeps_pc_battleparam_patch_tier = -1; // -1 = vanilla; 1..11 = currently applied PC BattleParam BP tier
|
||||
std::unique_ptr<Variations> override_variations;
|
||||
VectorXYZF pos;
|
||||
uint32_t floor = 0x0F;
|
||||
|
||||
+52
-7
@@ -314,8 +314,16 @@ static void send_main_menu(std::shared_ptr<Client> c) {
|
||||
? max_brutal_peeps_tier_for_level(character_level)
|
||||
: -1;
|
||||
|
||||
bool supports_brutal_peeps_menu =
|
||||
bb_destination_transport_menu ||
|
||||
(c->version() == Version::PC_V2);
|
||||
|
||||
auto brutal_peeps_menu_item_flags = (c->version() == Version::BB_V4)
|
||||
? MenuItem::Flag::BB_ONLY
|
||||
: static_cast<MenuItem::Flag>(0);
|
||||
|
||||
bool show_brutal_peeps_menu_items =
|
||||
bb_destination_transport_menu &&
|
||||
supports_brutal_peeps_menu &&
|
||||
s->enable_brutal_peeps_mode &&
|
||||
(max_brutal_peeps_menu_tier >= 1);
|
||||
|
||||
@@ -325,7 +333,7 @@ static void send_main_menu(std::shared_ptr<Client> c) {
|
||||
MainMenuItemID::BRUTAL_PEEPS_PLUS1 + static_cast<uint32_t>(tier - 1),
|
||||
std::format("Brutal Peeps +{}", tier),
|
||||
std::format("Enter Brutal Peeps\n+{}", tier),
|
||||
MenuItem::Flag::BB_ONLY);
|
||||
brutal_peeps_menu_item_flags);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2748,7 +2756,7 @@ static asio::awaitable<void> on_10_main_menu(std::shared_ptr<Client> c, uint32_t
|
||||
: 0;
|
||||
|
||||
if (!s->enable_brutal_peeps_mode ||
|
||||
!is_v4(c->version()) ||
|
||||
!((c->version() == Version::BB_V4) || (c->version() == Version::PC_V2)) ||
|
||||
!brutal_peeps_def ||
|
||||
(character_level < brutal_peeps_def->required_level)) {
|
||||
send_message_box(c, std::format(
|
||||
@@ -2759,10 +2767,12 @@ static asio::awaitable<void> on_10_main_menu(std::shared_ptr<Client> c, uint32_t
|
||||
}
|
||||
|
||||
c->selected_brutal_peeps_tier = tier;
|
||||
c->log.info_f("Brutal Peeps +{} selected from BB menu at level {}", tier, character_level);
|
||||
c->log.info_f("Brutal Peeps +{} selected from ship menu at level {}", tier, character_level);
|
||||
|
||||
co_await send_auto_patches_if_needed(c);
|
||||
co_await send_brutal_peeps_hp_patch_bb(c, tier);
|
||||
if (c->version() == Version::BB_V4) {
|
||||
co_await send_brutal_peeps_hp_patch_bb(c, tier);
|
||||
}
|
||||
co_await enable_save_if_needed(c);
|
||||
send_lobby_list(c);
|
||||
if (!c->lobby.lock()) {
|
||||
@@ -5125,14 +5135,14 @@ std::shared_ptr<Lobby> create_game_generic(
|
||||
if (requested_brutal_peeps_tier >= 0) {
|
||||
const auto* brutal_peeps_def = brutal_peeps_tier_definition(requested_brutal_peeps_tier);
|
||||
if (s->enable_brutal_peeps_mode &&
|
||||
is_v4(creator_c->version()) &&
|
||||
((creator_c->version() == Version::BB_V4) || (creator_c->version() == Version::PC_V2)) &&
|
||||
(difficulty == Difficulty::ULTIMATE) &&
|
||||
brutal_peeps_def &&
|
||||
(creator_character_level >= brutal_peeps_def->required_level)) {
|
||||
game->brutal_peeps_tier = requested_brutal_peeps_tier;
|
||||
game->set_flag(Lobby::Flag::BRUTAL_PEEPS_MODE);
|
||||
creator_c->selected_brutal_peeps_tier = requested_brutal_peeps_tier;
|
||||
game->log.info_f("Brutal Peeps +{} enabled for BB Ultimate game via {} at creator level {}",
|
||||
game->log.info_f("Brutal Peeps +{} enabled for BB/PC Ultimate game via {} at creator level {}",
|
||||
static_cast<int>(game->brutal_peeps_tier),
|
||||
requested_brutal_peeps_source,
|
||||
creator_character_level);
|
||||
@@ -5536,6 +5546,35 @@ static asio::awaitable<void> on_8A(std::shared_ptr<Client> c, Channel::Message&
|
||||
co_return;
|
||||
}
|
||||
|
||||
|
||||
static asio::awaitable<void> send_brutal_peeps_pc_patch_until_area_load(std::shared_ptr<Client> c) {
|
||||
for (size_t attempt = 1; attempt <= 120; attempt++) {
|
||||
asio::steady_timer timer(co_await asio::this_coro::executor);
|
||||
timer.expires_after(std::chrono::milliseconds(500));
|
||||
co_await timer.async_wait(asio::use_awaitable);
|
||||
|
||||
if (!c->channel->connected() || (c->version() != Version::PC_V2)) {
|
||||
co_return;
|
||||
}
|
||||
|
||||
auto l = c->lobby.lock();
|
||||
if (!l || !l->is_game()) {
|
||||
co_return;
|
||||
}
|
||||
|
||||
int64_t brutal_peeps_hp_patch_tier = (l->brutal_peeps_tier >= 1) ? l->brutal_peeps_tier : -1;
|
||||
if (c->brutal_peeps_pc_battleparam_patch_tier == brutal_peeps_hp_patch_tier) {
|
||||
co_return;
|
||||
}
|
||||
|
||||
co_await send_brutal_peeps_hp_patch_bb(c, brutal_peeps_hp_patch_tier);
|
||||
|
||||
if (c->brutal_peeps_pc_battleparam_patch_tier == brutal_peeps_hp_patch_tier) {
|
||||
co_return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static asio::awaitable<void> on_6F(std::shared_ptr<Client> c, Channel::Message& msg) {
|
||||
check_size_v(msg.data.size(), 0);
|
||||
|
||||
@@ -5589,6 +5628,12 @@ static asio::awaitable<void> on_6F(std::shared_ptr<Client> c, Channel::Message&
|
||||
if (loading_flag_cleared && (c->version() == Version::BB_V4)) {
|
||||
int64_t brutal_peeps_hp_patch_tier = (l->brutal_peeps_tier >= 1) ? l->brutal_peeps_tier : -1;
|
||||
co_await send_brutal_peeps_hp_patch_bb(c, brutal_peeps_hp_patch_tier);
|
||||
} else if (loading_flag_cleared && (c->version() == Version::PC_V2)) {
|
||||
// PC unloads/reloads the area BattleParam table between room loads; when it
|
||||
// appears again, assume it starts from the file's vanilla values.
|
||||
c->brutal_peeps_pc_battleparam_patch_tier = -1;
|
||||
auto s = c->require_server_state();
|
||||
asio::co_spawn(*s->io_context, send_brutal_peeps_pc_patch_until_area_load(c), asio::detached);
|
||||
}
|
||||
|
||||
// DC NTE creates players in the invisible state by default; if the joiner is not DC NTE, it won't send 6x23 to make
|
||||
|
||||
@@ -3742,6 +3742,15 @@ static asio::awaitable<void> on_trigger_set_event(std::shared_ptr<Client> c, Sub
|
||||
auto event_sts = l->map_state->event_states_for_id(c->version(), cmd.floor, cmd.event_id);
|
||||
l->log.info_f("Client triggered set events with floor {:02X} and ID {:X} ({} events)",
|
||||
cmd.floor, cmd.event_id, event_sts.size());
|
||||
|
||||
// PSO Peeps: PSO PC does not load the BattleParam table while still in
|
||||
// Pioneer 2, so the Brutal Peeps ATP/HP/EXP memory patch must be delayed
|
||||
// until the client reaches a combat floor.
|
||||
if ((c->version() == Version::PC_V2) &&
|
||||
(cmd.floor >= 1) &&
|
||||
(l->brutal_peeps_tier >= 1)) {
|
||||
co_await send_brutal_peeps_hp_patch_bb(c, l->brutal_peeps_tier);
|
||||
}
|
||||
for (auto ev_st : event_sts) {
|
||||
ev_st->flags |= 0x04;
|
||||
if (c->check_flag(Client::Flag::DEBUG_ENABLED)) {
|
||||
|
||||
+306
-11
@@ -989,22 +989,317 @@ static std::vector<std::pair<std::string, std::shared_ptr<AsyncPromise<C_Execute
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static std::vector<std::pair<std::string, std::shared_ptr<AsyncPromise<C_ExecuteCodeResult_B3>>>> send_brutal_peeps_hp_patch_pc_now(
|
||||
std::shared_ptr<Client> c,
|
||||
int64_t tier) {
|
||||
std::vector<std::pair<std::string, std::shared_ptr<AsyncPromise<C_ExecuteCodeResult_B3>>>> promises;
|
||||
|
||||
if (c->version() != Version::PC_V2) {
|
||||
return promises;
|
||||
}
|
||||
if (!c->check_flag(Client::Flag::HAS_SEND_FUNCTION_CALL) ||
|
||||
!c->check_flag(Client::Flag::SEND_FUNCTION_CALL_ACTUALLY_RUNS_CODE)) {
|
||||
c->log.warning_f("Skipping Brutal Peeps PC client patch because client does not support executable send_function_call");
|
||||
return promises;
|
||||
}
|
||||
if (!c->channel->connected()) {
|
||||
c->log.warning_f("Skipping Brutal Peeps PC client patch because client is disconnected");
|
||||
return promises;
|
||||
}
|
||||
|
||||
try {
|
||||
auto s = c->require_server_state();
|
||||
|
||||
if (tier < -1) {
|
||||
c->log.warning_f("Skipping Brutal Peeps PC client patch for invalid tier {}", tier);
|
||||
return promises;
|
||||
}
|
||||
|
||||
const auto* target_brutal_peeps_def = brutal_peeps_tier_definition(tier);
|
||||
if ((tier >= 0) && !target_brutal_peeps_def) {
|
||||
c->log.warning_f("Skipping Brutal Peeps PC client patch for invalid tier {}", tier);
|
||||
return promises;
|
||||
}
|
||||
|
||||
int64_t source_tier = c->brutal_peeps_pc_battleparam_patch_tier;
|
||||
const auto* source_brutal_peeps_def = brutal_peeps_tier_definition(source_tier);
|
||||
if ((source_tier >= 0) && !source_brutal_peeps_def) {
|
||||
c->log.warning_f("PC BattleParam patch source tier {} is invalid; resetting assumed source tier to vanilla", source_tier);
|
||||
source_tier = -1;
|
||||
source_brutal_peeps_def = nullptr;
|
||||
c->brutal_peeps_pc_battleparam_patch_tier = -1;
|
||||
}
|
||||
|
||||
if (source_tier == tier) {
|
||||
return promises;
|
||||
}
|
||||
|
||||
auto atp_mult_for_tier = +[](int64_t t) -> double {
|
||||
switch (t) {
|
||||
case 1:
|
||||
return 1.01;
|
||||
case 2:
|
||||
return 1.02;
|
||||
case 3:
|
||||
return 1.03;
|
||||
case 4:
|
||||
return 1.04;
|
||||
case 5:
|
||||
return 1.05;
|
||||
case 6:
|
||||
return 1.06;
|
||||
case 7:
|
||||
return 1.07;
|
||||
case 8:
|
||||
return 1.08;
|
||||
case 9:
|
||||
return 1.09;
|
||||
case 10:
|
||||
case 11:
|
||||
return 1.10;
|
||||
default:
|
||||
return 1.00;
|
||||
}
|
||||
};
|
||||
|
||||
constexpr const char* bp_filename = "BattleParamEntry_on.dat";
|
||||
std::string vanilla_data = phosg::load_file("system/patch-pc/Media/PSO/BattleParamEntry_on.dat");
|
||||
|
||||
constexpr uint32_t scan_start = 0x01E00000;
|
||||
constexpr uint32_t scan_end = 0x02E21000;
|
||||
constexpr uint32_t signature_size = 64;
|
||||
|
||||
// PC V2 BattleParamEntry_on.dat layout:
|
||||
// Ultimate stats rows start at 0x2880 and each stats row is 0x24 bytes.
|
||||
// Within each row: ATP is +0x00, HP is +0x06, EXP is +0x1C.
|
||||
//
|
||||
// The PC memory image does not contain the full file, but the Ultimate
|
||||
// stats block appears when an area is loaded. The scan signature must be
|
||||
// based on the client's current patch tier, not always vanilla, so we can
|
||||
// restore or move between BP tiers.
|
||||
constexpr uint32_t ultimate_block_offset = 0x00002880;
|
||||
constexpr uint32_t ultimate_atp_row_offset = 0x00;
|
||||
constexpr uint32_t ultimate_hp_row_offset = 0x06;
|
||||
constexpr uint32_t ultimate_exp_row_offset = 0x1C;
|
||||
constexpr uint32_t stats_row_size = 0x24;
|
||||
constexpr uint32_t num_bp_rows = 0x60;
|
||||
constexpr uint32_t ultimate_block_size = num_bp_rows * stats_row_size;
|
||||
|
||||
auto append_u32l = +[](std::string& out, uint32_t v) {
|
||||
out.push_back(static_cast<char>(v & 0xFF));
|
||||
out.push_back(static_cast<char>((v >> 8) & 0xFF));
|
||||
out.push_back(static_cast<char>((v >> 16) & 0xFF));
|
||||
out.push_back(static_cast<char>((v >> 24) & 0xFF));
|
||||
};
|
||||
|
||||
auto read_u16l = +[](const std::string& data, uint32_t offset) -> uint16_t {
|
||||
return static_cast<uint8_t>(data[offset]) |
|
||||
(static_cast<uint16_t>(static_cast<uint8_t>(data[offset + 1])) << 8);
|
||||
};
|
||||
|
||||
auto write_u16l = +[](std::string& data, uint32_t offset, uint16_t v) {
|
||||
data[offset] = static_cast<char>(v & 0xFF);
|
||||
data[offset + 1] = static_cast<char>((v >> 8) & 0xFF);
|
||||
};
|
||||
|
||||
auto read_u32l = +[](const std::string& data, uint32_t offset) -> uint32_t {
|
||||
return static_cast<uint8_t>(data[offset]) |
|
||||
(static_cast<uint32_t>(static_cast<uint8_t>(data[offset + 1])) << 8) |
|
||||
(static_cast<uint32_t>(static_cast<uint8_t>(data[offset + 2])) << 16) |
|
||||
(static_cast<uint32_t>(static_cast<uint8_t>(data[offset + 3])) << 24);
|
||||
};
|
||||
|
||||
auto write_u32l = +[](std::string& data, uint32_t offset, uint32_t v) {
|
||||
data[offset] = static_cast<char>(v & 0xFF);
|
||||
data[offset + 1] = static_cast<char>((v >> 8) & 0xFF);
|
||||
data[offset + 2] = static_cast<char>((v >> 16) & 0xFF);
|
||||
data[offset + 3] = static_cast<char>((v >> 24) & 0xFF);
|
||||
};
|
||||
|
||||
auto scale_u16 = +[](uint32_t v, double scale) -> uint16_t {
|
||||
if (v == 0) {
|
||||
return 0;
|
||||
}
|
||||
uint32_t scaled = static_cast<uint32_t>((static_cast<double>(v) * scale) + 0.5);
|
||||
if (scaled < 1) {
|
||||
scaled = 1;
|
||||
}
|
||||
if (scaled > 0xFFFF) {
|
||||
scaled = 0xFFFF;
|
||||
}
|
||||
return static_cast<uint16_t>(scaled);
|
||||
};
|
||||
|
||||
auto scale_u32 = +[](uint32_t v, double scale) -> uint32_t {
|
||||
if (v == 0) {
|
||||
return 0;
|
||||
}
|
||||
double scaled_f = (static_cast<double>(v) * scale) + 0.5;
|
||||
if (scaled_f < 1.0) {
|
||||
return 1;
|
||||
}
|
||||
if (scaled_f > 4294967295.0) {
|
||||
return 0xFFFFFFFF;
|
||||
}
|
||||
return static_cast<uint32_t>(scaled_f);
|
||||
};
|
||||
|
||||
auto append_patch_entry = [&](std::string& out, uint32_t offset, uint32_t value, uint8_t size) {
|
||||
append_u32l(out, offset);
|
||||
out.push_back(static_cast<char>(size));
|
||||
for (uint8_t x = 0; x < size; x++) {
|
||||
out.push_back(static_cast<char>((value >> (x * 8)) & 0xFF));
|
||||
}
|
||||
};
|
||||
|
||||
constexpr uint32_t last_needed_offset = ultimate_block_offset + ultimate_block_size;
|
||||
if (vanilla_data.size() < last_needed_offset) {
|
||||
c->log.warning_f("Skipping Brutal Peeps PC client patch: {} too small for Ultimate stats table", bp_filename);
|
||||
return promises;
|
||||
}
|
||||
|
||||
auto build_block_for_tier = [&](int64_t block_tier) -> std::string {
|
||||
std::string block = vanilla_data.substr(ultimate_block_offset, ultimate_block_size);
|
||||
|
||||
const auto* def = brutal_peeps_tier_definition(block_tier);
|
||||
const double hp_mult = def ? def->enemy_hp_multiplier : 1.0;
|
||||
const double exp_mult = def ? def->exp_multiplier : 1.0;
|
||||
const double atp_mult = atp_mult_for_tier(block_tier);
|
||||
|
||||
for (uint32_t z = 0; z < num_bp_rows; z++) {
|
||||
const uint32_t row_offset = z * stats_row_size;
|
||||
|
||||
const uint32_t atp_offset = row_offset + ultimate_atp_row_offset;
|
||||
const uint32_t hp_offset = row_offset + ultimate_hp_row_offset;
|
||||
const uint32_t exp_offset = row_offset + ultimate_exp_row_offset;
|
||||
|
||||
write_u16l(block, atp_offset, scale_u16(read_u16l(block, atp_offset), atp_mult));
|
||||
write_u16l(block, hp_offset, scale_u16(read_u16l(block, hp_offset), hp_mult));
|
||||
write_u32l(block, exp_offset, scale_u32(read_u32l(block, exp_offset), exp_mult));
|
||||
}
|
||||
|
||||
return block;
|
||||
};
|
||||
|
||||
const std::string source_block = build_block_for_tier(source_tier);
|
||||
const std::string target_block = build_block_for_tier(tier);
|
||||
|
||||
std::string suffix;
|
||||
append_u32l(suffix, scan_start);
|
||||
append_u32l(suffix, scan_end);
|
||||
append_u32l(suffix, signature_size);
|
||||
append_u32l(suffix, 0); // patched below after patch generation
|
||||
suffix.append(source_block.data(), signature_size);
|
||||
|
||||
uint32_t patch_entry_count = 0;
|
||||
|
||||
for (uint32_t z = 0; z < num_bp_rows; z++) {
|
||||
const uint32_t row_patch_offset = z * stats_row_size;
|
||||
|
||||
const uint32_t atp_patch_offset = row_patch_offset + ultimate_atp_row_offset;
|
||||
const uint32_t hp_patch_offset = row_patch_offset + ultimate_hp_row_offset;
|
||||
const uint32_t exp_patch_offset = row_patch_offset + ultimate_exp_row_offset;
|
||||
|
||||
append_patch_entry(suffix, atp_patch_offset, read_u16l(target_block, atp_patch_offset), 2);
|
||||
patch_entry_count++;
|
||||
|
||||
append_patch_entry(suffix, hp_patch_offset, read_u16l(target_block, hp_patch_offset), 2);
|
||||
patch_entry_count++;
|
||||
|
||||
append_patch_entry(suffix, exp_patch_offset, read_u32l(target_block, exp_patch_offset), 4);
|
||||
patch_entry_count++;
|
||||
}
|
||||
|
||||
suffix[12] = static_cast<char>(patch_entry_count & 0xFF);
|
||||
suffix[13] = static_cast<char>((patch_entry_count >> 8) & 0xFF);
|
||||
suffix[14] = static_cast<char>((patch_entry_count >> 16) & 0xFF);
|
||||
suffix[15] = static_cast<char>((patch_entry_count >> 24) & 0xFF);
|
||||
|
||||
auto fn = s->client_functions->get("PsoPeepsBrutalPeepsPC", c->specific_version);
|
||||
|
||||
auto promise = std::make_shared<AsyncPromise<C_ExecuteCodeResult_B3>>();
|
||||
c->function_call_response_queue.emplace_back(promise);
|
||||
|
||||
// This is a dynamic patch: the suffix changes by tier and by restore state.
|
||||
// Force the code+suffix to be sent every time instead of treating it as a
|
||||
// cached/enabled client function.
|
||||
send_function_call(
|
||||
c->channel,
|
||||
c->enabled_flags & (~fn->client_flag),
|
||||
fn,
|
||||
{},
|
||||
suffix.data(),
|
||||
suffix.size());
|
||||
|
||||
promises.emplace_back(bp_filename, promise);
|
||||
|
||||
const auto* target_def_for_log = brutal_peeps_tier_definition(tier);
|
||||
const double hp_mult_for_log = target_def_for_log ? target_def_for_log->enemy_hp_multiplier : 1.0;
|
||||
const double exp_mult_for_log = target_def_for_log ? target_def_for_log->exp_multiplier : 1.0;
|
||||
const double atp_mult_for_log = atp_mult_for_tier(tier);
|
||||
|
||||
c->log.info_f("Brutal Peeps PC ATP/HP/EXP client patch sent for {}: source_tier={} target_tier={} hp_mult={:g} atp_mult={:g} exp_mult={:g} patch_entries={} suffix_size={} scan={:08X}-{:08X}",
|
||||
bp_filename, source_tier, tier, hp_mult_for_log, atp_mult_for_log, exp_mult_for_log, patch_entry_count, suffix.size(), scan_start, scan_end);
|
||||
|
||||
return promises;
|
||||
|
||||
} catch (const std::exception& e) {
|
||||
c->log.warning_f("Failed to send Brutal Peeps PC client patch: {}", e.what());
|
||||
return promises;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
asio::awaitable<void> send_brutal_peeps_hp_patch_bb(std::shared_ptr<Client> c, int64_t tier) {
|
||||
try {
|
||||
co_await prepare_client_for_patches(c);
|
||||
|
||||
auto promises = send_brutal_peeps_hp_patch_bb_now(c, tier);
|
||||
for (auto& it : promises) {
|
||||
const auto& filename = it.first;
|
||||
auto& promise = it.second;
|
||||
if (promise && c->channel->connected()) {
|
||||
auto result = co_await promise->get();
|
||||
c->log.info_f("Brutal Peeps HP/ATP client patch result for {}: tier={} return_value={:08X} checksum={:08X}",
|
||||
filename,
|
||||
tier,
|
||||
static_cast<uint32_t>(result.return_value),
|
||||
static_cast<uint32_t>(result.checksum));
|
||||
const bool is_pc_bp_patch = (c->version() == Version::PC_V2);
|
||||
const size_t max_attempts = 1;
|
||||
|
||||
for (size_t attempt = 1; attempt <= max_attempts; attempt++) {
|
||||
auto promises = is_pc_bp_patch
|
||||
? send_brutal_peeps_hp_patch_pc_now(c, tier)
|
||||
: send_brutal_peeps_hp_patch_bb_now(c, tier);
|
||||
|
||||
bool any_zero_return = false;
|
||||
bool any_success = false;
|
||||
|
||||
for (auto& it : promises) {
|
||||
const auto& filename = it.first;
|
||||
auto& promise = it.second;
|
||||
if (promise && c->channel->connected()) {
|
||||
auto result = co_await promise->get();
|
||||
uint32_t return_value = static_cast<uint32_t>(result.return_value);
|
||||
c->log.info_f("Brutal Peeps HP/ATP client patch result for {}: tier={} attempt={}/{} return_value={:08X} checksum={:08X}",
|
||||
filename,
|
||||
tier,
|
||||
attempt,
|
||||
max_attempts,
|
||||
return_value,
|
||||
static_cast<uint32_t>(result.checksum));
|
||||
|
||||
if (is_pc_bp_patch && return_value) {
|
||||
c->brutal_peeps_pc_battleparam_patch_tier = static_cast<int8_t>(tier);
|
||||
c->log.info_f("Brutal Peeps PC BattleParam patch state is now tier {}", tier);
|
||||
}
|
||||
|
||||
if (return_value) {
|
||||
any_success = true;
|
||||
} else {
|
||||
any_zero_return = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!is_pc_bp_patch || any_success || !any_zero_return || !c->channel->connected() || (attempt >= max_attempts)) {
|
||||
break;
|
||||
}
|
||||
|
||||
c->log.warning_f("Brutal Peeps PC client patch did not find BattleParam table on attempt {}/{}; retrying",
|
||||
attempt,
|
||||
max_attempts);
|
||||
}
|
||||
|
||||
} catch (const std::exception& e) {
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
.meta key="PsoPeepsBrutalPeepsPC"
|
||||
.meta name="Brutal Peeps PC"
|
||||
.meta description="Applies Brutal Peeps\nPC ATP/HP/EXP scaling"
|
||||
.meta show_return_value
|
||||
|
||||
.versions 2OJW 2OJZ
|
||||
|
||||
entry_ptr:
|
||||
reloc0:
|
||||
.offsetof start
|
||||
|
||||
start:
|
||||
push ebx
|
||||
push esi
|
||||
push edi
|
||||
push ebp
|
||||
|
||||
jmp get_data_ptr
|
||||
|
||||
get_data_ptr_ret:
|
||||
pop ebx # ebx = suffix payload
|
||||
|
||||
mov esi, [ebx] # scan_start
|
||||
mov edx, [ebx + 4] # scan_end
|
||||
mov ecx, [ebx + 8] # signature_size
|
||||
sub edx, ecx # scan limit = end - sig_size
|
||||
lea edi, [ebx + 16] # signature ptr
|
||||
|
||||
scan_again:
|
||||
cmp esi, edx
|
||||
ja not_found
|
||||
|
||||
xor ebp, ebp
|
||||
|
||||
compare_again:
|
||||
cmp ebp, ecx
|
||||
jae found_table
|
||||
|
||||
mov al, [esi + ebp]
|
||||
cmp al, [edi + ebp]
|
||||
jne next_candidate
|
||||
|
||||
inc ebp
|
||||
jmp compare_again
|
||||
|
||||
next_candidate:
|
||||
inc esi
|
||||
jmp scan_again
|
||||
|
||||
found_table:
|
||||
# esi = matched Ultimate BattleParam block base
|
||||
mov ecx, [ebx + 12] # patch entry count
|
||||
mov edi, [ebx + 8] # signature_size
|
||||
lea edi, [ebx + edi + 16] # patch entries after header+signature
|
||||
|
||||
patch_again:
|
||||
test ecx, ecx
|
||||
jz done
|
||||
|
||||
mov edx, [edi] # offset from matched block base
|
||||
movzx ebp, byte [edi + 4] # byte count
|
||||
add edi, 5 # edi = source bytes
|
||||
|
||||
copy_patch_bytes_again:
|
||||
test ebp, ebp
|
||||
jz patch_entry_done
|
||||
|
||||
mov al, [edi]
|
||||
mov [esi + edx], al
|
||||
inc edi
|
||||
inc edx
|
||||
dec ebp
|
||||
jmp copy_patch_bytes_again
|
||||
|
||||
patch_entry_done:
|
||||
dec ecx
|
||||
jmp patch_again
|
||||
|
||||
done:
|
||||
mov eax, esi # return found block base
|
||||
jmp return
|
||||
|
||||
not_found:
|
||||
xor eax, eax
|
||||
|
||||
return:
|
||||
pop ebp
|
||||
pop edi
|
||||
pop esi
|
||||
pop ebx
|
||||
ret
|
||||
|
||||
get_data_ptr:
|
||||
call get_data_ptr_ret
|
||||
|
||||
# Server suffix:
|
||||
# uint32_t scan_start
|
||||
# uint32_t scan_end
|
||||
# uint32_t signature_size
|
||||
# uint32_t patch_entry_count
|
||||
# signature bytes
|
||||
# repeated compact patch entries:
|
||||
# uint32_t offset
|
||||
# uint8_t size
|
||||
# uint8_t data[size]
|
||||
Reference in New Issue
Block a user