Merge remote-tracking branch 'origin/sync/upstream-20260507'
CMake / build (macos-latest) (push) Has been cancelled
CMake / build (ubuntu-latest) (push) Has been cancelled

This commit is contained in:
2026-05-11 09:10:04 -04:00
71 changed files with 17519 additions and 495 deletions
-2
View File
@@ -30,8 +30,6 @@
## PSOBB
- Figure out why Pouilly Slime EXP doesn't work
- Make server-specified rare enemies work with maps loaded by the proxy
- Implement serialization for various table types (ItemPMT, ItemPT, etc.)
- Record some BB tests
- Add all necessary Guild Card number rewrites in BB commands on the proxy
+9
View File
@@ -81,12 +81,14 @@ Disable item equip restrictions ("God of equip")
3OJ5 => 041050D4 38000005
3OJT => 0415BF50 38000005
3OP0 => 041052D4 38000005
59NJ => 005C9F35 E9A7000000
59NL => 005C9F31 E9A7000000
All items visible in Pioneer 2
3OE1 => 04102D88 38600000
Mags visible in Pioneer 2
59NJ => 005D8F27 EB04
59NL => 005D8F4B EB04
Disable pause menu background + offset
@@ -94,6 +96,9 @@ Disable pause menu background + offset
0428735C 4800000C
3OE2 => 0424CED8 48000370
042887D8 4800000C
59NJ => 00719C58 9090
00733C57 9090
00733ABE 90E9
59NL => 00719B54 9090
00733BA7 9090
00733A0E 90E9
@@ -637,6 +642,8 @@ Fast tekker (skips wind-up jingle)
0023EF77 jmp +0x0A
4OPU => 0023F14C mov dword [ebp + 0x14C], 1
0023F167 jmp +0x0A
59NJ => 006DA14B mov dword [edi + 0x14C], 1
006DA168 jmp +0x0B
59NL => 006DA113 mov dword [edi + 0x14C], 1
006DA130 jmp +0x0B
@@ -973,6 +980,8 @@ Override Challenge mode random enemy location tables limit
4OEU => 002E742C XXXXXXXX (count as little-endian dword)
4OPD => 002E720C XXXXXXXX (count as little-endian dword)
4OPU => 002E745C XXXXXXXX (count as little-endian dword)
59NJ => 0080FA3F XXXXXXXX (count * 4 as little-endian dword)
0080FA58 XXXXXXXX (count as little-endian dword)
59NL => 0080ECB7 XXXXXXXX (count * 4 as little-endian dword)
0080ECD0 XXXXXXXX (count as little-endian dword)
+1 -1
View File
@@ -486,7 +486,7 @@ void ItemData::encode_for_version(Version to_version, shared_ptr<const ItemParam
if (should_encode_v2_data && (this->data1[1] > 0x26)) {
if (this->data1[1] < 0x89) {
this->data1[5] = this->data1[1];
this->data1[1] = item_parameter_table->get_weapon_class(this->data1[1]);
this->data1[1] = item_parameter_table->get_weapon_kind(this->data1[1]);
if (this->data1[1] == 0x00) {
this->data1[1] = 0x0F;
}
+195 -11
View File
@@ -1,5 +1,7 @@
#include "ItemNameIndex.hh"
#include <algorithm>
#include "StaticGameData.hh"
using namespace std;
@@ -787,9 +789,9 @@ void ItemNameIndex::print_table(FILE* stream) const {
auto pmt = this->item_parameter_table;
phosg::fwrite_fmt(stream, "WEAPONS\n");
phosg::fwrite_fmt(stream, " CODE => ---ID--- TYPE SKIN POINTS FLAG ATPLO ATPHI ATPRQ MSTRQ ATARQ -MST- GND PH SP ATA SB(S1:AMT1,S2:AMT2) PJ 1X 1Y 2X 2Y CR --A1-- A4 A5 TB BF CL ST* USL ---DIVISOR--- NAME\n");
phosg::fwrite_fmt(stream, " CODE => ---ID--- TYPE SKIN POINTS FLAG ATPLO ATPHI ATPRQ MSTRQ ATARQ -MST- GND PH SP ATA SB(S1:AMT1,S2:AMT2) PJ 1X 1Y 2X 2Y CR --A1-- A4 A5 TB(TN:FL:AMOUNT, ... ) BF CL ST* USL ---DIVISOR--- NAME\n");
for (size_t data1_1 = 0; data1_1 < pmt->num_weapon_classes(); data1_1++) {
uint8_t weapon_class = pmt->get_weapon_class(data1_1);
uint8_t weapon_class = pmt->get_weapon_kind(data1_1);
float sale_divisor = pmt->get_sale_divisor(0x00, data1_1);
string divisor_str = std::format("{:g}", sale_divisor);
divisor_str.resize(13, ' ');
@@ -806,8 +808,24 @@ void ItemNameIndex::print_table(FILE* stream) const {
item.data1[2] = data1_2;
string name = this->describe_item(item);
auto& stat_boost = pmt->get_stat_boost(w.stat_boost_entry_index);
phosg::fwrite_fmt(stream, " 00{:02X}{:02X} => {:08X} {:04X} {:04X} {:6} {:04X} {:5} {:5} {:5} {:5} {:5} {:5} {:3} {:02X} {:02X} {:3} {:02X}({:02X}:{:04X},{:02X}:{:04X}) {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X}{:02X}{:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:2}* {} {} {}\n",
const auto& stat_boost = pmt->get_stat_boost(w.stat_boost_entry_index);
string tech_boost_str;
if (w.tech_boost_entry_index < pmt->num_tech_boosts()) {
const auto& tech_boost = pmt->get_tech_boost(w.tech_boost_entry_index);
tech_boost_str = std::format("({:02X}:{:02X}:{:g},{:02X}:{:02X}:{:g},{:02X}:{:02X}:{:g})",
tech_boost.tech_num1, tech_boost.flags1, tech_boost.amount1, tech_boost.tech_num2, tech_boost.flags2,
tech_boost.amount2, tech_boost.tech_num3, tech_boost.flags3, tech_boost.amount3);
}
tech_boost_str.resize(40, ' ');
phosg::fwrite_fmt(stream,
// CODE => ID TYPE SKIN PTS FLAG ATP- ATP+ ATPR MSTR ATAR MST GND PH SP ATA
" 00{:02X}{:02X} => {:08X} {:04X} {:04X} {:6} {:04X} {:5} {:5} {:5} {:5} {:5} {:5} {:3} {:02X} {:02X} {:3} "
// SB( S1:AMT1 , S2:AMT2 ) PJ 1X 1Y 2X 2Y CR --------A1-------- A4
"{:02X}({:02X}:{:04X},{:02X}:{:04X}) {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X}{:02X}{:02X} {:02X} "
// A5 TB( TN: FL: AMT, TN: FL: AMT, TN: FL: AMT) BF CL ST* US DV NAME\n"
"{:02X} {:02X}{} {:02X} {:02X} {:2}* {} {} {}\n",
data1_1,
data1_2,
w.id,
@@ -841,7 +859,8 @@ void ItemNameIndex::print_table(FILE* stream) const {
w.unknown_a1[2],
w.unknown_a4,
w.unknown_a5,
w.tech_boost,
w.tech_boost_entry_index,
tech_boost_str,
w.behavior_flags,
weapon_class,
stars,
@@ -852,7 +871,7 @@ void ItemNameIndex::print_table(FILE* stream) const {
}
phosg::fwrite_fmt(stream, "ARMORS\n");
phosg::fwrite_fmt(stream, " CODE => ---ID--- TYPE SKIN POINTS -DFP- -EVP- BP BE FLAG LVL EFR ETH EIC EDK ELT DFR EVR SB(S1:AMT1,S2:AMT2) TB FT A4 ST* ---DIVISOR--- NAME\n");
phosg::fwrite_fmt(stream, " CODE => ---ID--- TYPE SKIN POINTS -DFP- -EVP- BP BE FLAG LVL EFR ETH EIC EDK ELT DFR EVR SB(S1:AMT1,S2:AMT2) TB(TN:FL:AMOUNT, ... ) FT A4 ST* ---DIVISOR--- NAME\n");
for (size_t data1_1 = 1; data1_1 < 3; data1_1++) {
float sale_divisor = pmt->get_sale_divisor(0x01, data1_1);
string divisor_str = std::format("{:g}", sale_divisor);
@@ -870,7 +889,17 @@ void ItemNameIndex::print_table(FILE* stream) const {
string name = this->describe_item(item);
auto& stat_boost = pmt->get_stat_boost(a.stat_boost_entry_index);
phosg::fwrite_fmt(stream, " 01{:02X}{:02X} => {:08X} {:04X} {:04X} {:6} {:5} {:5} {:02X} {:02X} {:04X} {:3} {:3} {:3} {:3} {:3} {:3} {:3} {:3} {:02X}({:02X}:{:04X},{:02X}:{:04X}) {:02X} {:02X} {:02X} {:2}* {} {}\n",
string tech_boost_str;
if (a.tech_boost_entry_index < pmt->num_tech_boosts()) {
const auto& tech_boost = pmt->get_tech_boost(a.tech_boost_entry_index);
tech_boost_str = std::format("({:02X}:{:02X}:{:g},{:02X}:{:02X}:{:g},{:02X}:{:02X}:{:g})",
tech_boost.tech_num1, tech_boost.flags1, tech_boost.amount1, tech_boost.tech_num2, tech_boost.flags2,
tech_boost.amount2, tech_boost.tech_num3, tech_boost.flags3, tech_boost.amount3);
}
tech_boost_str.resize(40, ' ');
phosg::fwrite_fmt(stream, " 01{:02X}{:02X} => {:08X} {:04X} {:04X} {:6} {:5} {:5} {:02X} {:02X} {:04X} {:3} {:3} {:3} {:3} {:3} {:3} {:3} {:3} {:02X}({:02X}:{:04X},{:02X}:{:04X}) {:02X}{} {:02X} {:02X} {:2}* {} {}\n",
data1_1,
data1_2,
a.id,
@@ -895,7 +924,8 @@ void ItemNameIndex::print_table(FILE* stream) const {
stat_boost.amount1,
stat_boost.stat2,
stat_boost.amount2,
a.tech_boost,
a.tech_boost_entry_index,
tech_boost_str,
a.flags_type,
a.unknown_a4,
stars,
@@ -1040,7 +1070,7 @@ void ItemNameIndex::print_table(FILE* stream) const {
}
phosg::fwrite_fmt(stream, "SPECIAL DEFINITIONS\n");
phosg::fwrite_fmt(stream, " SPECIAL => TYPE COUNT ST* NAME\n");
phosg::fwrite_fmt(stream, " SP => TYPE COUNT ST* NAME\n");
for (size_t index = 0; index < pmt->num_specials(); index++) {
const auto& sp = pmt->get_special(index);
uint8_t stars = pmt->get_special_stars(index);
@@ -1051,12 +1081,12 @@ void ItemNameIndex::print_table(FILE* stream) const {
} catch (const out_of_range&) {
}
}
phosg::fwrite_fmt(stream, " {:02X} => {:04X} {:5} {:2}* {}\n", index, sp.type, sp.amount, stars, name);
phosg::fwrite_fmt(stream, " {:02X} => {:04X} {:5} {:2}* {}\n", index, sp.type, sp.amount, stars, name);
}
phosg::fwrite_fmt(stream, "ITEM COMBINATIONS\n");
phosg::fwrite_fmt(stream, " ---USE + -EQUIP => RESULT MLV GND LVL CLS\n");
for (const auto& combo_list_it : pmt->get_all_item_combinations()) {
for (const auto& combo_list_it : pmt->item_combinations_index()) {
for (const auto& combo : combo_list_it.second) {
phosg::fwrite_fmt(stream, " {:02X}{:02X}{:02X} + {:02X}{:02X}{:02X} => {:02X}{:02X}{:02X}",
combo.used_item[0], combo.used_item[1], combo.used_item[2],
@@ -1096,4 +1126,158 @@ void ItemNameIndex::print_table(FILE* stream) const {
event_item.item[0], event_item.item[1], event_item.item[2], event_item.probability);
}
}
phosg::fwrite_fmt(stream, "PHOTON COLORS\n");
phosg::fwrite_fmt(stream, " ## => ---A1--- (A2) (A3)\n");
for (size_t z = 0; z < pmt->num_photon_colors(); z++) {
const auto& pc = pmt->get_photon_color(z);
phosg::fwrite_fmt(stream, " {:02X} => {:08X} ({:g}, {:g}, {:g}, {:g}) ({:g}, {:g}, {:g}, {:g})\n",
z, pc.unknown_a1, pc.unknown_a2.x, pc.unknown_a2.y, pc.unknown_a2.z, pc.unknown_a2.t,
pc.unknown_a3.x, pc.unknown_a3.y, pc.unknown_a3.z, pc.unknown_a3.t);
}
phosg::fwrite_fmt(stream, "WEAPON RANGES\n");
phosg::fwrite_fmt(stream, " ## => ---A3--- ---A4--- ---A5--- (A1) (A2)\n");
for (size_t z = 0; z < pmt->num_weapon_ranges(); z++) {
const auto& wr = pmt->get_weapon_range(z);
phosg::fwrite_fmt(stream, " {:02X} => {:08X} {:08X} {:08X} ({:g}) ({:g})\n",
z, wr.unknown_a3, wr.unknown_a4, wr.unknown_a5, wr.unknown_a1, wr.unknown_a2);
}
phosg::fwrite_fmt(stream, "SALE DIVISORS\n");
phosg::fwrite_fmt(stream, " ARMOR = {:g}\n", pmt->get_sale_divisor(1, 1));
phosg::fwrite_fmt(stream, " SHIELD = {:g}\n", pmt->get_sale_divisor(1, 2));
phosg::fwrite_fmt(stream, " UNIT = {:g}\n", pmt->get_sale_divisor(1, 3));
phosg::fwrite_fmt(stream, " MAG = {:g}\n", pmt->get_sale_divisor(2, 0));
auto write_data_string = [&](const std::string& data, size_t addr = 0) -> void {
if (data.empty()) {
phosg::fwrite_fmt(stream, " (no data)\n");
} else {
auto data_str = phosg::format_data(data, addr);
phosg::strip_trailing_whitespace(data_str);
phosg::fwrite_fmt(stream, " {}\n", phosg::str_replace_all(data_str, "\n", "\n "));
}
};
phosg::fwrite_fmt(stream, "STAR VALUES\n");
write_data_string(pmt->get_star_value_table(), pmt->get_star_value_index_range().first);
phosg::fwrite_fmt(stream, "UNKNOWN_A1\n");
write_data_string(pmt->get_unknown_a1());
phosg::fwrite_fmt(stream, "WEAPON EFFECTS\n");
phosg::fwrite_fmt(stream, " ## => -SOUND1- -VALUE1- -SOUND2- -VALUE2- ----------------A5---------------\n");
for (size_t z = 0; z < pmt->num_weapon_effects(); z++) {
const auto& we = pmt->get_weapon_effect(z);
auto a5_str = phosg::format_data_string(we.unknown_a5.data(), we.unknown_a5.size());
phosg::fwrite_fmt(stream, " {:02X} => {:08X} {:08X} {:08X} {:08X} {}\n",
z, we.sound_id1, we.eff_value1, we.sound_id2, we.eff_value2, a5_str);
}
phosg::fwrite_fmt(stream, "WEAPON STAT BOOST INDEX TABLE\n");
write_data_string(pmt->get_weapon_stat_boost_index_table());
phosg::fwrite_fmt(stream, "ARMOR STAT BOOST INDEX TABLE\n");
write_data_string(pmt->get_armor_stat_boost_index_table());
phosg::fwrite_fmt(stream, "SHIELD STAT BOOST INDEX TABLE\n");
write_data_string(pmt->get_shield_stat_boost_index_table());
phosg::fwrite_fmt(stream, "STAT BOOSTS\n");
phosg::fwrite_fmt(stream, " ## => BOOSTS\n");
for (size_t z = 0; z < pmt->num_stat_boosts(); z++) {
const auto& sb = pmt->get_stat_boost(z);
static constexpr std::array<const char*, 0x10> stat_names{
"ATP+", "ATA+", "EVP+", "DFP+", "MST+", "HP+", "LCK+", "ALL+",
"ATP-", "ATA-", "EVP-", "DFP-", "MST-", "HP-", "LCK-", "ALL-"};
string s;
if (sb.stat1 > 0x10) {
s = std::format("[{:02X}:{:04X}]", sb.stat1, sb.amount1);
} else if (sb.stat1 > 0) {
s = std::format("{}{}", stat_names[sb.stat1 - 1], sb.amount1);
}
if (sb.stat2) {
if (!s.empty()) {
s += ", ";
}
if (sb.stat2 > 0x10) {
s += std::format("[{:02X}:{:04X}]", sb.stat2, sb.amount2);
} else if (sb.stat2 > 0) {
s += std::format("{}{}", stat_names[sb.stat2 - 1], sb.amount2);
}
}
if (s.empty()) {
s = "(none)";
}
phosg::fwrite_fmt(stream, " {:02X} => {}\n", z, s);
}
phosg::fwrite_fmt(stream, "SHIELD EFFECTS\n");
phosg::fwrite_fmt(stream, " ## => -SOUND1- ---A1---\n");
for (size_t z = 0; z < pmt->num_shield_effects(); z++) {
const auto& se = pmt->get_shield_effect(z);
phosg::fwrite_fmt(stream, " {:02X} => {:08X} {:08X}\n", z, se.sound_id, se.unknown_a1);
}
phosg::fwrite_fmt(stream, "SOUND REMAPS\n");
phosg::fwrite_fmt(stream, " -SOUND1- => RT:[...] CC:[...]\n");
for (const auto& remap : pmt->get_all_sound_remaps()) {
std::string rt_str;
for (uint32_t rt_sound_id : remap.by_rt_index) {
if (!rt_str.empty()) {
rt_str += ",";
}
rt_str += std::format("{:08X}", rt_sound_id);
}
std::string cc_str;
for (uint32_t cc_sound_id : remap.by_char_class) {
if (!cc_str.empty()) {
cc_str += ",";
}
cc_str += std::format("{:08X}", cc_sound_id);
}
phosg::fwrite_fmt(stream, " {:08X} => RT:[{}] CC:[{}]\n", remap.sound_id, rt_str, cc_str);
}
phosg::fwrite_fmt(stream, "TECH BOOSTS\n");
phosg::fwrite_fmt(stream, " ## => BOOSTS\n");
for (size_t z = 0; z < pmt->num_tech_boosts(); z++) {
const auto& tb = pmt->get_tech_boost(z);
string s;
if (tb.amount1) {
s += std::format("{:02X}:{:02X}:{:g}", tb.tech_num1, tb.flags1, tb.amount1);
}
if (tb.amount2) {
s += std::format("{:02X}:{:02X}:{:g}", tb.tech_num2, tb.flags2, tb.amount2);
}
if (tb.amount3) {
s += std::format("{:02X}:{:02X}:{:g}", tb.tech_num3, tb.flags3, tb.amount3);
}
phosg::fwrite_fmt(stream, " {:02X} => {}\n", z, s);
}
phosg::fwrite_fmt(stream, "UNSEALABLE ITEMS\n");
phosg::fwrite_fmt(stream, " -ITEM- NAME\n");
std::vector<uint32_t> unsealable_items;
for (uint32_t item_code : pmt->all_unsealable_items()) {
unsealable_items.emplace_back(item_code);
}
std::sort(unsealable_items.begin(), unsealable_items.end());
for (uint32_t item_code : unsealable_items) {
ItemData item;
item.data1[0] = item_code >> 16;
item.data1[1] = item_code >> 8;
item.data1[2] = item_code;
string name = this->describe_item(item);
phosg::fwrite_fmt(stream, " {:06X} {}\n", item_code, name);
}
phosg::fwrite_fmt(stream, "RANGED SPECIALS\n");
phosg::fwrite_fmt(stream, " ## => 11 12 WR A1\n");
for (size_t z = 0; z < pmt->num_ranged_specials(); z++) {
const auto& rs = pmt->get_ranged_special(z);
phosg::fwrite_fmt(stream, " {:02X} => {:02X} {:02X} {:02X} {:02X}\n",
z, rs.data1_1, rs.data1_2, rs.weapon_range_index, rs.unknown_a1);
}
}
+2329 -279
View File
File diff suppressed because it is too large Load Diff
+207 -67
View File
@@ -51,6 +51,9 @@ public:
uint16_t type = 0; // "Model" in Soly's ItemPMT editor
uint16_t skin = 0; // "Texture" in Soly's ItemPMT editor
uint32_t team_points = 0;
void parse_base_from_json(const phosg::JSON& json);
phosg::JSON json() const;
};
struct Weapon : ItemBase {
uint16_t class_flags = 0;
@@ -64,7 +67,8 @@ public:
uint8_t photon = 0;
uint8_t special = 0;
uint8_t ata = 0;
uint8_t stat_boost_entry_index = 0; // TODO: This could be larger (16 or 32 bits)
uint8_t stat_boost_entry_index = 0;
parray<uint8_t, 3> v2_unknown_a9;
uint8_t projectile = 0;
int8_t trail1_x = 0;
int8_t trail1_y = 0;
@@ -74,13 +78,16 @@ public:
parray<uint8_t, 3> unknown_a1 = 0;
uint8_t unknown_a4 = 0;
uint8_t unknown_a5 = 0;
uint8_t tech_boost = 0;
uint8_t tech_boost_entry_index = 0;
// Bits in behavior_flags:
// 01 = disable combos (weapon can only be used once in a row)
// 02 = TODO (sets TItemWeapon flag 40000; used in TItemWeapon_v1E)
// 04 = TODO (sets TItemWeapon flag 80000; used in TItemWeapon_v1E)
// 08 = weapon cannot have attributes (they are ignored if present)
uint8_t behavior_flags = 0;
static Weapon from_json(const phosg::JSON& json);
phosg::JSON json() const;
};
struct ArmorOrShield : ItemBase {
@@ -98,7 +105,7 @@ public:
uint8_t dfp_range = 0;
uint8_t evp_range = 0;
uint8_t stat_boost_entry_index = 0;
uint8_t tech_boost = 0;
uint8_t tech_boost_entry_index = 0;
// TODO: Figure out what this does. Only a few values appear to do anything:
// Armors:
// 01 sets item->flags |= 1 (used in TItemProArmor_v10)
@@ -108,12 +115,18 @@ public:
// 03 sets item->flags |= 8 (used in TItemProShield_v1A)
uint8_t flags_type = 0;
uint8_t unknown_a4 = 0;
static ArmorOrShield from_json(const phosg::JSON& json);
phosg::JSON json() const;
};
struct Unit : ItemBase {
uint16_t stat = 0;
uint16_t stat_amount = 0;
int16_t modifier_amount = 0;
static Unit from_json(const phosg::JSON& json);
phosg::JSON json() const;
};
struct Mag : ItemBase {
@@ -142,6 +155,9 @@ public:
uint8_t on_death_flag = 0;
uint8_t on_boss_flag = 0;
uint16_t class_flags = 0x00FF;
static Mag from_json(const phosg::JSON& json);
phosg::JSON json() const;
};
struct Tool : ItemBase {
@@ -157,7 +173,10 @@ public:
// 00000020 - usable in boss arenas
// 00000040 - usable in Challenge mode
// 00000080 - is rare (renders as red box; V3+ only)
/* 0C */ uint32_t item_flags = 0;
uint32_t item_flags = 0;
static Tool from_json(const phosg::JSON& json);
phosg::JSON json() const;
};
struct MagFeedResult {
@@ -168,11 +187,17 @@ public:
int8_t iq = 0;
int8_t synchro = 0;
parray<uint8_t, 2> unused;
static MagFeedResult from_json(const phosg::JSON& json);
phosg::JSON json() const;
} __packed_ws__(MagFeedResult, 8);
struct Special {
uint16_t type = 0xFFFF;
uint16_t amount = 0;
static Special from_json(const phosg::JSON& json);
phosg::JSON json() const;
};
struct StatBoost {
@@ -201,6 +226,9 @@ public:
uint16_t amount1 = 0;
uint8_t stat2 = 0;
uint16_t amount2 = 0;
static StatBoost from_json(const phosg::JSON& json);
phosg::JSON json() const;
} __attribute__((packed));
// Indexed as [tech_num][char_class]
@@ -215,23 +243,41 @@ public:
uint8_t level = 0;
uint8_t char_class = 0;
parray<uint8_t, 3> unused;
static ItemCombination from_json(const phosg::JSON& json);
phosg::JSON json() const;
} __packed_ws__(ItemCombination, 0x10);
struct TechniqueBoost {
uint8_t tech_num = 0;
// It appears that only one bit in the flags field is used: 01 = enable piercing (for Megid)
uint8_t flags = 0;
float amount = 0.0f;
struct TechBoost {
// It appears that only one bit in the flags fields is used: 01 = enable piercing (for Megid)
uint8_t tech_num1 = 0;
uint8_t flags1 = 0;
float amount1 = 0.0f;
uint8_t tech_num2 = 0;
uint8_t flags2 = 0;
float amount2 = 0.0f;
uint8_t tech_num3 = 0;
uint8_t flags3 = 0;
float amount3 = 0.0f;
static TechBoost from_json(const phosg::JSON& json);
phosg::JSON json() const;
};
struct EventItem {
parray<uint8_t, 3> item;
uint8_t probability = 0;
static EventItem from_json(const phosg::JSON& json);
phosg::JSON json() const;
} __packed_ws__(EventItem, 4);
struct UnsealableItem {
parray<uint8_t, 3> item;
uint8_t unused = 0;
static UnsealableItem from_json(const phosg::JSON& json);
phosg::JSON json() const;
} __packed_ws__(UnsealableItem, 4);
struct NonWeaponSaleDivisors {
@@ -239,119 +285,213 @@ public:
float shield_divisor = 0.0f;
float unit_divisor = 0.0f;
float mag_divisor = 0.0f;
static NonWeaponSaleDivisors from_json(const phosg::JSON& json);
phosg::JSON json() const;
};
struct ShieldEffect {
uint32_t sound_id;
uint32_t unknown_a1;
uint32_t sound_id = 0;
uint32_t unknown_a1 = 0;
static ShieldEffect from_json(const phosg::JSON& json);
phosg::JSON json() const;
};
struct PhotonColorEntry {
uint32_t unknown_a1;
uint32_t unknown_a1 = 0;
VectorXYZTF unknown_a2;
VectorXYZTF unknown_a3;
static PhotonColorEntry from_json(const phosg::JSON& json);
phosg::JSON json() const;
};
struct UnknownA1 {
uint16_t unknown_a1;
uint16_t unknown_a2;
};
uint16_t unknown_a1 = 0;
uint16_t unknown_a2 = 0;
struct UnknownA5 {
uint32_t target_param; // For players, char_class; for enemies, rt_index; for objects, 0x30
uint32_t unknown_a2;
uint32_t unknown_a3;
static UnknownA1 from_json(const phosg::JSON& json);
phosg::JSON json() const;
};
struct WeaponEffect {
uint32_t sound_id1;
uint32_t eff_value1;
uint32_t sound_id2;
uint32_t eff_value2;
uint32_t sound_id1 = 0;
uint32_t eff_value1 = 0;
uint32_t sound_id2 = 0;
uint32_t eff_value2 = 0;
parray<uint8_t, 0x10> unknown_a5;
static WeaponEffect from_json(const phosg::JSON& json);
phosg::JSON json() const;
};
struct WeaponRange {
float unknown_a1;
float unknown_a2;
uint32_t unknown_a3; // Angle
uint32_t unknown_a4; // Angle
uint32_t unknown_a5;
float unknown_a1 = 0;
float unknown_a2 = 0;
uint32_t unknown_a3 = 0; // Angle
uint32_t unknown_a4 = 0; // Angle
uint32_t unknown_a5 = 0;
static WeaponRange from_json(const phosg::JSON& json);
phosg::JSON json() const;
};
struct RangedSpecial {
uint8_t data1_1;
uint8_t data1_2;
uint8_t weapon_range_index;
uint8_t unknown_a1;
uint8_t data1_1 = 0;
uint8_t data1_2 = 0;
uint8_t weapon_range_index = 0;
uint8_t unknown_a1 = 0;
static RangedSpecial from_json(const phosg::JSON& json);
phosg::JSON json() const;
} __packed_ws__(RangedSpecial, 4);
ItemParameterTable() = delete;
struct SoundRemaps {
uint32_t sound_id = 0;
std::vector<uint32_t> by_rt_index;
std::vector<uint32_t> by_char_class;
static SoundRemaps from_json(const phosg::JSON& json);
phosg::JSON json() const;
};
virtual ~ItemParameterTable() = default;
static std::shared_ptr<ItemParameterTable> create(std::shared_ptr<const std::string> data, Version version);
static std::shared_ptr<ItemParameterTable> from_binary(std::shared_ptr<const std::string> data, Version version);
static std::shared_ptr<ItemParameterTable> from_json(const phosg::JSON& json);
phosg::JSON json() const;
std::string serialize_binary(Version version) const;
std::set<uint32_t> compute_all_valid_primary_identifiers() const;
// weapon_table accessors
virtual size_t num_weapon_classes() const = 0;
virtual size_t num_weapons_in_class(uint8_t data1_1) const = 0;
virtual const Weapon& get_weapon(uint8_t data1_1, uint8_t data1_2) const = 0;
// armor_table accessors
virtual size_t num_armors_or_shields_in_class(uint8_t data1_1) const = 0;
virtual const ArmorOrShield& get_armor_or_shield(uint8_t data1_1, uint8_t data1_2) const = 0;
// unit_table accessors
virtual size_t num_units() const = 0;
virtual const Unit& get_unit(uint8_t data1_2) const = 0;
virtual size_t num_mags() const = 0;
virtual const Mag& get_mag(uint8_t data1_1) const = 0;
// tool_table accessors
virtual size_t num_tool_classes() const = 0;
virtual size_t num_tools_in_class(uint8_t data1_1) const = 0;
virtual const Tool& get_tool(uint8_t data1_1, uint8_t data1_2) const = 0;
virtual std::pair<uint8_t, uint8_t> find_tool_by_id(uint32_t id) const = 0;
std::variant<const Weapon*, const ArmorOrShield*, const Unit*, const Mag*, const Tool*>
definition_for_primary_identifier(uint32_t primary_identifier) const;
// mag_table accessors
virtual size_t num_mags() const = 0;
virtual const Mag& get_mag(uint8_t data1_1) const = 0;
// weapon_kind_table accessors (data1_1 in [0, num_weapon_classes()])
virtual size_t num_weapon_kinds() const = 0;
virtual uint8_t get_weapon_kind(uint8_t data1_1) const = 0;
// photon_color_table accessors
virtual size_t num_photon_colors() const = 0;
virtual const PhotonColorEntry& get_photon_color(size_t index) const = 0;
// weapon_range_table accessors
virtual size_t num_weapon_ranges() const = 0;
virtual const WeaponRange& get_weapon_range(size_t index) const = 0;
// weapon_sale_divisor_table and non_weapon_sale_divisor_table accessors (data1_0 in [0, 1, 2]; data1_1 in [0,
// num_weapon_classes()] for weapons or ignored otherwise)
virtual size_t num_weapon_sale_divisors() const = 0;
virtual float get_sale_divisor(uint8_t data1_0, uint8_t data1_1) const = 0;
virtual const MagFeedResult& get_mag_feed_result(uint8_t table_index, uint8_t which) const = 0;
// mag_feed_table accessors (table_index in [0, 7], item_index in [0, 10])
virtual const MagFeedResult& get_mag_feed_result(uint8_t table_index, uint8_t item_index) const = 0;
// star_value_table accessors
virtual std::pair<uint32_t, uint32_t> get_star_value_index_range() const = 0;
virtual uint32_t get_special_stars_base_index() const = 0;
virtual uint8_t get_item_stars(uint32_t id) const = 0;
virtual uint8_t get_special_stars(uint8_t special) const = 0;
std::string get_star_value_table() const;
// unknown_a1 accessors
virtual std::string get_unknown_a1() const = 0;
// special_table accessors
virtual size_t num_specials() const = 0;
virtual const Special& get_special(uint8_t special) const = 0;
virtual const StatBoost& get_stat_boost(uint8_t entry_index) const = 0;
virtual uint8_t get_max_tech_level(uint8_t char_class, uint8_t tech_num) const = 0;
virtual uint8_t get_weapon_class(uint8_t data1_1) const = 0;
// weapon_effect_table accessors
virtual size_t num_weapon_effects() const = 0;
virtual const WeaponEffect& get_weapon_effect(size_t index) const = 0;
// weapon_stat_boost_index_table accessors
virtual size_t num_weapon_stat_boost_indexes() const = 0;
virtual uint8_t get_weapon_stat_boost_index(size_t index) const = 0;
std::string get_weapon_stat_boost_index_table() const;
// armor_stat_boost_index_table accessors
virtual size_t num_armor_stat_boost_indexes() const = 0;
virtual uint8_t get_armor_stat_boost_index(size_t index) const = 0;
std::string get_armor_stat_boost_index_table() const;
// shield_stat_boost_index_table accessors
virtual size_t num_shield_stat_boost_indexes() const = 0;
virtual uint8_t get_shield_stat_boost_index(size_t index) const = 0;
std::string get_shield_stat_boost_index_table() const;
// stat_boost_table accessors
virtual size_t num_stat_boosts() const = 0;
virtual const StatBoost& get_stat_boost(size_t index) const = 0;
// shield_effect_table accessors
virtual size_t num_shield_effects() const = 0;
virtual const ShieldEffect& get_shield_effect(size_t index) const = 0;
// max_tech_level_table accessors
virtual uint8_t get_max_tech_level(uint8_t char_class, uint8_t tech_num) const = 0;
// combination_table accessors
virtual size_t num_item_combinations() const = 0;
virtual const ItemCombination& get_item_combination(size_t index) const = 0;
const std::map<uint32_t, std::vector<ItemCombination>>& item_combinations_index() const;
const std::vector<ItemCombination>& all_combinations_for_used_item(const ItemData& used_item) const;
const ItemCombination& get_item_combination(const ItemData& used_item, const ItemData& equipped_item) const;
// sound_remap_table accessors
virtual const std::vector<SoundRemaps>& get_all_sound_remaps() const = 0;
// tech_boost_table accessors
virtual size_t num_tech_boosts() const = 0;
virtual const TechBoost& get_tech_boost(size_t index) const = 0;
// unwrap_table accessors
virtual size_t num_events() const = 0;
virtual std::pair<const EventItem*, size_t> get_event_items(uint8_t event_number) const = 0;
// unsealable_table accessors
virtual const std::unordered_set<uint32_t>& all_unsealable_items() const = 0;
bool is_unsealable_item(uint8_t data1_0, uint8_t data1_1, uint8_t data1_2) const;
bool is_unsealable_item(const ItemData& item) const;
// ranged_special_table accessors
virtual size_t num_ranged_specials() const = 0;
virtual const RangedSpecial& get_ranged_special(size_t index) const = 0;
// Composite accessors
std::variant<const Weapon*, const ArmorOrShield*, const Unit*, const Mag*, const Tool*>
definition_for_primary_identifier(uint32_t primary_identifier) const;
uint32_t get_item_id(const ItemData& item) const;
uint32_t get_item_team_points(const ItemData& item) const;
uint8_t get_item_base_stars(const ItemData& item) const;
uint8_t get_item_adjusted_stars(const ItemData& item, bool ignore_unidentified = false) const;
bool is_item_rare(const ItemData& item) const;
virtual bool is_unsealable_item(uint8_t data1_0, uint8_t data1_1, uint8_t data1_2) const = 0;
bool is_unsealable_item(const ItemData& item) const;
const ItemCombination& get_item_combination(const ItemData& used_item, const ItemData& equipped_item) const;
const std::vector<ItemCombination>& get_all_combinations_for_used_item(const ItemData& used_item) const;
virtual const std::map<uint32_t, std::vector<ItemCombination>>& get_all_item_combinations() const = 0;
virtual size_t num_events() const = 0;
virtual std::pair<const EventItem*, size_t> get_event_items(uint8_t event_number) const = 0;
size_t price_for_item(const ItemData& item) const;
protected:
std::shared_ptr<const std::string> data;
phosg::StringReader r;
ItemParameterTable() = default;
mutable std::unordered_map<uint16_t, Weapon> weapons;
mutable std::vector<ArmorOrShield> armors;
mutable std::vector<ArmorOrShield> shields;
mutable std::vector<Unit> units;
mutable std::vector<Mag> mags;
mutable std::unordered_map<uint16_t, Tool> tools;
mutable std::vector<Special> specials;
mutable std::vector<StatBoost> stat_boosts;
// Key is used_item. We can't index on (used_item, equipped_item) because equipped_item may contain wildcards, and
// the matching order matters.
mutable std::map<uint32_t, std::vector<ItemCombination>> item_combination_index;
explicit ItemParameterTable(std::shared_ptr<const std::string> data);
mutable std::optional<std::map<uint32_t, std::vector<ItemCombination>>> item_combination_index;
};
+4 -4
View File
@@ -46,18 +46,18 @@ ItemTranslationTable::ItemTranslationTable(
uint32_t e_id = this->entries[z].id_for_version[v_s];
if (is_canonical(e_id)) {
if (!entry_index.count(e_id)) {
throw logic_error(std::format("(row {} version {}) canonical ID {:X} is missing from the index", z, phosg::name_for_enum(v), e_id));
throw logic_error(std::format("(row {} version {}) canonical ID {:08X} is missing from the index", z, phosg::name_for_enum(v), e_id));
}
try {
item_parameter_table->definition_for_primary_identifier(e_id);
} catch (const out_of_range&) {
throw runtime_error(std::format("(row {} version {}) ID {:X} not defined in item parameter table", z, phosg::name_for_enum(v), e_id));
throw runtime_error(std::format("(row {} version {}) ID {:08X} not defined in item parameter table", z, phosg::name_for_enum(v), e_id));
}
if (!remaining_identifiers.erase(e_id)) {
throw runtime_error(std::format("(row {} version {}) ID {:X} not in item parameter table's primary identifier list", z, phosg::name_for_enum(v), e_id));
throw runtime_error(std::format("(row {} version {}) ID {:08X} not in item parameter table's primary identifier list", z, phosg::name_for_enum(v), e_id));
}
} else if (!entry_index.count(make_canonical(e_id))) {
throw runtime_error(std::format("(row {} version {}) ID {:X} refers to nonexistent canonical ID", z, phosg::name_for_enum(v), e_id));
throw runtime_error(std::format("(row {} version {}) ID {:08X} refers to nonexistent canonical ID", z, phosg::name_for_enum(v), e_id));
}
}
+41
View File
@@ -2387,6 +2387,47 @@ Action a_compare_common_item_set(
cs1->print_diff(stdout, *cs2);
});
Action a_decode_item_parameter_table(
"decode-item-parameter-table", "\
decode-item-parameter-table [INPUT-FILENAME [OUTPUT-FILENAME]] [OPTIONS...]\n\
Converts an ItemPMT file into a JSON item parameter table. A version\n\
option is required. Use --hex to make item codes in the output readable;\n\
however, this option also uses nonstandard JSON syntax - newserv can parse\n\
it, but many other JSON parsers can\'t. Expects compressed input (a .prs\n\
file) by default; use --decompressed if the input is not compressed.\n",
+[](phosg::Arguments& args) {
auto input_data = read_input_data(args);
if (!args.get<bool>("decompressed")) {
input_data = prs_decompress(input_data);
}
auto data = std::make_shared<string>(std::move(input_data));
auto pmt = ItemParameterTable::from_binary(data, get_cli_version(args, Version::BB_V4));
auto json = pmt->json();
uint32_t serialize_options = phosg::JSON::SerializeOption::FORMAT | phosg::JSON::SerializeOption::SORT_DICT_KEYS;
if (args.get<bool>("hex")) {
serialize_options |= phosg::JSON::SerializeOption::HEX_INTEGERS;
}
string json_data = json.serialize(serialize_options);
write_output_data(args, json_data.data(), json_data.size(), nullptr);
});
Action a_encode_item_parameter_table(
"encode-item-parameter-table", "\
encode-item-parameter-table [INPUT-FILENAME [OUTPUT-FILENAME]] [OPTIONS...]\n\
Converts a JSON item parameter table into an ItemPMT file compatible with\n\
the game client. A version option is required. By default the output will\n\
be compressed, as the client expects; use --decompressed to get\n\
uncompressed output.\n",
+[](phosg::Arguments& args) {
auto json = phosg::JSON::parse(read_input_data(args));
auto pmt = ItemParameterTable::from_json(json);
string data = pmt->serialize_binary(get_cli_version(args, Version::BB_V4));
if (!args.get<bool>("decompressed")) {
data = prs_compress_optimal(data);
}
write_output_data(args, data.data(), data.size(), nullptr);
});
Action a_describe_item(
"describe-item", "\
describe-item DATA-OR-DESCRIPTION\n\
+48 -50
View File
@@ -692,6 +692,7 @@ void send_guild_card_chunk_bb(shared_ptr<Client> c, size_t chunk_index) {
}
static bool is_battle_param_stream_file_for_blueballz(const string& filename) {
return (filename == "BattleParamEntry.dat") ||
(filename == "BattleParamEntry_on.dat") ||
@@ -701,31 +702,23 @@ static bool is_battle_param_stream_file_for_blueballz(const string& filename) {
(filename == "BattleParamEntry_ep4_on.dat");
}
static shared_ptr<const string> bb_stream_file_data_for_client(shared_ptr<Client> c, const string& filename) {
static string bb_stream_file_data_for_client(shared_ptr<Client> c) {
auto s = c->require_server_state();
auto raw_data = s->bb_stream_files_cache->get_or_load("system/blueburst/" + filename).file->data;
int64_t effective_blueballz_hp_scale_tier = (c->selected_blueballz_tier >= 0)
? c->selected_blueballz_tier
: s->blueballz_enemy_hp_scale_tier;
if (!is_battle_param_stream_file_for_blueballz(filename) || (effective_blueballz_hp_scale_tier < 0)) {
return raw_data;
if (effective_blueballz_hp_scale_tier < 0) {
return s->bb_stream_file->data;
}
effective_blueballz_hp_scale_tier = std::min<int64_t>(
s->blueballz_max_tier,
effective_blueballz_hp_scale_tier);
string scaled_data = *raw_data;
if (scaled_data.size() < sizeof(BattleParamsIndex::Table)) {
c->log.warning_f("Blueballz enemy HP scaling skipped for {}; file is too small", filename);
return raw_data;
}
string scaled_data = s->bb_stream_file->data;
double mult = 1.0 + (static_cast<double>(effective_blueballz_hp_scale_tier) * 0.25);
auto* table = reinterpret_cast<BattleParamsIndex::Table*>(scaled_data.data());
size_t ultimate_index = static_cast<size_t>(Difficulty::ULTIMATE);
auto scale_u16 = [mult](uint32_t v) -> uint16_t {
@@ -742,28 +735,39 @@ static shared_ptr<const string> bb_stream_file_data_for_client(shared_ptr<Client
return static_cast<uint16_t>(scaled);
};
for (size_t z = 0; z < 0x60; z++) {
auto& stats = table->stats[ultimate_index][z];
stats.char_stats.hp = scale_u16(stats.char_stats.hp);
for (const auto& sf_entry : s->bb_stream_file->entries) {
if (!is_battle_param_stream_file_for_blueballz(sf_entry.filename)) {
continue;
}
if ((sf_entry.offset > scaled_data.size()) ||
(sf_entry.size > (scaled_data.size() - sf_entry.offset))) {
c->log.warning_f("Blueballz enemy HP scaling skipped for {}; invalid stream-file range",
sf_entry.filename);
continue;
}
if (sf_entry.size < sizeof(BattleParamsIndex::Table)) {
c->log.warning_f("Blueballz enemy HP scaling skipped for {}; file is too small",
sf_entry.filename);
continue;
}
auto* table = reinterpret_cast<BattleParamsIndex::Table*>(
scaled_data.data() + sf_entry.offset);
for (size_t z = 0; z < 0x60; z++) {
auto& stats = table->stats[ultimate_index][z];
stats.char_stats.hp = scale_u16(stats.char_stats.hp);
}
c->log.info_f("Blueballz enemy HP scaling: serving {} with tier {} ({:g}x Ultimate HP)",
sf_entry.filename, effective_blueballz_hp_scale_tier, mult);
}
c->log.info_f("Blueballz enemy HP scaling: serving {} with tier {} ({:g}x Ultimate HP)",
filename, effective_blueballz_hp_scale_tier, mult);
return make_shared<string>(std::move(scaled_data));
return scaled_data;
}
static const vector<string> stream_file_entries = {
"ItemMagEdit.prs",
"ItemPMT.prs",
"BattleParamEntry.dat",
"BattleParamEntry_on.dat",
"BattleParamEntry_lab.dat",
"BattleParamEntry_lab_on.dat",
"BattleParamEntry_ep4.dat",
"BattleParamEntry_ep4_on.dat",
"PlyLevelTbl.prs",
};
void send_stream_file_index_bb(shared_ptr<Client> c) {
auto s = c->require_server_state();
@@ -771,15 +775,19 @@ void send_stream_file_index_bb(shared_ptr<Client> c) {
c->log.info_f("PSO Peeps BBZ stream debug: send_stream_file_index_bb called");
vector<S_StreamFileIndexEntry_BB_01EB> entries;
size_t offset = 0;
for (const string& filename : stream_file_entries) {
auto file_data = bb_stream_file_data_for_client(c, filename);
string contents = bb_stream_file_data_for_client(c);
for (const auto& sf_entry : s->bb_stream_file->entries) {
if ((sf_entry.offset > contents.size()) ||
(sf_entry.size > (contents.size() - sf_entry.offset))) {
throw runtime_error("invalid BB stream file entry range");
}
auto& e = entries.emplace_back();
e.size = file_data->size();
e.checksum = crc32(file_data->data(), e.size);
e.offset = offset;
e.filename.encode(filename);
offset += e.size;
e.size = sf_entry.size;
e.checksum = crc32(contents.data() + sf_entry.offset, e.size);
e.offset = sf_entry.offset;
e.filename.encode(sf_entry.filename);
}
send_command_vt(c, 0x01EB, entries.size(), entries);
}
@@ -787,18 +795,7 @@ void send_stream_file_index_bb(shared_ptr<Client> c) {
void send_stream_file_chunk_bb(shared_ptr<Client> c, uint32_t chunk_index) {
auto s = c->require_server_state();
c->log.info_f("PSO Peeps BBZ stream debug: send_stream_file_chunk_bb called for chunk {}", chunk_index);
size_t total_bytes = 0;
for (const auto& name : stream_file_entries) {
total_bytes += bb_stream_file_data_for_client(c, name)->size();
}
string contents;
contents.reserve(total_bytes);
for (const auto& name : stream_file_entries) {
contents += *bb_stream_file_data_for_client(c, name);
}
string contents = bb_stream_file_data_for_client(c);
S_StreamFileChunk_BB_02EB chunk_cmd;
chunk_cmd.chunk_index = chunk_index;
@@ -809,6 +806,7 @@ void send_stream_file_chunk_bb(shared_ptr<Client> c, uint32_t chunk_index) {
size_t bytes = min<size_t>(contents.size() - offset, sizeof(chunk_cmd.data));
chunk_cmd.data.assign_range(reinterpret_cast<const uint8_t*>(contents.data() + offset), bytes, 0);
size_t cmd_size = offsetof(S_StreamFileChunk_BB_02EB, data) + bytes;
cmd_size = (cmd_size + 3) & ~3;
send_command(c, 0x02EB, 0x00000000, &chunk_cmd, cmd_size);
+36 -6
View File
@@ -79,7 +79,6 @@ ServerState::ServerState(const string& config_filename, bool is_replay)
config_filename(config_filename),
is_replay(is_replay),
thread_pool(make_unique<asio::thread_pool>()),
bb_stream_files_cache(new FileContentsCache(3600000000ULL)),
bb_system_cache(new FileContentsCache(3600000000ULL)),
gba_files_cache(new FileContentsCache(3600000000ULL)) {}
@@ -1847,8 +1846,6 @@ vector<shared_ptr<const SuperMap>> ServerState::supermaps_for_variations(
}
void ServerState::clear_file_caches() {
config_log.info_f("Clearing BB stream file cache");
this->bb_stream_files_cache.reset(new FileContentsCache(3600000000ULL));
config_log.info_f("Clearing BB system cache");
this->bb_system_cache.reset(new FileContentsCache(3600000000ULL));
config_log.info_f("Clearing GBA file cache");
@@ -2164,10 +2161,9 @@ void ServerState::load_item_definitions() {
config_log.info_f("Loading item definition tables");
for (size_t v_s = NUM_PATCH_VERSIONS; v_s < NUM_VERSIONS; v_s++) {
Version v = static_cast<Version>(v_s);
string path = std::format("system/item-tables/ItemPMT-{}.prs", file_path_token_for_version(v));
string path = std::format("system/item-tables/item-parameter-table-{}.json", file_path_token_for_version(v));
config_log.debug_f("Loading item definition table {}", path);
auto data = make_shared<string>(prs_decompress(phosg::load_file(path)));
new_item_parameter_tables[v_s] = ItemParameterTable::create(data, v);
new_item_parameter_tables[v_s] = ItemParameterTable::from_json(phosg::JSON::parse(phosg::load_file(path)));
}
auto json = phosg::JSON::parse(phosg::load_file("system/item-tables/translation-table.json"));
@@ -2243,6 +2239,39 @@ void ServerState::load_dol_files() {
this->dol_file_index = make_shared<DOLFileIndex>("system/dol");
}
void ServerState::generate_bb_stream_file() {
config_log.info_f("Generating BB stream file");
auto sf = std::make_shared<BBStreamFile>();
auto add_file = [&](const std::string& filename, std::string&& file_data = "") -> void {
if (file_data.empty()) {
file_data = phosg::load_file("system/blueburst/" + filename);
}
auto& e = sf->entries.emplace_back();
e.size = file_data.size();
e.checksum = phosg::crc32(file_data.data(), file_data.size());
e.offset = sf->data.size();
e.filename = filename;
sf->data += file_data;
config_log.debug_f(
"[BBStreamFile] Added file {} at offset {:08X} ({:08X} bytes) with checksum {:08X}; total size is now {:08X}",
filename, e.offset, e.size, e.checksum, sf->data.size());
};
add_file("BattleParamEntry.dat");
add_file("BattleParamEntry_on.dat");
add_file("BattleParamEntry_lab.dat");
add_file("BattleParamEntry_lab_on.dat");
add_file("BattleParamEntry_ep4.dat");
add_file("BattleParamEntry_ep4_on.dat");
add_file("PlyLevelTbl.prs");
add_file("ItemMagEdit.prs");
auto pmt = this->item_parameter_table(Version::BB_V4);
add_file("ItemPMT.prs", prs_compress_optimal(pmt->serialize_binary(Version::BB_V4)));
this->bb_stream_file = sf;
}
void ServerState::create_default_lobbies() {
if (this->default_lobbies_created) {
return;
@@ -2322,6 +2351,7 @@ void ServerState::load_all(bool enable_thread_pool) {
this->load_config_late();
this->load_teams();
this->load_quest_index();
this->generate_bb_stream_file();
}
void ServerState::disconnect_all_banned_clients() {
+13 -1
View File
@@ -65,6 +65,17 @@ struct CheatFlags {
explicit CheatFlags(const phosg::JSON& json);
};
struct BBStreamFile {
struct Entry {
uint32_t offset;
uint32_t size;
uint32_t checksum; // crc32
std::string filename;
};
std::vector<Entry> entries;
std::string data;
};
struct ServerState : public std::enable_shared_from_this<ServerState> {
enum class RunShellBehavior {
DEFAULT = 0,
@@ -188,7 +199,7 @@ struct ServerState : public std::enable_shared_from_this<ServerState> {
std::unordered_map<uint64_t, std::shared_ptr<const SuperMap>> supermap_for_source_hash_sum;
std::unordered_map<uint32_t, std::shared_ptr<const SuperMap>> supermap_for_free_play_key;
std::shared_ptr<const RoomLayoutIndex> room_layout_index;
std::shared_ptr<FileContentsCache> bb_stream_files_cache;
std::shared_ptr<const BBStreamFile> bb_stream_file;
std::shared_ptr<FileContentsCache> bb_system_cache;
std::shared_ptr<FileContentsCache> gba_files_cache;
std::shared_ptr<const DOLFileIndex> dol_file_index;
@@ -450,6 +461,7 @@ struct ServerState : public std::enable_shared_from_this<ServerState> {
void load_quest_index(bool raise_on_any_failure = false);
void compile_functions(bool raise_on_any_failure = false);
void load_dol_files();
void generate_bb_stream_file();
void load_all(bool enable_thread_pool);
+3
View File
@@ -211,14 +211,17 @@ ShellCommand c_reload(
args.s->load_set_data_tables();
} else if (type == "battle-params") {
args.s->load_battle_params();
args.s->generate_bb_stream_file();
} else if (type == "level-tables") {
args.s->load_level_tables();
args.s->generate_bb_stream_file();
} else if (type == "text-index") {
args.s->load_text_index();
} else if (type == "word-select") {
args.s->load_word_select_table();
} else if (type == "item-definitions") {
args.s->load_item_definitions();
args.s->generate_bb_stream_file();
} else if (type == "item-name-index") {
args.s->load_item_name_indexes();
} else if (type == "drop-tables") {
+3 -3
View File
@@ -478,8 +478,8 @@ Language language_for_name(const string& name) {
}
const vector<string> tech_id_to_name = {
"foie", "gifoie", "rafoie", "barta", "gibarta", "rabarta", "zonde", "gizonde", "razonde", "grants", "deband",
"jellen", "zalure", "shifta", "ryuker", "resta", "anti", "reverser", "megid"};
"Foie", "Gifoie", "Rafoie", "Barta", "Gibarta", "Rabarta", "Zonde", "Gizonde", "Razonde", "Grants", "Deband",
"Jellen", "Zalure", "Shifta", "Ryuker", "Resta", "Anti", "Reverser", "Megid"};
const unordered_map<string, uint8_t> name_to_tech_id = {
{"foie", 0}, {"gifoie", 1}, {"rafoie", 2}, {"barta", 3}, {"gibarta", 4}, {"rabarta", 5}, {"zonde", 6},
@@ -497,7 +497,7 @@ const string& name_for_technique(uint8_t tech) {
uint8_t technique_for_name(const string& name) {
try {
return name_to_tech_id.at(name);
return name_to_tech_id.at(phosg::tolower(name));
} catch (const out_of_range&) {
}
try {
-1
View File
@@ -1 +0,0 @@
../item-tables/ItemPMT-bb-v4.prs
@@ -1,6 +1,8 @@
.meta name="Kill count fix"
.meta description="Fixes client-side\nkill counts when\nmultiple enemies are\nkilled on the same\nframe"
.versions 59NJ 59NL
entry_ptr:
reloc0:
.offsetof start
@@ -9,9 +11,9 @@ start:
.data 0x005E32C8
.data <VERS 0x005E32A4 0x005E32C8>
.deltaof TItemUnitUnsealable_count_kill, TItemUnitUnsealable_count_kill_end
.address 0x005E32C8
.address <VERS 0x005E32A4 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
@@ -29,14 +31,14 @@ TItemUnitUnsealable_count_kill_skip_update:
setae dh
shl edx, 1
or dword [ecx + 0xDC], edx
jmp 0x005E2C34
jmp <VERS 0x005E2C10 0x005E2C34>
TItemUnitUnsealable_count_kill_end:
.data 0x005F3EFC
.data <VERS 0x005F3E94 0x005F3EFC>
.deltaof TItemWeapon_LameDArgent_count_kill, TItemWeapon_LameDArgent_count_kill_end
.address 0x005F3EFC
.address <VERS 0x005F3E94 0x005F3EFC>
TItemWeapon_LameDArgent_count_kill:
mov eax, [ecx + 0xF8]
movsx eax, word [eax + 0x11A]
@@ -59,9 +61,9 @@ TItemWeapon_LameDArgent_count_kill_end:
.data 0x005FCA74
.data <VERS 0x005FC95C 0x005FCA74>
.deltaof TItemWeapon_SealedJSword_count_kill, TItemWeapon_SealedJSword_count_kill_end
.address 0x005FCA74
.address <VERS 0x005FC95C 0x005FCA74>
TItemWeapon_SealedJSword_count_kill:
mov eax, [ecx + 0xF8]
movsx eax, word [eax + 0x11A]
@@ -0,0 +1,624 @@
# This patch changes the number of BB character save slots from 4 to any number
# up to 127.
# This patch is for documentation purposes only; it works when used as a server
# patch via newserv, but is decidedly inconvenient to use via this method. This
# is because it affects logic that runs before any patches can be sent by the
# server, so the player has to connect once to get the patch, then disconnect
# and connect again to use the additional slots.
# As written, this patch changes the slot count from 4 to 12. To use a
# different slot count, first compute the following values:
# slot count = your desired number of player slots (must be >= 4, <= 127)
# total file size = (slot count * 0x2EA4) + 0x14
# bgm_test_songs_unlocked offset = total file size - 0x10
# save_count offset = total file size - 8
# round2_seed offset = total file size - 4
# Then, for each of the above, search for the string to the left of the = sign
# and change the values used in all of the matching lines.
.meta name="More save slots"
.meta description=""
.meta hide_from_patches_menu
entry_ptr:
reloc0:
.offsetof start
# Include a few functions first
write_call_to_code:
.include WriteCallToCode-59NJ
memcpy:
.include CopyData
ret
start:
# Apply all necessary patches
call apply_enable_scroll_patch
call apply_fix_scroll_patch1
call apply_fix_scroll_patch2
call apply_fix_file_index
call apply_preview_window_fix
call apply_static_patches
# Rewrite the existing char file regions to have the appropriate size; this
# must be done after the patches are applied because we call the checksum
# function, which is patched by one of the above calls
call update_existing_char_file_list
jmp update_existing_char_file_list_memcard
apply_enable_scroll_patch:
# This patch enables scrolling behavior within the character list
push -5 # Jump size (negative = jmp instead of call)
push 0x00413B77 # Jump address
call get_code_size_for_enable_scroll
.deltaof enable_scroll_start, enable_scroll_end
get_code_size_for_enable_scroll:
pop eax
push dword [eax]
call enable_scroll_end
enable_scroll_start:
mov eax, dword ptr [edi + 0x28] # cursor = char_select_menu->cursor_obj (TAdSelectCurGC*)
or dword [eax + 0x01F8], 3 # cursor->flags |= 3 # Enable scrolling
mov eax, [0x00A38BD0] # scroll_bar = TAdScrollBarXb_objs[0]
mov ecx, [eax + 0xEC] # ecx = scroll_bar->client_id
imul ecx, ecx, 0x24
# Set up scroll bar graphics (in struct at scroll_bar + 0x1C)
mov dword [eax + ecx + 0x1C], 0x439D0000
mov dword [eax + ecx + 0x20], 0x43360000
mov dword [eax + ecx + 0x24], 0x439D0000
mov dword [eax + ecx + 0x28], 0x4392AB85
mov dword [eax + ecx + 0x2C], 0x40400000
mov dword [eax + ecx + 0x30], 0x425EA3D7
mov dword [eax + ecx + 0x34], 0x00000008
mov dword [eax + ecx + 0x38], 0x00000000
mov dword [eax + ecx + 0x3C], 0x00000000
or dword [eax + 0xF0], 1 # scroll_bar->flags |= 1
mov ecx, [eax + 0xEC]
shl ecx, 4
mov dword [eax + ecx + 0xAC], 0 # scroll_bar->selection_state[client_id].scroll_offset = 0
mov dword [eax + ecx + 0xB0], 0 # scroll_bar->selection_state[client_id].selected_index = 0
mov dword [eax + ecx + 0xB4], 4 # scroll_bar->selection_state[client_id].num_items_in_view = 4
mov dword [eax + ecx + 0xB8], 0x0B # scroll_bar->selection_state[client_id].last_item_index = (slot count - 1)
pop edi
ret
enable_scroll_end:
call write_call_to_code
ret
apply_fix_scroll_patch1:
# This patch fixes character selection cursor object so it will take the
# scroll offset into account
push 6 # Call size
push 0x00413C30 # Call address
call get_code_size_for_fix_scroll_patch1
.deltaof fix_scroll_patch1_start, fix_scroll_patch1_end
get_code_size_for_fix_scroll_patch1:
pop eax
push dword [eax]
call fix_scroll_patch1_end
fix_scroll_patch1_start:
mov edx, [edi + 0x28] # cursor = this->ad_select_cur_obj (TAdSelectCurGC*)
mov ebp, [edx + 0x44] # ebp = cursor->selected_index_within_view
mov eax, [0x00A38BD0] # scroll_bar = TAdScrollBarXb_objs[0]
add ebp, [eax + 0xAC] # ebp += scroll_bar->selection_state[0].scroll_offset
ret
fix_scroll_patch1_end:
call write_call_to_code
ret
apply_fix_scroll_patch2:
# This patch changes the TAdSinglePlyChrSelectGC::selected_index_within_view
# to be the selected character's absolute index (including scroll_offset),
# not the index only within the displayed four characters
push 6 # Call size
push 0x00413CD0 # Call address
call get_code_size_for_fix_scroll_patch2
.deltaof fix_scroll_patch2_start, fix_scroll_patch2_end
get_code_size_for_fix_scroll_patch2:
pop eax
push dword [eax]
call fix_scroll_patch2_end
fix_scroll_patch2_start:
mov eax, [0x00A38BD0] # scroll_bar = TAdScrollBarXb_objs[0]
mov eax, [eax + 0xAC] # eax = scroll_bar->selection_state[0].scroll_offset
mov edx, [edi + 0x28] # cursor = this->ad_select_cur_obj (TAdSelectCurGC*)
add eax, [edx + 0x44] # eax += cursor->selected_index_within_view
ret
fix_scroll_patch2_end:
call write_call_to_code
ret
apply_fix_file_index:
# This patch fixes the character file indexing so it will account for the
# scroll position
push 5 # Call size
push 0x00413CE8 # Call address
call get_code_size_for_selection_index_fix2
.deltaof selection_index_fix2_start, selection_index_fix2_end
get_code_size_for_selection_index_fix2:
pop eax
push dword [eax]
call selection_index_fix2_end
selection_index_fix2_start:
mov eax, [0x00A38BD0]
mov eax, [eax + 0xAC] # eax = TAdScrollBarXb_objs[0]->selection_state[0].scroll_offset
add ebp, eax # arg0 += eax
mov [esp + 4], ebp
mov eax, 0x006C1ABC
jmp eax # set_current_char_slot
selection_index_fix2_end:
call write_call_to_code
ret
apply_preview_window_fix:
# This patch fixes the preview display so it will show the correct section
# ID, level, etc.
push 5 # Call size
push 0x0040216C # Call address
call get_code_size_for_preview_window_fix
.deltaof preview_window_fix_start, preview_window_fix_end
get_code_size_for_preview_window_fix:
pop eax
push dword [eax]
call preview_window_fix_end
preview_window_fix_start:
mov eax, [0x00A38BD0] # scroll_bar = TAdScrollBarXb_objs[0]
mov eax, [eax + 0xAC] # eax = scroll_bar->selection_state[0].scroll_offset
add [esp + 4], eax
mov eax, 0x006C4514 # get_player_preview_info
jmp eax
preview_window_fix_end:
# This patch applies in two places, so push the second set of args now, then
# apply it twice
push 5 # Call size
push 0x00401842 # Call address
push dword [esp + 0x10] # Code size
push dword [esp + 0x10] # Code address
call write_call_to_code
call write_call_to_code
ret
apply_static_patches:
.include WriteCodeBlocksBB
# These patches change various places where the character data size and slot
# count are referenced
.data 0x00475294
.data 0x00000001
.binary 0C # slot count; TDataProtocol::handle_E5
.data 0x0047534B
.data 0x00000001
.binary 0C # slot count; import_player_preview
.data 0x004786D1
.data 0x00000001
.binary 0C # slot count; TDataProtocol::handle_E4
.data 0x00482559
.data 0x00000004
.data 0x00022FC4 # total file size
.data 0x006C17FB
.data 0x00000001
.binary 0C # slot count
.data 0x006C1D07
.data 0x00000004
.data 0x00022FC4 # total file size
.data 0x006C1D3A
.data 0x00000001
.binary 0C # slot count
.data 0x006C1D58
.data 0x00000004
.data 0x00022FC4 # total file size
.data 0x006C1E13
.data 0x00000004
.data 0x00022FC4 # total file size
.data 0x006C226A
.data 0x00000004
.data 0x00022FC4 # total file size
.data 0x006C22A9
.data 0x00000001
.binary 0C # slot count
.data 0x006C22CA
.data 0x00000004
.data 0x00022FC4 # total file size
.data 0x006C22DA
.data 0x00000004
.data 0x00022FC4 # total file size
.data 0x006C2517
.data 0x00000004
.data 0x00022FC4 # total file size
.data 0x006C267F
.data 0x00000004
.data 0x00022FBC # save_count offset
.data 0x006C2689
.data 0x00000004
.data 0x00022FBC # save_count offset
.data 0x006C272B
.data 0x00000004
.data 0x00022FBC # save_count offset
.data 0x006C2741
.data 0x00000004
.data 0x00022FC0 # round2_seed offset
.data 0x006C27CF
.data 0x00000004
.data 0x00022FC4 # total file size
.data 0x006C28A8
.data 0x00000004
.data 0x00022FC4 # total file size
.data 0x006C314F
.data 0x00000004
.data 0x00022FC4 # total file size
.data 0x006C357B
.data 0x00000004
.data 0x00022FC4 # total file size
.data 0x006C35BA
.data 0x00000001
.binary 0C # slot count
.data 0x006C35E6
.data 0x00000004
.data 0x00022FC4 # total file size
.data 0x006C35F3
.data 0x00000004
.data 0x00022FC4 # total file size
.data 0x006C360E
.data 0x00000004
.data 0x00022FBC # save_count offset
.data 0x006C3617
.data 0x00000004
.data 0x00022FBC # save_count offset
.data 0x006C371C
.data 0x00000004
.data 0x00022FC4 # total file size
.data 0x006C3B5A
.data 0x00000004
.data 0x00022FC4 # total file size
.data 0x006C424D
.data 0x00000004
.data 0x00022FC4 # total file size
.data 0x006C4833
.data 0x00000004
.data 0x00022FC4 # total file size
.data 0x006C486A
.data 0x00000001
.binary 0C # slot count
.data 0x006C49A6
.data 0x00000004
.data 0x00022FC4 # total file size
.data 0x006C49DD
.data 0x00000001
.binary 0C # slot count
.data 0x006C4AC5
.data 0x00000004
.data 0x00022FC4 # total file size
.data 0x006C4AFE
.data 0x00000001
.binary 0C # slot count
.data 0x006C4CDE
.data 0x00000004
.data 0x00022FC4 # total file size
.data 0x006C4D15
.data 0x00000001
.binary 0C # slot count
.data 0x006C4DFD
.data 0x00000004
.data 0x00022FC4 # total file size
.data 0x006C4E36
.data 0x00000001
.binary 0C # slot count
.data 0x006C4F9C
.data 0x00000004
.data 0x00022FC4 # total file size
.data 0x006C4FD7
.data 0x00000001
.binary 0C # slot count
.data 0x006C51C5
.data 0x00000004
.data 0x00022FC4 # total file size
.data 0x006C5201
.data 0x00000001
.binary 0C # slot count
.data 0x006C5376
.data 0x00000004
.data 0x00022FC4 # total file size
.data 0x006C53B0
.data 0x00000001
.binary 0C # slot count
.data 0x006C5545
.data 0x00000004
.data 0x00022FC4 # total file size
.data 0x006C5581
.data 0x00000001
.binary 0C # slot count
.data 0x006C56F6
.data 0x00000004
.data 0x00022FC4 # total file size
.data 0x006C5730
.data 0x00000001
.binary 0C # slot count
.data 0x006C58B6
.data 0x00000004
.data 0x00022FC4 # total file size
.data 0x006C58F0
.data 0x00000001
.binary 0C # slot count
.data 0x006C5A85
.data 0x00000004
.data 0x00022FC4 # total file size
.data 0x006C5AC1
.data 0x00000001
.binary 0C # slot count
.data 0x006C5BB2
.data 0x00000004
.data 0x00022FC4 # total file size
.data 0x006C5BEC
.data 0x00000001
.binary 0C # slot count
.data 0x006C5D72
.data 0x00000004
.data 0x00022FC4 # total file size
.data 0x006C5DAC
.data 0x00000001
.binary 0C # slot count
.data 0x006C5F32
.data 0x00000004
.data 0x00022FC4 # total file size
.data 0x006C5F6C
.data 0x00000001
.binary 0C # slot count
.data 0x006C60F2
.data 0x00000004
.data 0x00022FC4 # total file size
.data 0x006C612C
.data 0x00000001
.binary 0C # slot count
.data 0x006C6346
.data 0x00000004
.data 0x00022FC4 # total file size
.data 0x006C6381
.data 0x00000001
.binary 0C # slot count
.data 0x006C6505
.data 0x00000004
.data 0x00022FC4 # total file size
.data 0x006C6541
.data 0x00000001
.binary 0C # slot count
.data 0x006C6632
.data 0x00000004
.data 0x00022FC4 # total file size
.data 0x006C666C
.data 0x00000001
.binary 0C # slot count
.data 0x006C67F2
.data 0x00000004
.data 0x00022FC4 # total file size
.data 0x006C682C
.data 0x00000001
.binary 0C # slot count
.data 0x006C69B2
.data 0x00000004
.data 0x00022FC4 # total file size
.data 0x006C69EC
.data 0x00000001
.binary 0C # slot count
.data 0x006C6B87
.data 0x00000004
.data 0x00022FC4 # total file size
.data 0x006C6BB8
.data 0x00000004
.data 0x0000005D # memcard block count
.data 0x006C6C3A
.data 0x00000004
.data 0x00022FC4 # total file size
.data 0x006C6C74
.data 0x00000001
.binary 0C # slot count
.data 0x006C6E82
.data 0x00000004
.data 0x00022FC4 # total file size
.data 0x006C6EBC
.data 0x00000001
.binary 0C # slot count
.data 0x006C70B9
.data 0x00000004
.data 0x00022FC4 # total file size
.data 0x006C70F3
.data 0x00000001
.binary 0C # slot count
.data 0x006C7A46
.data 0x00000004
.data 0x00022FC4 # total file size
.data 0x006C7D66
.data 0x00000004
.data 0x00022FC4 # total file size
.data 0x006C7D7C
.data 0x00000001
.binary 0C # slot count
.data 0x006C7DC0
.data 0x00000004
.data 0x00022FC4 # total file size
.data 0x0077CC72
.data 0x00000004
.data 0x00022FB4 # bgm_test_songs_unlocked offset
# Signature check on all save files (rewritten as loop)
.data 0x006C1C69
.deltaof sig_check_begin, sig_check_end
sig_check_begin:
mov edx, 0xC87ED5B1 # Expected signature value
add eax, 0x04E8 # &char_file_list->chars[0].part2.signature
mov ecx, 0x0C # slot count
again:
cmp dword [eax], 0 # signature == 0 (no char in slot)
je sig_ok
cmp dword [eax], edx # signature == expected value
jne sig_bad
sig_ok:
add eax, 0x2EA4 # Advance to next slot
dec ecx
jnz again
xor eax, eax # All signatures OK (eax = 0)
jmp sig_check_end
sig_bad:
xor eax, eax # Bad signature (eax = 1)
inc eax
jmp sig_check_end
.binary CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
sig_check_end: # 006C1CB2
# Send slot count in E3 command
.data 0x0046EC10 # TDataProtocol::send_E3_for_index
.deltaof send_slot_count_in_E3_begin, send_slot_count_in_E3_end
send_slot_count_in_E3_begin:
# ecx = this (TDataProtocol*)
# [esp + 4] = slot_index
push 0
push dword [esp + 8] # slot_index
push 0x0C # slot count
push 0x00E30010
mov eax, esp
push 0x10
push eax
mov eax, [ecx]
call [eax + 0x20] # this->send_command(&cmd, 0x10) // ret 8
add esp, 8
mov eax, 0x006C1ABC
call eax # set_current_char_slot(slot_index) // ret 0
add esp, 8
ret 4
send_slot_count_in_E3_end:
# Show slot number in each menu item
.data 0x00401D57
.deltaof show_slot_number_begin, show_slot_number_end
show_slot_number_begin:
# Original call (sprintf(line_buf, "LV%d", preview_info->visual.disp.level + 1))
lea edx, [esp + 0x02C4]
mov ebx, [ebx + 8]
inc ebx
push ebx
mov ecx, esi
push edx
mov eax, 0x00402604
call eax
# Find the end of the string
lea eax, [esp + 0x02C4]
show_slot_number_strend_again:
cmp word [eax], 0
je show_slot_number_strend_done
add eax, 2
jmp show_slot_number_strend_again
show_slot_number_strend_done:
# Format the slot number and append it to the string
mov ecx, [0x00A38BD0] # scroll_bar = TAdScrollBarXb_objs[0]
mov ecx, [ecx + 0xAC] # ecx = scroll_bar->selection_state[0].scroll_offset
lea ecx, [ecx + ebp + 1]
push ecx # Slot number (scroll_offset + z)
call get_show_slot_number_suffix_fmt
.binary 20002800230025006400290020000000 # L" (#%d) "
get_show_slot_number_suffix_fmt:
push eax # Destination buffer
mov eax, 0x00835578 # _swprintf
call eax
add esp, 0x0C
jmp show_slot_number_end
.zero 0x96
show_slot_number_end: # 00401E4D
# End static patches
.data 0x00000000
.data 0x00000000
update_existing_char_file_list:
# Replace the existing character list with an appropriately-longer one. This
# part does not need to be done if the patch is applied statically to the
# executable; this is only necessary when used as a server patch because the
# character list is already allocated at the time the patch is applied.
push 0x00022FC4 # total file size
mov eax, 0x00835915 # operator_new
call eax
add esp, 4
mov edx, [0x00A939C4] # edx = old char_file_list
mov [0x00A939C4], eax
mov ecx, [edx + 0xBA94] # Copy bgm_test_songs_unlocked_high to new file
mov [eax + 0x00022FB4], ecx
mov ecx, [edx + 0xBA98] # Copy bgm_test_songs_unlocked_low to new file
mov [eax + 0x00022FB8], ecx
mov ecx, [edx + 0xBA9C] # Copy save_count to new file
mov [eax + 0x00022FBC], ecx
mov ecx, [edx + 0xBAA0] # Copy round2_seed to new file
mov [eax + 0x00022FC0], ecx
add eax, 4
add edx, 4
mov ecx, 0xBA90
call memcpy # Copy the existing 4 characters over
mov eax, [0x00A939C4]
add eax, 0xBA94
mov ecx, 4
clear_next_char:
cmp ecx, 0x0C # slot count
jge clear_next_char_done
lea edx, [eax + 0x2EA4] # edx = ptr to next char (or footer)
clear_next_char_write_again:
mov dword [eax], 0
add eax, 4
cmp eax, edx
jl clear_next_char_write_again
clear_next_char_done:
# Call eh_vector_constructor_iterator(
# &char_file_list.chars[4],
# sizeof(char_file_list.chars[0]),
# countof(char_file_list.chars) - 4,
# PSOCharacterFile::init,
# PSOCharacterFile::destroy)
push 0x006C197C # PSOCharacterFile::destroy
push 0x006C182C # PSOCharacterFile::init
push 0x08 # slot count - 4
push 0x2EA4 # sizeof(PSOCharacterFile)
mov eax, [0x00A939C4]
add eax, 0xBA94
push eax
mov eax, 0x00835E86
call eax
# Fix the file's checksum
mov eax, [0x00A939C4]
mov ecx, 0x006C2738
jmp ecx # PSOBBCharacterFileList::checksum(char_file_list)
update_existing_char_file_list_memcard:
# Allocate a new memory card file area and copy the data there too. It seems
# Sega didn't fully strip out the local saving code from PSOBB; instead, they
# just made it write to a heap-allocated buffer. Since the file is much
# bigger now, we also have to make that heap-allocated buffer larger. We add
# a few "blocks" on the end, since the original code in the game does that
# too, but it's probably not strictly necessary.
# Like the above, this part is not necessary if this patch is statically
# applied to the executable.
mov eax, 0x00022FC4 # total file size
add eax, 0x0000FFFF
and eax, 0xFFFFC000
push eax
mov eax, 0x0084F258
call eax # malloc10(total file size)
add esp, 4
mov [0x00A939AC], eax
mov edx, [0x00A939C4]
mov ecx, 0x00022FC4 # total file size
jmp memcpy
@@ -0,0 +1,103 @@
# This patch causes the client not to generate its own EXP text and instead use
# the EXP values generated by the server when showing the purple text for enemy
# deaths. This makes EXP gained via EXP share visible, as well as makes
# fractional EXP multiplers (in config.json) display properly.
.meta name="Server EXP display"
.meta description=""
.meta hide_from_patches_menu
entry_ptr:
reloc0:
.offsetof start
start:
call install_hook
call apply_static_patches
ret
install_hook:
pop ecx
push 0 # Write address instead of a call/jmp opcode
push 0x00A0DC54
call get_code_size
.deltaof handle_6xBF_start, handle_6xBF_end
get_code_size:
pop eax
push dword [eax]
call handle_6xBF_end
handle_6xBF_start: # [std](G_6xBF* cmd @ [esp + 4]) -> void
mov edx, [esp + 4]
mov ecx, [0x00A9A074] # local_client_id
cmp [edx + 2], cx
jne skip_text
cmp byte [edx + 1], 3
jl skip_text
movzx eax, word [edx + 8] # cmd.from_enemy_id
cmp eax, 0x1000
jl skip_text
cmp eax, 0x1B50
jge skip_text
call get_enemy_entity
test eax, eax
jnz enemy_entity_ok
# Use player entity if enemy entity is already gone
mov eax, 0x0068D618
xchg eax, ecx
call ecx # eax = TObjPlayer::for_client_id(local_client_id); conveniently, this function preserves all regs except eax
enemy_entity_ok:
push 0x0000FFFF # entity_id; ignored by TFontSmallTask if not a player
push dword [edx + 4] # amount = cmd.amount
push 0x00976380 # prefix = L"EXP"
push 0x14
push 0x14
push 0xFFFF00FF # color (ARGB)
add eax, 0x300
push eax # position
mov eax, 0x0078B8E8
call eax # TFontSmallTask___new__(...)
add esp, 0x1C
skip_text:
mov eax, 0x0069292C # Original handle_6xBF
jmp eax # original_handle_6xBF(cmd)
get_enemy_entity:
.include GetEnemyEntity-59NJ
ret
handle_6xBF_end:
push ecx
.include WriteCallToCode-59NJ
apply_static_patches:
.include WriteCodeBlocksBB
.data 0x0078827D
.deltaof disable_kill_enemy_callsite_start, disable_kill_enemy_callsite_end
disable_kill_enemy_callsite_start:
nop
nop
nop
nop
nop
disable_kill_enemy_callsite_end:
.data 0x00777381
.deltaof disable_exp_steal_callsite_start, disable_exp_steal_callsite_end
disable_exp_steal_callsite_start:
add esp, 0x0C # Original function has `ret 0x0C`
nop
nop
disable_exp_steal_callsite_end:
.data 0x00000000
.data 0x00000000
@@ -10,6 +10,8 @@
.meta description=""
.meta hide_from_patches_menu
.versions 59NJ 59NL
entry_ptr:
reloc0:
.offsetof start
@@ -17,7 +19,7 @@ start:
.include WriteCodeBlocksBB
# Patch 1: rewrite item_is_stackable
.data 0x005C502C
.data <VERS 0x005C5020 0x005C502C>
.deltaof item_is_stackable_start, item_is_stackable_end
item_is_stackable_start:
@@ -32,7 +34,7 @@ item_is_stackable_start:
push eax
mov ecx, esp
.binary E8EC130100 # call max_stack_size_for_tool_start
.binary <VERS E8D8130100 E8EC130100> # call max_stack_size_for_tool_start
pop ecx
cmp eax, 1
jg return_1
@@ -48,7 +50,7 @@ return_1:
item_is_stackable_end:
# Patch 2: rewrite max_stack_size_for_tool
.data 0x005D6430
.data <VERS 0x005D6410 0x005D6430>
.deltaof max_stack_size_for_tool_start, max_stack_size_for_tool_end
max_stack_size_for_tool_start:
@@ -0,0 +1,146 @@
# Currently beta quality, map objects that fade like boxes, and Pioneer's
# background billboards and elevators still have regular draw distance.
# TODO: 90% of stuff is included, bring home the last 10%.
.meta name="Draw Distance"
.meta description="Extends the draw\ndistance of many\nobjects"
entry_ptr:
reloc0:
.offsetof start
write_call_func:
.include WriteCallToCode-59NJ
start:
mov eax, 0x41800000 # Environment clip distance mod 16.0f
mov [0x0097D198], eax # This affects mostly static map objects
mov [0x0097D19C], eax
mov [0x00097D1A0], eax
mov ax, 0x9090
mov [0x00689BC7], ax # Players draw distance 10000.0f always
mov eax, 0x41000000 # Use newly acquired skipped branch room
mov [0x00689BD1], eax # to store our float multiplier
call patch_func_1 # Floor items
call patch_func_2 # Whole bunch of stuff, including NPCs
call patch_func_3 # Duplicate function from above, reuse same hook
call patch_func_4 # TODO: Which objects this affects?
call patch_func_5 # TODO: This one too?
call patch_func_6 # TODO: And this one?
ret
# Floor items
patch_func_1:
pop ecx
push 8
push 0x005C525B
call get_code_size1
.deltaof patch_code1, patch_code_end1
get_code_size1:
pop eax
push dword [eax]
call patch_code_end1
patch_code1:
mov edx, [esp + 0x18]
fld st0, dword [0x00689BD1]
fld st0, dword [esp + 0x14]
fmulp st1, st0
ret
patch_code_end1:
push ecx
jmp write_call_func
# Whole bunch of stuff, including NPCs
patch_func_2:
pop ecx
push 9
push 0x007BB21E
call get_code_size2
.deltaof patch_code2, patch_code_end2
get_code_size2:
pop eax
push dword [eax]
call patch_code_end2
patch_code2:
test eax, 0x400
fld st0, dword [0x00689BD1]
fld st0, dword [esp + 0x2C]
fmulp st1, st0
ret
patch_code_end2:
push ecx
jmp write_call_func
# Duplicate function from above, reuse same hook
patch_func_3:
mov eax, dword [0x007BB21F]
add eax, 0x002A1C74
mov dword [0x00518843], eax
mov byte [0x00518842], 0xE8
mov dword [0x00518847], 0x90909090
ret
# TOComputerMachine01
patch_func_4:
pop ecx
push 7
push 0x00616FF4
call get_code_size4
.deltaof patch_code4, patch_code_end4
get_code_size4:
pop eax
push dword [eax]
call patch_code_end4
patch_code4:
lea edx, [edi + 0x38]
fld st0, dword [0x00689BD1]
fld st0, dword [esp + 0x14]
fmulp st1, st0
ret
patch_code_end4:
push ecx
jmp write_call_func
# TObjCamera
patch_func_5:
pop ecx
push 6
push 0x006439A8
call get_code_size5
.deltaof patch_code5, patch_code_end5
get_code_size5:
pop eax
push dword [eax]
call patch_code_end5
patch_code5:
fld st0, dword [0x00689BD1]
fld st0, dword [esp + 0x28]
fmulp st1, st0
fchs st0
ret
patch_code_end5:
push ecx
jmp write_call_func
# TODO: And this one?
patch_func_6:
pop ecx
push 6
push 0x0065B959
call get_code_size6
.deltaof patch_code6, patch_code_end6
get_code_size6:
pop eax
push dword [eax]
call patch_code_end6
patch_code6:
mov ebp, ecx
fld st0, dword [0x00689BD1]
fld st0, dword [esp + 0x30]
fmulp st1, st0
ret
patch_code_end6:
push ecx
jmp write_call_func
@@ -0,0 +1,258 @@
.meta name="DMC"
.meta description="Mitigates effects\nof enemy health\ndesync"
.meta client_flag="0x0000001000000000"
entry_ptr:
reloc0:
.offsetof start
write_call_to_code_multi:
.include WriteCallToCodeMulti-59NJ
write_address_of_code:
.include WriteAddressOfCode-59NJ
start:
# Replace 6x09 with 6xE4 in subcommand handler table
mov dword [0x00A0DC30], 0x000600E4 # subcommand=0xE4, flags=6
push 0x00A0DC34
call +4
.deltaof handle_6xE4_start, handle_6xE4_end
pop eax
push dword [eax]
call handle_6xE4_end
handle_6xE4_start: # (G_6xE4* cmd @ [esp + 4]) -> void
push ebx
push esi
push edi
test byte [0x00AA8DFC], 0x80
jz handle_6xE4_return
mov ebx, [esp + 0x10] # cmd
movzx eax, word [ebx + 2]
cmp eax, 0x1000
jl handle_6xE4_return
cmp eax, 0x1B50
jge handle_6xE4_return
movzx eax, word [ebx + 2]
.include GetEnemyEntity-59NJ # auto* ene = get_enemy_entity(cmd->header.entity_id);
push eax
movzx eax, word [ebx + 2]
and eax, 0x0FFF
imul eax, eax, 0x0C
add eax, [0x00AADE38] # eax = state_for_enemy(cmd->header.entity_id)
cmp dword [ebx + 0x0C], 0
jl handle_6xE4_not_proportional
mov cx, [ebx + 0x0A] # cmd->max_hp
sub cx, [eax + 0x06] # st.total_damage
movzx ecx, cx
xor edx, edx
cmp ecx, edx
cmovl ecx, edx
push ecx
fild st0, dword [esp] # current_hp = static_cast<float>(max<int32_t>(cmd->max_hp - st.total_damage, 0))
fld st0, dword [ebx + 0x0C]
fmulp st1, st0
fistp dword [esp], st0
mov ecx, dword [esp] # adjusted_hit_amount = static_cast<int16_t>(current_hp * cmd->factor)
add esp, 4
xor edx, edx
inc edx
cmp ecx, edx
cmovl ecx, edx
mov [ebx + 0x04], cx # cmd->hit_amount = min<int32_t>(1, adjusted_hit_amount)
handle_6xE4_not_proportional:
movzx edx, word [eax + 0x06] # st.total_damage
movsx esi, word [ebx + 0x04] # cmd->hit_amount
movzx edi, word [ebx + 0x0A] # cmd->max_hp
add edx, esi # st.total_damage + cmd->hit_amount
cmp edx, edi
jl handle_6xE4_damage_less_than_max_hp
mov [eax + 0x06], di # st.total_damage = cmd->max_hp;
mov edx, [eax]
test edx, 0x800
jnz handle_6xE4_return_pop_ene
or edx, 0x800
mov [eax], edx
cmp dword [esp], 0
je handle_6xE4_return_pop_ene
push edx # out_cmd.flags
sub esp, 8
mov word [esp], 0x030A # out_cmd.header.{subcommand,size}
mov si, [ebx + 2]
mov [esp + 2], si # out_cmd.header.entity_id
and si, 0x0FFF
mov [esp + 4], si # out_cmd.entity_index
mov [esp + 6], di # out_cmd.total_damage
mov ecx, esp
mov edx, 0x00801150
call edx # send_and_handle_60(&out_cmd);
add esp, 0x10
jmp handle_6xE4_return
handle_6xE4_damage_less_than_max_hp:
xor edi, edi
cmp edx, edx
cmovl edx, edi
mov [eax + 0x06], dx # st.total_damage = std::max<int16_t>(st.total_damage + cmd->hit_amount, 0);
mov edx, eax # edx = ene_st
mov eax, [esp] # eax = ene
test eax, eax
jz handle_6xE4_return_pop_ene
mov ecx, eax
push edx
mov edx, [ecx]
call [edx + 0x148] # ene->vtable[0x52](ene, &st);
handle_6xE4_return_pop_ene:
add esp, 4
handle_6xE4_return:
pop edi
pop esi
pop ebx
ret
handle_6xE4_end:
call write_address_of_code
# Write TObjectV00b421c0::incr_hp_with_sync
push 5
push 0x00775224 # TObjectV00b421c0::v18_accept_hit (presumably Resta) - this is add_hp, not subtract_hp!
push 5
push 0x00778063 # TObjectV00b421c0::subtract_hp_if_not_in_state_2
push 5
push 0x00777AB2 # TObjectV00b421c0::v19_handle_hit_special_effects
push 5
push 0x00777B2B # TObjectV00b421c0::v19_handle_hit_special_effects
push 5
push 0x00777BFC # TObjectV00b421c0::v19_handle_hit_special_effects
push 5
push 0x00777C75 # TObjectV00b421c0::v19_handle_hit_special_effects
push 5
push 0x00776D2D # TObjectV00b421c0::v19_handle_hit_special_effects
push 5
push 0x007769C2 # TObjectV00b421c0::v19_handle_hit_special_effects
push 5
push 0x0077683C # TObjectV00b421c0::v19_handle_hit_special_effects
push 5
push 0x00776502 # TObjectV00b421c0::v19_handle_hit_special_effects (Devil's/Demon's)
push 5
push 0x00775B57 # TObjectV00b421c0::v18_accept_hit
push 5
push 0x00775A23 # TObjectV00b421c0::v18_accept_hit
push 5
push 0x007757F0 # TObjectV00b421c0::v18_accept_hit
push 5
push 0x00775606 # TObjectV00b421c0::v18_accept_hit
push 5
push 0x007754BC # TObjectV00b421c0::v18_accept_hit
push 5
push 0x00774E3D # TObjectV00b421c0::v18_accept_hit
push 5
push 0x00774CD6 # TObjectV00b421c0::v18_accept_hit
push 5
push 0x00774713 # TObjectV00b421c0::v17
push 18
call +4
.deltaof on_add_or_subtract_hp_start, on_add_or_subtract_hp_end
pop eax
push dword [eax]
call on_add_or_subtract_hp_end
on_add_or_subtract_hp_start: # (TObjectV00b421c0* this @ ecx, int16_t amount @ [esp + 4]) -> bool @ eax
test byte [0x00AA8DFC], 0x80
jz on_add_or_subtract_hp_skip_send
movzx eax, word [ecx + 0x1C] # ene->entity_id
cmp eax, 0x1000
jl on_add_or_subtract_hp_skip_send
cmp eax, 0x1B50
jge on_add_or_subtract_hp_skip_send
and eax, 0x0FFF
imul eax, eax, 0x0C
add eax, [0x00AADE38] # eax = state_for_enemy(cmd->header.entity_id)
sub esp, 0x10
mov word [esp], 0x04E4
mov dx, [ecx + 0x1C]
mov [esp + 0x02], dx # cmd.entity_id
mov dx, [esp + 0x14]
cmp dword [esp + 0x10], 0x00775229 # Check if callsite is add_hp
jne on_add_or_subtract_hp_skip_negate_amount
neg dx
on_add_or_subtract_hp_skip_negate_amount:
mov [esp + 0x04], dx # cmd.hit_amount
mov dx, [eax + 6]
mov [esp + 0x06], dx # cmd.total_damage_before_hit
mov dx, [ecx + 0x0334]
mov [esp + 0x08], dx # cmd.current_hp
mov dx, [ecx + 0x02BC]
mov [esp + 0x0A], dx # cmd.max_hp
mov dword [esp + 0x0C], 0xBF800000 # cmd.factor
cmp dword [esp + 0x10], 0x00776507 # Check if callsite is Devil's/Demon's
jne on_add_or_subtract_hp_not_proportional
# esp is 0x18 down from where it is in caller's context
mov edx, 100
sub edx, [esp + 0x24] # edx = (100 - special_amount)
push edx
fild st0, dword [esp] # current_hp_factor = static_cast<float>(100 - special_amount)
fmul st0, dword [esp + 0x54] # *= weapon_reduction_factor
mov dword [esp], 0x42C80000 # 100.0f
fdiv st0, dword [esp]
add esp, 4
fstp dword [esp + 0x0C], st0 # cmd.factor = ((100 - special_amount) * weapon_reduction_factor) / 100
on_add_or_subtract_hp_not_proportional:
mov edx, esp
push ecx
push 0x10
push edx
mov ecx, [0x00AA8E04]
mov edx, 0x007D4CBC
call edx # send_60(root_protocol, &cmd, sizeof(cmd));
pop ecx
add esp, 0x10
on_add_or_subtract_hp_skip_send:
mov eax, 0x007781F0 # subtract_hp
mov edx, 0x007781B0 # add_hp
cmp dword [esp], 0x00775229 # Check if callsite is add_hp
cmove eax, edx
jmp eax
on_add_or_subtract_hp_end:
call write_call_to_code_multi
push 5
push 0x0078864B
push 1
call +4
.deltaof on_6x0A_patch_start, on_6x0A_patch_end
pop eax
push dword [eax]
call on_6x0A_patch_end
on_6x0A_patch_start: # (TObjectV00b421c0* this @ ecx, int16_t amount @ [esp + 4]) -> bool @ eax
test byte [0x00AA8DFC], 0x80
jz on_6x0A_patch_skip_write
mov [esp + 0x0A], cx
on_6x0A_patch_skip_write:
ret
on_6x0A_patch_end:
call write_call_to_code_multi
ret
@@ -1,45 +1,47 @@
.meta name="Enemy HP bars"
.meta description="Shows HP bars in\nenemy info windows"
.versions 59NJ 59NL
entry_ptr:
reloc0:
.offsetof start
start:
.include WriteCodeBlocksBB
.data 0x007318DD
.data <VERS 0x0073197D 0x007318DD>
.data 6
.binary 81E2FDFFFFFF
.data 0x00731F2F
.data <VERS 0x00731FCF 0x00731F2F>
.data 1
.binary FA
.data 0x009F2DA4
.data <VERS 0x009F0DA4 0x009F2DA4>
.data 4
.data 0x42480000
.data 0x009F2DAC
.data <VERS 0x009F0DAC 0x009F2DAC>
.data 4
.data 0x41C00000
.data 0x009F2DD4
.data <VERS 0x009F0DD4 0x009F2DD4>
.data 4
.data 0x42480000
.data 0x009F2DDC
.data <VERS 0x009F0DDC 0x009F2DDC>
.data 4
.data 0x41C00000
.data 0x009F2E04
.data <VERS 0x009F0E04 0x009F2E04>
.data 4
.data 0x42480000
.data 0x009F2E0C
.data <VERS 0x009F0E0C 0x009F2E0C>
.data 4
.data 0x41C00000
.data 0x009F2E34
.data <VERS 0x009F0E34 0x009F2E34>
.data 4
.data 0x42480000
.data 0x009F2E3C
.data <VERS 0x009F0E3C 0x009F2E3C>
.data 4
.data 0x41C00000
.data 0x009F2E64
.data <VERS 0x009F0E64 0x009F2E64>
.data 4
.data 0x42200000
.data 0x009F2E80
.data <VERS 0x009F0E80 0x009F2E80>
.data 4
.data 0xFF00FF15
.data 0x00000000
@@ -1,17 +0,0 @@
# This function implements $exit in a game when no quest is loaded.
.meta name="Exit anywhere"
.meta description=""
.meta hide_from_patches_menu
entry_ptr:
reloc0:
.offsetof start
start:
xor eax, eax
mov [0x00A95624], eax # is_in_quest = false
mov [0x00A955E0], eax # dat_source_type = NONE
inc eax
mov [0x00AAE6D4], ax # should_leave_game = true
ret
@@ -0,0 +1,19 @@
# This function implements $exit in a game when no quest is loaded.
.meta name="Exit anywhere"
.meta description=""
.meta hide_from_patches_menu
.versions 59NJ 59NL
entry_ptr:
reloc0:
.offsetof start
start:
xor eax, eax
mov [<VERS 0x00A931A4 0x00A95624>], eax # is_in_quest = false
mov [<VERS 0x00A93160 0x00A955E0>], eax # dat_source_type = NONE
inc eax
mov [<VERS 0x00AAC254 0x00AAE6D4>], ax # should_leave_game = true
ret
@@ -1,19 +1,21 @@
.meta name="Fast tekker"
.meta description="Skips wind-up sound\nat tekker window"
.versions 59NJ 59NL
entry_ptr:
reloc0:
.offsetof start
start:
.include WriteCodeBlocksBB
.data 0x006DA113
.data <VERS 0x006DA14B 0x006DA113>
.deltaof patch1_start, patch1_end
patch1_start:
mov dword [edi + 0x14C], 1
patch1_end:
.data 0x006DA130
.data <VERS 0x006DA168 0x006DA130>
.deltaof patch2_start, patch2_end
patch2_start:
nop
@@ -0,0 +1,34 @@
.meta name="MAG alert"
.meta description="Plays a sound when\nyour MAG is hungry"
entry_ptr:
reloc0:
.offsetof start
start:
pop ecx
push 6
push 0x005D91BE
call get_code_size
.deltaof patch_code, patch_code_end
get_code_size:
pop eax
push dword [eax]
call patch_code_end
patch_code: # [eax] (TItemMag* this @ ecx) -> void
mov dword [ecx + 0x01B8], eax
mov eax, [ecx + 0x00F8]
movzx eax, word [eax + 0x001C] # eax = this->owner_player->entity_id
cmp [0x00A9A074], eax
jne patch_code_skip_sound
push 0
push 0
push 0
push 0xAC
mov eax, 0x00815020
call eax
add esp, 0x10
patch_code_skip_sound:
ret
patch_code_end:
push ecx
.include WriteCallToCode-59NJ
@@ -0,0 +1,43 @@
# Original patch by Soly, in Blue Burst Patch Project
# https://github.com/Solybum/Blue-Burst-Patch-Project
.meta name="No rare selling"
.meta description="Stops you from accidentally\nselling rares to vendors"
entry_ptr:
reloc0:
.offsetof start
start:
# This works by setting the item price to zero if it's rare, which causes
# the game to prevent you from selling the item. For armors and weapons, this
# is easy because there are easily-patchable opcodes within branches that
# return a constant price for rare items.
xor eax, eax
mov [0x005D258F], eax # Rare armors
mov [0x005D26D1], eax # Unidentified weapons
mov [0x005D26E6], eax # Rare weapons
# For tools, it's harder to implement this, because the price comes from the
# ItemPMT tools table and there is no branch for rares. Still, we can add a
# branch to a stub to handle tools.
pop ecx
push 5
push 0x005D2508
call get_code_size
.deltaof patch_code, patch_code_end
get_code_size:
pop eax
push dword [eax]
call patch_code_end
patch_code:
# TODO: It'd be nice to have something like WriteJumpToAndFromCode, since
# this hook is supposed to return to a different place than where it was
# called, hence this mov [esp].
mov dword [esp], 0x005D2556
xor edi, edi
test byte [eax + 0x14], 0x80 # flags & 0x80 = is rare
cmovz edi, [eax + 0x10] # Use price from table if not rare
ret
patch_code_end:
push ecx
.include WriteCallToCode-59NJ
@@ -0,0 +1,230 @@
# Original patch by Soly, in Blue Burst Patch Project
# https://github.com/Solybum/Blue-Burst-Patch-Project
.meta name="Palette"
.meta description="Enables the alternate action\npalette for number keys"
entry_ptr:
reloc0:
.offsetof start
write_call_func:
.include WriteCallToCode-59NJ
start:
mov al, 0xEB
mov [0x0068A7A5], al # SecondaryPaletteAttack1
xor al, al
mov [0x006A11B7], al # SecondaryPaletteAttack2
mov [0x006A0CB7], al # SecondaryPaletteAttack3
call patch_func_1 # GetCurrentPalette
call patch_func_2 # CheckHotkey1_1
call patch_func_3 # CheckHotkey1_2
call patch_func_4 # CheckHotkey2_1
call patch_func_5 # CheckHotkey2_2
call patch_func_6 # CheckHotkey3_1
call patch_func_7 # CheckHotkey3_2
jmp write_code_blocks # UnsetHotkey1, UnsetHotkey2, SetHotkey
# GetCurrentPalette
patch_func_1:
pop ecx
push 8
push 0x00748990
call get_code_size1
.deltaof patch_code1, patch_code_end1
get_code_size1:
pop eax
push dword [eax]
call patch_code_end1
patch_code1:
mov edx, [ebp - 0x14]
mov edx, [edx + 0x2C]
movzx edx, byte [edx + 0x62]
test edx, edx
setnz byte [0x00748B1B]
mov edx, edi
and edx, 0xFF
ret
patch_code_end1:
push ecx
jmp write_call_func
# CheckHotkey1_1
patch_func_2:
pop ecx
push 5
push 0x007489DE
call get_code_size2
.deltaof patch_code2, patch_code_end2
get_code_size2:
pop eax
push dword [eax]
call patch_code_end2
patch_code2:
cmp byte [0x00748B1B], 0
jnz +0x06
movzx edx, byte [eax + esi * 4 + 0x04] # main palette
ret
movzx edx, byte [eax + esi * 4 + 0x3C] # alt palette
ret
patch_code_end2:
push ecx
jmp write_call_func
# CheckHotkey1_2
patch_func_3:
pop ecx
push 5
push 0x007489ED
call get_code_size3
.deltaof patch_code3, patch_code_end3
get_code_size3:
pop eax
push dword [eax]
call patch_code_end3
patch_code3:
cmp byte [0x00748B1B], 0
jnz +0x06
movzx ecx, byte [eax + ecx * 2 + 0x05] # main palette
ret
movzx ecx, byte [eax + ecx * 2 + 0x3D] # alt palette
ret
patch_code_end3:
push ecx
jmp write_call_func
# CheckHotkey2_1
patch_func_4:
pop ecx
push 5
push 0x00748A88
call get_code_size4
.deltaof patch_code4, patch_code_end4
get_code_size4:
pop eax
push dword [eax]
call patch_code_end4
patch_code4:
cmp byte [0x00748B1B], 0
jnz +0x06
movzx edx, byte [edx + ebx * 4 + 0x04] # main palette
ret
movzx edx, byte [edx + ebx * 4 + 0x3C] # alt palette
ret
patch_code_end4:
push ecx
jmp write_call_func
# CheckHotkey2_2
patch_func_5:
pop ecx
push 5
push 0x00748A97
call get_code_size5
.deltaof patch_code5, patch_code_end5
get_code_size5:
pop eax
push dword [eax]
call patch_code_end5
patch_code5:
cmp byte [0x00748B1B], 0
jnz +0x06
movzx ecx, byte [edx + eax * 2 + 0x05] # main palette
ret
movzx ecx, byte [edx + eax * 2 + 0x3D] # alt palette
ret
patch_code_end5:
push ecx
jmp write_call_func
# CheckHotkey3_1
patch_func_6:
pop ecx
push 5
push 0x007103D3
call get_code_size6
.deltaof patch_code6, patch_code_end6
get_code_size6:
pop eax
push dword [eax]
call patch_code_end6
patch_code6:
cmp byte [0x00748B1B], 0
jnz +0x06
movzx ecx, byte [eax + edx * 4 + 0x04] # main palette
ret
movzx ecx, byte [eax + edx * 4 + 0x3C] # alt palette
ret
patch_code_end6:
push ecx
jmp write_call_func
# CheckHotkey3_2
patch_func_7:
pop ecx
push 5
push 0x007103DC
call get_code_size7
.deltaof patch_code7, patch_code_end7
get_code_size7:
pop eax
push dword [eax]
call patch_code_end7
patch_code7:
cmp byte [0x00748B1B], 0
jnz +0x06
movzx ecx, byte [eax + edx * 4 + 0x05] # main palette
ret
movzx ecx, byte [eax + edx * 4 + 0x3D] # alt palette
ret
patch_code_end7:
push ecx
jmp write_call_func
write_code_blocks:
.include WriteCodeBlocksBB
.data 0x00748A05
.deltaof code_block1_start, code_block1_end
# UnsetHotkey1
code_block1_start:
push dword [0x00748B1B]
push eax
mov eax, 0x0068CE4C # SetPaletteHotkey
call eax
.binary 909090909090909090
code_block1_end:
.data 0x00748AAB
.deltaof code_block2_start, code_block2_end
# UnsetHotkey2
code_block2_start:
push dword [0x00748B1B]
push eax
mov eax, 0x0068CE4C # SetPaletteHotkey
call eax
.binary 909090909090909090
code_block2_end:
.data 0x00748B0A
.deltaof code_block3_start, code_block3_end
# SetHotkey
code_block3_start:
mov eax, [ebp - 0x24]
mov ecx, [ebp - 0x28]
movzx ebx, word [eax]
movzx edx, word [eax + 0x02]
push edx
push ebx
push esi
.binary 6800000000 # tmpCurrentPalette = 0x00748B1B
push 0
mov eax, 0x0068CE4C # SetPaletteHotkey
call eax
.binary 90909090909090909090909090909090
code_block3_end:
.data 0x00000000
.data 0x00000000
@@ -0,0 +1,44 @@
# (uint16_t entity_id @ eax) -> TObjectV00b421c0* @ eax
# Preserves all registers except eax
get_enemy_entity:
push esi
push edi
push edx
push ecx
xor edx, edx
xchg edx, eax
cmp edx, 0x1000
jl done
cmp edx, 0x4000
jge done
mov esi, [0x00AABCE8] # bs_low = next_player_entity_index
mov edi, [0x00AABCE4]
lea edi, [edi + esi - 1] # bs_high = next_player_entity_index + next_enemy_entity_index - 1
bs_again:
cmp esi, edi
jge bs_done
lea ecx, [esi + edi]
shr ecx, 1
mov eax, [ecx * 4 + 0x00AAB2A0] # all_entities[ecx]
cmp [eax + 0x1C], dx
jge bs_not_less
lea esi, [ecx + 1]
jmp bs_again
bs_not_less:
mov edi, ecx
jmp bs_again
bs_done:
mov eax, [esi * 4 + 0x00AAB2A0] # all_entities[bs_low]
test eax, eax
je done
xor ecx, ecx
cmp [eax + 0x1C], dx
cmovne eax, ecx
done:
pop ecx
pop edx
pop edi
pop esi
@@ -0,0 +1,42 @@
# This file defines the following function:
# write_address_of_code(
# const void* patch_code,
# size_t patch_code_size,
# void** ptr_addr);
# This function allocates memory for patch_code, copies patch_code to that
# memory, then writes the address of the allocated code at the specified
# pointer. The allocated memory is never freed.
# This function pops its arguments off the stack before returning.
write_call_to_code:
# [esp + 0x04] = code ptr
# [esp + 0x08] = code size
# [esp + 0x0C] = ptr addr
# Allocate memory for the copied code
mov ecx, [0x00AA8F84]
push dword [esp + 0x08]
mov eax, 0x007A984C
call eax # malloc7
test eax, eax
je done
# Copy the code to the newly-allocated memory
# eax = dest pointer (from malloc7 call above)
mov edx, [esp + 0x04] # edx = source pointer
mov ecx, [esp + 0x08] # ecx = source size
push ebx
memcpy_again:
dec ecx
mov bl, [edx + ecx] # Copy one byte from source to dest
mov [eax + ecx], bl
test ecx, ecx
jne memcpy_again
pop ebx
# Write the address
mov ecx, [esp + 0x0C]
mov [ecx], eax
done:
ret 0x0C
@@ -0,0 +1,76 @@
# This file defines the following function:
# write_call_to_code(
# const void* patch_code,
# size_t patch_code_size,
# void* call_opcode_address,
# ssize_t call_opcode_bytes);
# This function allocates memory for patch_code, copies patch_code to that
# memory, then writes a call or jmp opcode to call_opcode_address that calls
# the code in the allocated memory region. The allocated memory is never freed.
# call_opcode_bytes specifies how many bytes at the callsite should be
# overwritten. This value must be at least 5; the first 5 bytes are overwritten
# with the call/jmp opcode itself; the rest are overwritten with nop opcodes.
# If call_opcode_bytes is positive, a call opcode is written; if it's negative,
# a jmp opcode is written.
# This function pops its arguments off the stack before returning.
write_call_to_code:
# [esp + 0x04] = code ptr
# [esp + 0x08] = code size
# [esp + 0x0C] = jump callsite
# [esp + 0x10] = callsite size (if zero, write the address instead of a call)
# Allocate memory for the copied code
mov ecx, [0x00AA8F84]
push dword [esp + 0x08]
mov eax, 0x007A984C
call eax # malloc7
test eax, eax
je done
# Copy the code to the newly-allocated memory
# eax = dest pointer (from malloc7 call above)
mov edx, [esp + 0x04] # edx = source pointer
mov ecx, [esp + 0x08] # ecx = source size
push ebx
memcpy_again:
dec ecx
mov bl, [edx + ecx] # Copy one byte from source to dest
mov [eax + ecx], bl
test ecx, ecx
jne memcpy_again
pop ebx
mov edx, [esp + 0x0C] # edx = jump callsite
# If the callsite size is zero, just write the address directly
cmp dword [esp + 0x10], 0
jne write_call_or_jmp
mov [edx], eax
jmp done
# Write the call or jmp opcode
write_call_or_jmp:
lea ecx, [eax - 5]
sub ecx, edx # ecx = (dest code addr) - (jump callsite) - 5
cmp dword [esp + 0x10], 0
setl al
or al, 0xE8
mov [edx], al # Write E8 (call), or E9 (jmp) if size was negative
mov [edx + 1], ecx # Write delta
# Write as many nops after the call opcode as necessary
mov ecx, 5
mov eax, [esp + 0x10]
cmp eax, 0
jge write_nop_again
neg eax
write_nop_again:
cmp ecx, eax
jge done
mov byte [edx + ecx], 0x90
inc ecx
jmp write_nop_again
done:
ret 0x10
@@ -0,0 +1,83 @@
# This file defines the following function:
# void [/std] write_call_to_code(
# const void* patch_code @ [esp + 0x04],
# size_t patch_code_size @ [esp + 0x08],
# size_t call_count @ [esp + 0x0C],
# void* call_opcode_address @ [esp + 0x10],
# ssize_t call_opcode_bytes @ [esp + 0x14],
# ...);
# This function allocates memory for patch_code, copies patch_code to that
# memory, then writes a call or jmp opcode to call_opcode_address that calls
# the code in the allocated memory region. The allocated memory is never freed.
# call_opcode_bytes specifies how many bytes at the callsite should be
# overwritten. This value must be at least 5; the first 5 bytes are overwritten
# with the call/jmp opcode itself; the rest are overwritten with nop opcodes.
# This function pops its arguments off the stack before returning (including
# all the varargs).
write_call_to_code:
# [esp + 0x04] = code ptr
# [esp + 0x08] = code size
# [esp + 0x0C] = callsite count
# [esp + 0x10] = callsite address
# [esp + 0x14] = callsite size
# ... (further callsite address/size pairs)
# Allocate memory for the copied code
mov ecx, [0x00AA8F84]
push dword [esp + 0x08]
mov eax, 0x007A984C
call eax # malloc7
test eax, eax
je done
# Copy the code to the newly-allocated memory
# eax = dest pointer (from malloc7 call above)
mov edx, [esp + 0x04] # edx = source pointer
mov ecx, [esp + 0x08] # ecx = source size
push ebx
memcpy_again:
dec ecx
mov bl, [edx + ecx] # Copy one byte from source to dest
mov [eax + ecx], bl
test ecx, ecx
jne memcpy_again
pop ebx
# Write the call opcodes
xchg ebx, [esp + 0x0C] # Save ebx; get callsite count
mov [esp - 0x08], esi
mov [esp - 0x0C], eax
mov esi, 0x10 # Stack offset of first callsite pair
next_callsite:
mov edx, [esp + esi] # edx = jump callsite
lea ecx, [eax - 5]
sub ecx, edx # ecx = (dest code addr) - (jump callsite) - 5
mov byte [edx], 0xE8
mov [edx + 1], ecx # Write E8 (call) followed by delta
# Write as many nops after the call opcode as necessary
mov ecx, 5
mov eax, [esp + esi + 4]
write_nop_again:
cmp ecx, eax
jge this_callsite_done
mov byte [edx + ecx], 0x90
inc ecx
jmp write_nop_again
this_callsite_done:
mov eax, [esp - 0x0C]
add esi, 8
dec ebx
jnz next_callsite
mov ecx, esi
mov ebx, [esp + 0x0C]
mov esi, [esp - 0x08]
done:
mov eax, [esp]
add esp, ecx
jmp eax
+2 -2
View File
@@ -576,14 +576,14 @@
],
// BB bank size. If you change either of these values, you must also add "BankSize" to BBRequiredPatches, and change
// the patch contents in system/client-functions/BlueBurstExclusive/BankSize.59NL.patch.s to reflect the counts here.
// the patch contents in system/client-functions/BlueBurstExclusive/BankSize.5___.patch.s to reflect the counts here.
"BBMaxBankItems": 200,
"BBMaxBankMeseta": 999999,
// Item stack limits. Note that changing these does not affect the client's behavior automatically - this only exists
// to allow the server to understand the behavior of clients that are already patched with different stack limits.
// If you want to use an unpatched BB client but still have custom stack limits, you can use the StackLimits runtime
// patch by editing system/client-functions/BlueBurstExclusive/StackLimits.59NL.patch.s to match the BB stack limits
// patch by editing system/client-functions/BlueBurstExclusive/StackLimits.5___.patch.s to match the BB stack limits
// and adding "StackLimits" to the BBRequiredPatches list.
// It's important that all players in the same game have the same stack limits, both on the client and server! So, if
// you change the BB stack limits, you must either prevent BB from playing with other versions (see
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
-1
View File
@@ -1 +0,0 @@
ItemPMT-pc-v2.prs
@@ -1 +0,0 @@
ItemPMT-gc-v3.prs
-1
View File
@@ -1 +0,0 @@
ItemPMT-gc-v3.prs
Binary file not shown.
Binary file not shown.
-1
View File
@@ -1 +0,0 @@
ItemPMT-pc-v2.prs
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1
View File
@@ -0,0 +1 @@
item-parameter-table-pc-v2.json
@@ -0,0 +1 @@
item-parameter-table-gc-v3.json
+1
View File
@@ -0,0 +1 @@
item-parameter-table-gc-v3.json
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+1
View File
@@ -0,0 +1 @@
item-parameter-table-pc-v2.json
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+25 -23
View File
@@ -1,19 +1,15 @@
[
// This table maps in-game primary identifiers across client versions. The
// high bit of the entries in this table specifies the entry type; if this
// bit is set, the entry is not "canonical"; that is, it's a one-way mapping
// and the client does not actually have that item defined. Each primary
// identifier must have at most one "canonical" listing for each version
// (but may have none). To convert an item from client A to client B:
// 1. Scan the column for A's version in the table to find the cell that
// contains the primary identifier sent by A. (We optimize this with an
// index.)
// This table maps in-game primary identifiers across client versions. The high bit of the entries in this table
// specifies the entry type; if this bit is set, the entry is not "canonical"; that is, it's a one-way mapping and
// the client does not actually have that item defined. Each primary identifier must have at most one "canonical"
// listing for each version (but may have none). To convert an item from client A to client B:
// 1. Scan the column for A's version in the table to find the cell that contains the primary identifier sent by A.
// (We optimize this with an index.)
// 2. Move left or right to the column for B's version.
// 3. Take the value from that cell, clear the high bit if needed, and use
// that as the primary identifier to show to B.
// 3. Take the value from that cell, clear the high bit if needed, and use that as the primary identifier for B.
// This logic is implemented in ItemTranslationTable.cc.
// DC_NTE-- 11/2000--- DC_V1----- DC_V2----- PC_NTE---- PC_V2----- GC_NTE---- GC_V3----- GC_EP3_NTE GC_EP3---- XB_V3----- BB_V4----- NAME
// DC_NTE-- 11/2000--- DC_V1----- DC_V2----- PC_NTE---- PC_V2----- GC_NTE---- GC_V3----- GC_EP3_NTE GC_EP3---- XB_V3----- BB_V4----- NAME
[0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, "UNUSED"],
[0x00010000, 0x00010000, 0x00010000, 0x00010000, 0x00010000, 0x00010000, 0x00010000, 0x00010000, 0x00010000, 0x00010000, 0x00010000, 0x00010000, "Saber"],
[0x00010100, 0x00010100, 0x00010100, 0x00010100, 0x00010100, 0x00010100, 0x00010100, 0x00010100, 0x00010100, 0x00010100, 0x00010100, 0x00010100, "Brand"],
@@ -134,7 +130,6 @@
[0x000F0100, 0x000F0100, 0x000F0100, 0x000F0100, 0x000F0100, 0x000F0100, 0x800F0000, 0x000F0100, 0x000F0100, 0x000F0100, 0x000F0100, 0x000F0100, "ANGRY FIST"],
[0x000F0200, 0x000F0200, 0x000F0200, 0x000F0200, 0x000F0200, 0x000F0200, 0x800F0000, 0x000F0200, 0x000F0200, 0x000F0200, 0x000F0200, 0x000F0200, "GOD HAND"],
[0x000F0300, 0x000F0300, 0x000F0300, 0x000F0300, 0x000F0300, 0x000F0300, 0x800F0000, 0x000F0300, 0x000F0300, 0x000F0300, 0x000F0300, 0x000F0300, "SONIC KNUCKLE"],
[0x000F0400, 0x000F0400, 0x000F0400, 0x000F0400, 0x000F0400, 0x000F0400, 0x80020500, 0x80100000, 0x80100000, 0x80100000, 0x80100000, 0x80100000, "OROTIAGITO (v0-v2)"],
[0x800F0300, 0x800F0300, 0x800F0300, 0x800F0300, 0x800F0300, 0x800F0300, 0x800F0000, 0x800F0300, 0x800F0300, 0x800F0300, 0x800F0300, 0x000F0400, "LOGiN"],
[0x00100000, 0x00100000, 0x00100000, 0x00100000, 0x00100000, 0x00100000, 0x00100000, 0x00100000, 0x00100000, 0x00100000, 0x00100000, 0x00100000, "OROTIAGITO"],
[0x00100100, 0x00100100, 0x00100100, 0x00100100, 0x00100100, 0x00100100, 0x80100000, 0x00100100, 0x00100100, 0x00100100, 0x00100100, 0x00100100, "AGITO (1975)"],
@@ -439,7 +434,9 @@
[0x80080500, 0x80080500, 0x80080500, 0x80770000, 0x80770000, 0x80770000, 0x80080500, 0x80770000, 0x80770000, 0x80770000, 0x80770000, 0x00EA0000, "TypeME/MECHGUN"],
[0x80090500, 0x80090500, 0x80090500, 0x80780000, 0x80780000, 0x80780000, 0x80090500, 0x80780000, 0x80780000, 0x80780000, 0x80780000, 0x00EB0000, "TypeSH/SHOT"],
[0x800C0400, 0x800C0400, 0x800C0400, 0x807B0000, 0x807B0000, 0x807B0000, 0x800C0400, 0x807B0000, 0x807B0000, 0x807B0000, 0x807B0000, 0x00EC0000, "TypeWA/WAND"],
[0x80010000, 0x80010000, 0x80010000, 0x00890000, 0x00890000, 0x00890000, 0x008D0000, 0x00AA0000, 0x00AA0000, 0x00AA0000, 0x00AA0000, 0x00ED0000, "???? (WEAPON)"],
// DC_NTE-- 11/2000--- DC_V1----- DC_V2----- PC_NTE---- PC_V2----- GC_NTE---- GC_V3----- GC_EP3_NTE GC_EP3---- XB_V3----- BB_V4----- NAME
[0x01010000, 0x01010000, 0x01010000, 0x01010000, 0x01010000, 0x01010000, 0x01010000, 0x01010000, 0x01010000, 0x01010000, 0x01010000, 0x01010000, "Frame"],
[0x01010100, 0x01010100, 0x01010100, 0x01010100, 0x01010100, 0x01010100, 0x01010100, 0x01010100, 0x01010100, 0x01010100, 0x01010100, 0x01010100, "Armor"],
[0x01010200, 0x01010200, 0x01010200, 0x01010200, 0x01010200, 0x01010200, 0x01010200, 0x01010200, 0x01010200, 0x01010200, 0x01010200, 0x01010200, "Psy Armor"],
@@ -528,8 +525,9 @@
[0x81012800, 0x81012800, 0x81012800, 0x81013400, 0x81013400, 0x81013400, 0x81010C00, 0x81013400, 0x81013400, 0x81013400, 0x81013400, 0x01015500, "UNION FIELD"],
[0x81012800, 0x81012800, 0x81012800, 0x81013400, 0x81013400, 0x81013400, 0x81010C00, 0x81013400, 0x81013400, 0x81013400, 0x81013400, 0x01015600, "SAMURAI ARMOR"],
[0x81012800, 0x81012800, 0x81012800, 0x81013400, 0x81013400, 0x81013400, 0x81010C00, 0x81013400, 0x81013400, 0x81013400, 0x81013400, 0x01015700, "STEALTH SUIT"],
[0x81012800, 0x81012800, 0x81012800, 0x81013400, 0x81013400, 0x81013400, 0x01013500, 0x01013500, 0x01013500, 0x01013500, 0x01013500, 0x01015800, "???? (ARMOR)"],
[0x81012800, 0x81012800, 0x81012800, 0x01013500, 0x01013500, 0x01013500, 0x01013500, 0x01013500, 0x01013500, 0x01013500, 0x01013500, 0x01015800, "???? (ARMOR)"],
// DC_NTE-- 11/2000--- DC_V1----- DC_V2----- PC_NTE---- PC_V2----- GC_NTE---- GC_V3----- GC_EP3_NTE GC_EP3---- XB_V3----- BB_V4----- NAME
[0x01020000, 0x01020000, 0x01020000, 0x01020000, 0x01020000, 0x01020000, 0x01020000, 0x01020000, 0x01020000, 0x01020000, 0x01020000, 0x01020000, "Barrier"],
[0x01020100, 0x01020100, 0x01020100, 0x01020100, 0x01020100, 0x01020100, 0x01020100, 0x01020100, 0x01020100, 0x01020100, 0x01020100, 0x01020100, "Shield"],
[0x01020200, 0x01020200, 0x01020200, 0x01020200, 0x01020200, 0x01020200, 0x01020200, 0x01020200, 0x01020200, 0x01020200, 0x01020200, 0x01020200, "Core Shield"],
@@ -695,8 +693,9 @@
[0x81022600, 0x81022600, 0x81022600, 0x81023500, 0x81023500, 0x81023500, 0x81020C00, 0x81028400, 0x81028400, 0x81028400, 0x81028400, 0x0102A200, "GENPEI (unused)"],
[0x81022600, 0x81022600, 0x81022600, 0x81023500, 0x81023500, 0x81023500, 0x81020C00, 0x81028400, 0x81028400, 0x81028400, 0x81028400, 0x0102A300, "GENPEI (unused)"],
[0x81022600, 0x81022600, 0x81022600, 0x81023500, 0x81023500, 0x81023500, 0x81020C00, 0x81028400, 0x81028400, 0x81028400, 0x81028400, 0x0102A400, "GENPEI (unused)"],
[0x81022600, 0x81022600, 0x81022600, 0x81023500, 0x81023500, 0x81023500, 0x81020C00, 0x01028500, 0x01028500, 0x01028500, 0x01028500, 0x0102A500, "???? (SHIELD)"],
[0x81022600, 0x81022600, 0x81022600, 0x01023A00, 0x01023A00, 0x01023A00, 0x81020C00, 0x01028500, 0x01028500, 0x01028500, 0x01028500, 0x0102A500, "???? (SHIELD)"],
// DC_NTE-- 11/2000--- DC_V1----- DC_V2----- PC_NTE---- PC_V2----- GC_NTE---- GC_V3----- GC_EP3_NTE GC_EP3---- XB_V3----- BB_V4----- NAME
[0x01030000, 0x01030000, 0x01030000, 0x01030000, 0x01030000, 0x01030000, 0x01030000, 0x01030000, 0x01030000, 0x01030000, 0x01030000, 0x01030000, "Knight/Power"],
[0x01030100, 0x01030100, 0x01030100, 0x01030100, 0x01030100, 0x01030100, 0x01030100, 0x01030100, 0x01030100, 0x01030100, 0x01030100, 0x01030100, "General/Power"],
[0x01030200, 0x01030200, 0x01030200, 0x01030200, 0x01030200, 0x01030200, 0x01030200, 0x01030200, 0x01030200, 0x01030200, 0x01030200, 0x01030200, "Ogre/Power"],
@@ -799,8 +798,9 @@
[0x81033500, 0x81033500, 0x81033500, 0x81033500, 0x81033500, 0x81033500, 0x81033400, 0x81033500, 0x81033500, 0x81033500, 0x81033500, 0x01036100, "HP/Resurrection"],
[0x81033800, 0x81033800, 0x81033800, 0x81033800, 0x81033800, 0x81033800, 0x81033700, 0x81033800, 0x81033800, 0x81033800, 0x81033800, 0x01036200, "TP/Resurrection"],
[0x81033B00, 0x81033B00, 0x81033B00, 0x81033B00, 0x81033B00, 0x81033B00, 0x81033A00, 0x81033B00, 0x81033B00, 0x81033B00, 0x81033B00, 0x01036300, "PB/Increase"],
[0x81033B00, 0x81033B00, 0x81033B00, 0x81033B00, 0x81033B00, 0x81033B00, 0x01034700, 0x01034800, 0x01034800, 0x01034800, 0x01034800, 0x01036400, "???? (UNIT)"],
[0x81033B00, 0x81033B00, 0x81033B00, 0x01034400, 0x01034400, 0x01034400, 0x01034700, 0x01034800, 0x01034800, 0x01034800, 0x01034800, 0x01036400, "???? (UNIT)"],
// DC_NTE-- 11/2000--- DC_V1----- DC_V2----- PC_NTE---- PC_V2----- GC_NTE---- GC_V3----- GC_EP3_NTE GC_EP3---- XB_V3----- BB_V4----- NAME
[0x02000000, 0x02000000, 0x02000000, 0x02000000, 0x02000000, 0x02000000, 0x02000000, 0x02000000, 0x02000000, 0x02000000, 0x02000000, 0x02000000, "Mag"],
[0x02010000, 0x02010000, 0x02010000, 0x02010000, 0x02010000, 0x02010000, 0x02010000, 0x02010000, 0x02010000, 0x02010000, 0x02010000, 0x02010000, "Varuna"],
[0x02020000, 0x02020000, 0x02020000, 0x02020000, 0x02020000, 0x02020000, 0x02020000, 0x02020000, 0x02020000, 0x02020000, 0x02020000, 0x02020000, "Mitra"],
@@ -841,10 +841,10 @@
[0x02250000, 0x02250000, 0x02250000, 0x02250000, 0x02250000, 0x02250000, 0x02250000, 0x02250000, 0x02250000, 0x02250000, 0x02250000, 0x02250000, "Naraka"],
[0x02260000, 0x02260000, 0x02260000, 0x02260000, 0x02260000, 0x02260000, 0x02260000, 0x02260000, 0x02260000, 0x02260000, 0x02260000, 0x02260000, "Madhu"],
[0x02270000, 0x02270000, 0x02270000, 0x02270000, 0x02270000, 0x02270000, 0x02270000, 0x02270000, 0x02270000, 0x02270000, 0x02270000, 0x02270000, "Churel"],
[0x82000000, 0x82000000, 0x82000000, 0x02280000, 0x02280000, 0x02280000, 0x82000000, 0x02280000, 0x02280000, 0x02280000, 0x02280000, 0x02280000, "ROBOCHAO"],
[0x82000000, 0x82000000, 0x82000000, 0x02290000, 0x02290000, 0x02290000, 0x82000000, 0x02290000, 0x02290000, 0x02290000, 0x02290000, 0x02290000, "OPA-OPA"],
[0x82000000, 0x82000000, 0x82000000, 0x022A0000, 0x022A0000, 0x022A0000, 0x82000000, 0x022A0000, 0x022A0000, 0x022A0000, 0x022A0000, 0x022A0000, "PIAN"],
[0x82000000, 0x82000000, 0x82000000, 0x022B0000, 0x022B0000, 0x022B0000, 0x82000000, 0x022B0000, 0x022B0000, 0x022B0000, 0x022B0000, 0x022B0000, "CHAO"],
[0x02280000, 0x02280000, 0x02280000, 0x02280000, 0x02280000, 0x02280000, 0x82000000, 0x02280000, 0x02280000, 0x02280000, 0x02280000, 0x02280000, "ROBOCHAO"],
[0x02290000, 0x02290000, 0x02290000, 0x02290000, 0x02290000, 0x02290000, 0x82000000, 0x02290000, 0x02290000, 0x02290000, 0x02290000, 0x02290000, "OPA-OPA"],
[0x022A0000, 0x022A0000, 0x022A0000, 0x022A0000, 0x022A0000, 0x022A0000, 0x82000000, 0x022A0000, 0x022A0000, 0x022A0000, 0x022A0000, 0x022A0000, "PIAN"],
[0x022B0000, 0x022B0000, 0x022B0000, 0x022B0000, 0x022B0000, 0x022B0000, 0x82000000, 0x022B0000, 0x022B0000, 0x022B0000, 0x022B0000, 0x022B0000, "CHAO"],
[0x82000000, 0x82000000, 0x82000000, 0x022C0000, 0x022C0000, 0x022C0000, 0x82000000, 0x022C0000, 0x022C0000, 0x022C0000, 0x022C0000, 0x022C0000, "CHU CHU"],
[0x82000000, 0x82000000, 0x82000000, 0x022D0000, 0x022D0000, 0x022D0000, 0x82000000, 0x022D0000, 0x022D0000, 0x022D0000, 0x022D0000, 0x022D0000, "KAPU KAPU"],
[0x82000000, 0x82000000, 0x82000000, 0x022E0000, 0x022E0000, 0x022E0000, 0x82000000, 0x022E0000, 0x022E0000, 0x022E0000, 0x022E0000, 0x022E0000, "ANGEL'S WING"],
@@ -883,8 +883,9 @@
[0x82000000, 0x82000000, 0x82000000, 0x82000000, 0x82000000, 0x82000000, 0x82000000, 0x82000000, 0x82000000, 0x82000000, 0x82000000, 0x024F0000, "Cell of MAG 0505 (mag)"],
[0x82000000, 0x82000000, 0x82000000, 0x82000000, 0x82000000, 0x82000000, 0x82000000, 0x82000000, 0x82000000, 0x82000000, 0x82000000, 0x02500000, "Cell of MAG 0506 (mag)"],
[0x82000000, 0x82000000, 0x82000000, 0x82000000, 0x82000000, 0x82000000, 0x82000000, 0x82000000, 0x82000000, 0x82000000, 0x82000000, 0x02510000, "Cell of MAG 0507 (mag)"],
[0x82000000, 0x82000000, 0x82000000, 0x82000000, 0x82000000, 0x82000000, 0x02280000, 0x02420000, 0x02420000, 0x02420000, 0x02420000, 0x02520000, "???? (MAG)"],
[0x82000000, 0x82000000, 0x82000000, 0x02390000, 0x02390000, 0x02390000, 0x02280000, 0x02420000, 0x02420000, 0x02420000, 0x02420000, 0x02520000, "???? (MAG)"],
// DC_NTE-- 11/2000--- DC_V1----- DC_V2----- PC_NTE---- PC_V2----- GC_NTE---- GC_V3----- GC_EP3_NTE GC_EP3---- XB_V3----- BB_V4----- NAME
[0x03000000, 0x03000000, 0x03000000, 0x03000000, 0x03000000, 0x03000000, 0x03000000, 0x03000000, 0x03000000, 0x03000000, 0x03000000, 0x03000000, "Monomate"],
[0x03000100, 0x03000100, 0x03000100, 0x03000100, 0x03000100, 0x03000100, 0x03000100, 0x03000100, 0x03000100, 0x03000100, 0x03000100, 0x03000100, "Dimate"],
[0x03000200, 0x03000200, 0x03000200, 0x03000200, 0x03000200, 0x03000200, 0x03000200, 0x03000200, 0x03000200, 0x03000200, 0x03000200, 0x03000200, "Trimate"],
@@ -1027,7 +1028,7 @@
[0x830C0100, 0x830D0500, 0x830D0500, 0x830D0500, 0x830D0500, 0x830D0500, 0x830E0000, 0x03100200, 0x03100200, 0x03100200, 0x03100200, 0x03100200, "Photon Crystal"],
[0x830C0100, 0x830D0500, 0x830D0500, 0x830D0500, 0x830D0500, 0x830D0500, 0x830E0000, 0x83100000, 0x83100000, 0x83100000, 0x83100000, 0x03100300, "Secret Ticket"],
[0x830C0100, 0x830D0500, 0x830D0500, 0x830D0500, 0x830D0500, 0x830D0500, 0x830E0000, 0x83100000, 0x83100000, 0x83100000, 0x83100000, 0x03100400, "Photon Ticket"],
[0x830C0100, 0x830D0500, 0x830D0500, 0x030E0700, 0x030E0700, 0x030E0700, 0x830E0000, 0x03120000, 0x03120000, 0x03120000, 0x03120000, 0x03120000, "Weapons Bronze Badge"],
[0x830C0100, 0x830D0500, 0x830D0500, 0x030E0700, 0x030E0700, 0x030E0700, 0x03120000, 0x03120000, 0x03120000, 0x03120000, 0x03120000, 0x03120000, "Weapons Bronze Badge"],
[0x830C0100, 0x830D0500, 0x830D0500, 0x030E0800, 0x030E0800, 0x030E0800, 0x830E0000, 0x03120100, 0x03120100, 0x03120100, 0x03120100, 0x03120100, "Weapons Silver Badge"],
[0x830C0100, 0x830D0500, 0x830D0500, 0x030E0900, 0x030E0900, 0x030E0900, 0x830E0000, 0x03120200, 0x03120200, 0x03120200, 0x03120200, 0x03120200, "Weapons Gold Badge"],
[0x830C0100, 0x830D0500, 0x830D0500, 0x030E0A00, 0x030E0A00, 0x030E0A00, 0x830E0000, 0x03120300, 0x03120300, 0x03120300, 0x03120300, 0x03120300, "Weapons Crystal Badge"],
@@ -1090,5 +1091,6 @@
[0x830C0100, 0x830D0500, 0x830D0500, 0x830D0500, 0x830D0500, 0x830D0500, 0x830E0000, 0x830D0500, 0x830D0500, 0x830D0500, 0x830D0500, 0x03190100, "Team Points 1000"],
[0x830C0100, 0x830D0500, 0x830D0500, 0x830D0500, 0x830D0500, 0x830D0500, 0x830E0000, 0x830D0500, 0x830D0500, 0x830D0500, 0x830D0500, 0x03190200, "Team Points 5000"],
[0x830C0100, 0x830D0500, 0x830D0500, 0x830D0500, 0x830D0500, 0x830D0500, 0x830E0000, 0x830D0500, 0x830D0500, 0x830D0500, 0x830D0500, 0x03190300, "Team Points 10000"],
[0x830C0100, 0x830D0500, 0x830D0500, 0x030F0000, 0x030F0000, 0x030F0000, 0x03120000, 0x83170400, 0x83170400, 0x83170400, 0x83170400, 0x031A0000, "???? (TOOL)"],
[0x830C0100, 0x830D0500, 0x830D0500, 0x030F0000, 0x030F0000, 0x030F0000, 0x03130000, 0x03180000, 0x03180000, 0x03180000, 0x03180000, 0x031A0000, "???? (TOOL)"],
// DC_NTE-- 11/2000--- DC_V1----- DC_V2----- PC_NTE---- PC_V2----- GC_NTE---- GC_V3----- GC_EP3_NTE GC_EP3---- XB_V3----- BB_V4----- NAME
]
+73
View File
@@ -0,0 +1,73 @@
#!/bin/sh
set -e
EXECUTABLE="$1"
if [ -z "$EXECUTABLE" ]; then
EXECUTABLE="./newserv"
fi
DIR=tests/item-parameter-tables
echo "... DC NTE"
$EXECUTABLE decode-item-parameter-table --dc-nte $DIR/dc-nte.expected.bin --decompressed $DIR/dc-nte.json --hex
$EXECUTABLE encode-item-parameter-table --dc-nte $DIR/dc-nte.json $DIR/dc-nte.encoded.bin --decompressed
bindiff $DIR/dc-nte.expected.bin $DIR/dc-nte.encoded.bin
echo "... DC 11/2000"
$EXECUTABLE decode-item-parameter-table --dc-11-2000 $DIR/dc-11-2000.expected.bin --decompressed $DIR/dc-11-2000.json --hex
$EXECUTABLE encode-item-parameter-table --dc-11-2000 $DIR/dc-11-2000.json $DIR/dc-11-2000.encoded.bin --decompressed
bindiff $DIR/dc-11-2000.expected.bin $DIR/dc-11-2000.encoded.bin
echo "... DC V1"
$EXECUTABLE decode-item-parameter-table --dc-v1 $DIR/dc-v1.expected.bin --decompressed $DIR/dc-v1.json --hex
$EXECUTABLE encode-item-parameter-table --dc-v1 $DIR/dc-v1.json $DIR/dc-v1.encoded.bin --decompressed
bindiff $DIR/dc-v1.expected.bin $DIR/dc-v1.encoded.bin
echo "... DC V2"
$EXECUTABLE decode-item-parameter-table --dc-v2 $DIR/dc-v2.expected.bin --decompressed $DIR/dc-v2.json --hex
$EXECUTABLE encode-item-parameter-table --dc-v2 $DIR/dc-v2.json $DIR/dc-v2.encoded.bin --decompressed
bindiff $DIR/dc-v2.expected.bin $DIR/dc-v2.encoded.bin
echo "... PC NTE"
$EXECUTABLE decode-item-parameter-table --pc-nte $DIR/pc-nte.expected.bin --decompressed $DIR/pc-nte.json --hex
$EXECUTABLE encode-item-parameter-table --pc-nte $DIR/pc-nte.json $DIR/pc-nte.encoded.bin --decompressed
bindiff $DIR/pc-nte.expected.bin $DIR/pc-nte.encoded.bin
echo "... PC V2"
$EXECUTABLE decode-item-parameter-table --pc-v2 $DIR/pc-v2.expected.bin --decompressed $DIR/pc-v2.json --hex
$EXECUTABLE encode-item-parameter-table --pc-v2 $DIR/pc-v2.json $DIR/pc-v2.encoded.bin --decompressed
bindiff $DIR/pc-v2.expected.bin $DIR/pc-v2.encoded.bin
echo "... GC NTE"
$EXECUTABLE decode-item-parameter-table --gc-nte $DIR/gc-nte.expected.bin --decompressed $DIR/gc-nte.json --hex
$EXECUTABLE encode-item-parameter-table --gc-nte $DIR/gc-nte.json $DIR/gc-nte.encoded.bin --decompressed
bindiff $DIR/gc-nte.expected.bin $DIR/gc-nte.encoded.bin
echo "... GC V3"
$EXECUTABLE decode-item-parameter-table --gc-v3 $DIR/gc-v3.expected.bin --decompressed $DIR/gc-v3.json --hex
$EXECUTABLE encode-item-parameter-table --gc-v3 $DIR/gc-v3.json $DIR/gc-v3.encoded.bin --decompressed
bindiff $DIR/gc-v3.expected.bin $DIR/gc-v3.encoded.bin
echo "... GC Ep3 NTE"
$EXECUTABLE decode-item-parameter-table --gc-ep3-nte $DIR/gc-ep3-nte.expected.bin --decompressed $DIR/gc-ep3-nte.json --hex
$EXECUTABLE encode-item-parameter-table --gc-ep3-nte $DIR/gc-ep3-nte.json $DIR/gc-ep3-nte.encoded.bin --decompressed
bindiff $DIR/gc-ep3-nte.expected.bin $DIR/gc-ep3-nte.encoded.bin
echo "... GC Ep3"
$EXECUTABLE decode-item-parameter-table --gc-ep3 $DIR/gc-ep3.expected.bin --decompressed $DIR/gc-ep3.json --hex
$EXECUTABLE encode-item-parameter-table --gc-ep3 $DIR/gc-ep3.json $DIR/gc-ep3.encoded.bin --decompressed
bindiff $DIR/gc-ep3.expected.bin $DIR/gc-ep3.encoded.bin
echo "... XB"
$EXECUTABLE decode-item-parameter-table --xb-v3 $DIR/xb-v3.expected.bin --decompressed $DIR/xb-v3.json --hex
$EXECUTABLE encode-item-parameter-table --xb-v3 $DIR/xb-v3.json $DIR/xb-v3.encoded.bin --decompressed
bindiff $DIR/xb-v3.expected.bin $DIR/xb-v3.encoded.bin
echo "... BB"
$EXECUTABLE decode-item-parameter-table --bb-v4 $DIR/bb-v4.expected.bin --decompressed $DIR/bb-v4.json --hex
$EXECUTABLE encode-item-parameter-table --bb-v4 $DIR/bb-v4.json $DIR/bb-v4.encoded.bin --decompressed
bindiff $DIR/bb-v4.expected.bin $DIR/bb-v4.encoded.bin
echo "... clean up"
rm -f tests/item-parameter-tables/*.encoded.bin tests/item-parameter-tables/*.json
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.