Track PC Brutal Peeps BattleParam patch tier
This commit is contained in:
@@ -153,6 +153,7 @@ public:
|
|||||||
uint8_t override_lobby_number = 0x80; // 80 = no override
|
uint8_t override_lobby_number = 0x80; // 80 = no override
|
||||||
int64_t override_random_seed = -1;
|
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 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;
|
std::unique_ptr<Variations> override_variations;
|
||||||
VectorXYZF pos;
|
VectorXYZF pos;
|
||||||
uint32_t floor = 0x0F;
|
uint32_t floor = 0x0F;
|
||||||
|
|||||||
@@ -5563,7 +5563,15 @@ static asio::awaitable<void> send_brutal_peeps_pc_patch_until_area_load(std::sha
|
|||||||
}
|
}
|
||||||
|
|
||||||
int64_t brutal_peeps_hp_patch_tier = (l->brutal_peeps_tier >= 1) ? l->brutal_peeps_tier : -1;
|
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);
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -5621,6 +5629,9 @@ static asio::awaitable<void> on_6F(std::shared_ptr<Client> c, Channel::Message&
|
|||||||
int64_t brutal_peeps_hp_patch_tier = (l->brutal_peeps_tier >= 1) ? l->brutal_peeps_tier : -1;
|
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);
|
co_await send_brutal_peeps_hp_patch_bb(c, brutal_peeps_hp_patch_tier);
|
||||||
} else if (loading_flag_cleared && (c->version() == Version::PC_V2)) {
|
} 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();
|
auto s = c->require_server_state();
|
||||||
asio::co_spawn(*s->io_context, send_brutal_peeps_pc_patch_until_area_load(c), asio::detached);
|
asio::co_spawn(*s->io_context, send_brutal_peeps_pc_patch_until_area_load(c), asio::detached);
|
||||||
}
|
}
|
||||||
|
|||||||
+86
-33
@@ -1010,16 +1010,33 @@ static std::vector<std::pair<std::string, std::shared_ptr<AsyncPromise<C_Execute
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
auto s = c->require_server_state();
|
auto s = c->require_server_state();
|
||||||
const auto* brutal_peeps_def = brutal_peeps_tier_definition(tier);
|
|
||||||
if ((tier >= 0) && !brutal_peeps_def) {
|
if (tier < -1) {
|
||||||
c->log.warning_f("Skipping Brutal Peeps PC client patch for invalid tier {}", tier);
|
c->log.warning_f("Skipping Brutal Peeps PC client patch for invalid tier {}", tier);
|
||||||
return promises;
|
return promises;
|
||||||
}
|
}
|
||||||
|
|
||||||
const double hp_mult = brutal_peeps_def ? brutal_peeps_def->enemy_hp_multiplier : 1.0;
|
const auto* target_brutal_peeps_def = brutal_peeps_tier_definition(tier);
|
||||||
const double exp_mult = brutal_peeps_def ? brutal_peeps_def->exp_multiplier : 1.0;
|
if ((tier >= 0) && !target_brutal_peeps_def) {
|
||||||
const double atp_mult = [&]() -> double {
|
c->log.warning_f("Skipping Brutal Peeps PC client patch for invalid tier {}", tier);
|
||||||
switch (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:
|
case 1:
|
||||||
return 1.01;
|
return 1.01;
|
||||||
case 2:
|
case 2:
|
||||||
@@ -1044,7 +1061,7 @@ static std::vector<std::pair<std::string, std::shared_ptr<AsyncPromise<C_Execute
|
|||||||
default:
|
default:
|
||||||
return 1.00;
|
return 1.00;
|
||||||
}
|
}
|
||||||
}();
|
};
|
||||||
|
|
||||||
constexpr const char* bp_filename = "BattleParamEntry_on.dat";
|
constexpr const char* bp_filename = "BattleParamEntry_on.dat";
|
||||||
std::string vanilla_data = phosg::load_file("system/patch-pc/Media/PSO/BattleParamEntry_on.dat");
|
std::string vanilla_data = phosg::load_file("system/patch-pc/Media/PSO/BattleParamEntry_on.dat");
|
||||||
@@ -1057,16 +1074,17 @@ static std::vector<std::pair<std::string, std::shared_ptr<AsyncPromise<C_Execute
|
|||||||
// Ultimate stats rows start at 0x2880 and each stats row is 0x24 bytes.
|
// 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.
|
// Within each row: ATP is +0x00, HP is +0x06, EXP is +0x1C.
|
||||||
//
|
//
|
||||||
// Important: PC memory does not appear to contain a byte-perfect full file
|
// The PC memory image does not contain the full file, but the Ultimate
|
||||||
// image, but the Ultimate block is present. Therefore the signature starts
|
// stats block appears when an area is loaded. The scan signature must be
|
||||||
// at ultimate_block_offset, and all patch offsets are relative to the
|
// based on the client's current patch tier, not always vanilla, so we can
|
||||||
// Ultimate block address returned by the client function.
|
// restore or move between BP tiers.
|
||||||
constexpr uint32_t ultimate_block_offset = 0x00002880;
|
constexpr uint32_t ultimate_block_offset = 0x00002880;
|
||||||
constexpr uint32_t ultimate_atp_row_offset = 0x00;
|
constexpr uint32_t ultimate_atp_row_offset = 0x00;
|
||||||
constexpr uint32_t ultimate_hp_row_offset = 0x06;
|
constexpr uint32_t ultimate_hp_row_offset = 0x06;
|
||||||
constexpr uint32_t ultimate_exp_row_offset = 0x1C;
|
constexpr uint32_t ultimate_exp_row_offset = 0x1C;
|
||||||
constexpr uint32_t stats_row_size = 0x24;
|
constexpr uint32_t stats_row_size = 0x24;
|
||||||
constexpr uint32_t num_bp_rows = 0x60;
|
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) {
|
auto append_u32l = +[](std::string& out, uint32_t v) {
|
||||||
out.push_back(static_cast<char>(v & 0xFF));
|
out.push_back(static_cast<char>(v & 0xFF));
|
||||||
@@ -1080,6 +1098,11 @@ static std::vector<std::pair<std::string, std::shared_ptr<AsyncPromise<C_Execute
|
|||||||
(static_cast<uint16_t>(static_cast<uint8_t>(data[offset + 1])) << 8);
|
(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 {
|
auto read_u32l = +[](const std::string& data, uint32_t offset) -> uint32_t {
|
||||||
return static_cast<uint8_t>(data[offset]) |
|
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 + 1])) << 8) |
|
||||||
@@ -1087,6 +1110,13 @@ static std::vector<std::pair<std::string, std::shared_ptr<AsyncPromise<C_Execute
|
|||||||
(static_cast<uint32_t>(static_cast<uint8_t>(data[offset + 3])) << 24);
|
(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 {
|
auto scale_u16 = +[](uint32_t v, double scale) -> uint16_t {
|
||||||
if (v == 0) {
|
if (v == 0) {
|
||||||
return 0;
|
return 0;
|
||||||
@@ -1123,48 +1153,61 @@ static std::vector<std::pair<std::string, std::shared_ptr<AsyncPromise<C_Execute
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
constexpr uint32_t last_needed_offset =
|
constexpr uint32_t last_needed_offset = ultimate_block_offset + ultimate_block_size;
|
||||||
ultimate_block_offset + ((num_bp_rows - 1) * stats_row_size) + ultimate_exp_row_offset + 4;
|
|
||||||
if (vanilla_data.size() < last_needed_offset) {
|
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);
|
c->log.warning_f("Skipping Brutal Peeps PC client patch: {} too small for Ultimate stats table", bp_filename);
|
||||||
return promises;
|
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;
|
std::string suffix;
|
||||||
append_u32l(suffix, scan_start);
|
append_u32l(suffix, scan_start);
|
||||||
append_u32l(suffix, scan_end);
|
append_u32l(suffix, scan_end);
|
||||||
append_u32l(suffix, signature_size);
|
append_u32l(suffix, signature_size);
|
||||||
append_u32l(suffix, 0); // patched below after patch generation
|
append_u32l(suffix, 0); // patched below after patch generation
|
||||||
suffix.append(vanilla_data.data() + ultimate_block_offset, signature_size);
|
suffix.append(source_block.data(), signature_size);
|
||||||
|
|
||||||
uint32_t patch_entry_count = 0;
|
uint32_t patch_entry_count = 0;
|
||||||
|
|
||||||
for (uint32_t z = 0; z < num_bp_rows; z++) {
|
for (uint32_t z = 0; z < num_bp_rows; z++) {
|
||||||
const uint32_t row_file_offset = ultimate_block_offset + (z * stats_row_size);
|
|
||||||
const uint32_t row_patch_offset = z * stats_row_size;
|
const uint32_t row_patch_offset = z * stats_row_size;
|
||||||
|
|
||||||
const uint32_t atp_file_offset = row_file_offset + ultimate_atp_row_offset;
|
|
||||||
const uint32_t atp_patch_offset = row_patch_offset + ultimate_atp_row_offset;
|
const uint32_t atp_patch_offset = row_patch_offset + ultimate_atp_row_offset;
|
||||||
uint16_t old_atp = read_u16l(vanilla_data, atp_file_offset);
|
|
||||||
uint16_t new_atp = scale_u16(old_atp, atp_mult);
|
|
||||||
|
|
||||||
append_patch_entry(suffix, atp_patch_offset, new_atp, 2);
|
|
||||||
patch_entry_count++;
|
|
||||||
|
|
||||||
const uint32_t hp_file_offset = row_file_offset + ultimate_hp_row_offset;
|
|
||||||
const uint32_t hp_patch_offset = row_patch_offset + ultimate_hp_row_offset;
|
const uint32_t hp_patch_offset = row_patch_offset + ultimate_hp_row_offset;
|
||||||
uint16_t old_hp = read_u16l(vanilla_data, hp_file_offset);
|
const uint32_t exp_patch_offset = row_patch_offset + ultimate_exp_row_offset;
|
||||||
uint16_t new_hp = scale_u16(old_hp, hp_mult);
|
|
||||||
|
|
||||||
append_patch_entry(suffix, hp_patch_offset, new_hp, 2);
|
append_patch_entry(suffix, atp_patch_offset, read_u16l(target_block, atp_patch_offset), 2);
|
||||||
patch_entry_count++;
|
patch_entry_count++;
|
||||||
|
|
||||||
const uint32_t exp_file_offset = row_file_offset + ultimate_exp_row_offset;
|
append_patch_entry(suffix, hp_patch_offset, read_u16l(target_block, hp_patch_offset), 2);
|
||||||
const uint32_t exp_patch_offset = row_patch_offset + ultimate_exp_row_offset;
|
patch_entry_count++;
|
||||||
uint32_t old_exp = read_u32l(vanilla_data, exp_file_offset);
|
|
||||||
uint32_t new_exp = scale_u32(old_exp, exp_mult);
|
|
||||||
|
|
||||||
append_patch_entry(suffix, exp_patch_offset, new_exp, 4);
|
append_patch_entry(suffix, exp_patch_offset, read_u32l(target_block, exp_patch_offset), 4);
|
||||||
patch_entry_count++;
|
patch_entry_count++;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1191,8 +1234,13 @@ static std::vector<std::pair<std::string, std::shared_ptr<AsyncPromise<C_Execute
|
|||||||
|
|
||||||
promises.emplace_back(bp_filename, promise);
|
promises.emplace_back(bp_filename, promise);
|
||||||
|
|
||||||
c->log.info_f("Brutal Peeps PC ATP/HP/EXP client patch sent for {}: tier={} hp_mult={:g} atp_mult={:g} exp_mult={:g} patch_entries={} suffix_size={} scan={:08X}-{:08X}",
|
const auto* target_def_for_log = brutal_peeps_tier_definition(tier);
|
||||||
bp_filename, tier, hp_mult, atp_mult, exp_mult, patch_entry_count, suffix.size(), scan_start, scan_end);
|
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;
|
return promises;
|
||||||
|
|
||||||
@@ -1232,6 +1280,11 @@ asio::awaitable<void> send_brutal_peeps_hp_patch_bb(std::shared_ptr<Client> c, i
|
|||||||
return_value,
|
return_value,
|
||||||
static_cast<uint32_t>(result.checksum));
|
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) {
|
if (return_value) {
|
||||||
any_success = true;
|
any_success = true;
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
Reference in New Issue
Block a user