Merge upstream newserv master
This commit is contained in:
+15
-12
@@ -60,10 +60,12 @@ set(SOURCES
|
||||
src/ChatCommands.cc
|
||||
src/ChoiceSearch.cc
|
||||
src/Client.cc
|
||||
src/ClientFunctionIndex.cc
|
||||
src/CommonItemSet.cc
|
||||
src/Compression.cc
|
||||
src/DCSerialNumbers.cc
|
||||
src/DNSServer.cc
|
||||
src/DOLFileIndex.cc
|
||||
src/DownloadSession.cc
|
||||
src/EnemyType.cc
|
||||
src/Episode3/AssistServer.cc
|
||||
@@ -79,7 +81,6 @@ set(SOURCES
|
||||
src/Episode3/Server.cc
|
||||
src/Episode3/Tournament.cc
|
||||
src/FileContentsCache.cc
|
||||
src/FunctionCompiler.cc
|
||||
src/GameServer.cc
|
||||
src/GSLArchive.cc
|
||||
src/HTTPServer.cc
|
||||
@@ -153,23 +154,25 @@ add_dependencies(newserv newserv-Revision-cc)
|
||||
|
||||
enable_testing()
|
||||
|
||||
file(GLOB LogTestCases ${CMAKE_SOURCE_DIR}/tests/*.test.txt)
|
||||
file(GLOB LogRDTestCases ${CMAKE_SOURCE_DIR}/tests/*.rdtest.txt)
|
||||
|
||||
foreach(LogTestCase IN ITEMS ${LogTestCases})
|
||||
file(GLOB LOG_TEST_CASES ${CMAKE_SOURCE_DIR}/tests/*.test.txt)
|
||||
foreach(LOG_TEST_CASE IN ITEMS ${LOG_TEST_CASES})
|
||||
add_test(
|
||||
NAME ${LogTestCase}
|
||||
NAME ${LOG_TEST_CASE}
|
||||
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
|
||||
COMMAND ${CMAKE_BINARY_DIR}/newserv --replay-log=${LogTestCase} --config=${CMAKE_SOURCE_DIR}/tests/config.json)
|
||||
COMMAND ${CMAKE_BINARY_DIR}/newserv --replay-log=${LOG_TEST_CASE} --config=${CMAKE_SOURCE_DIR}/tests/config.json)
|
||||
endforeach()
|
||||
# list(TRANSFORM LOG_TEST_CASES PREPEND "--replay-log=" OUTPUT_VARIABLE LOG_REPLAY_ARGS)
|
||||
# add_test(
|
||||
# NAME "log-replays"
|
||||
# WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
|
||||
# COMMAND ${CMAKE_BINARY_DIR}/newserv --config=${CMAKE_SOURCE_DIR}/tests/config.json ${LOG_REPLAY_ARGS})
|
||||
|
||||
file(GLOB ScriptTestCases ${CMAKE_SOURCE_DIR}/tests/*.test.sh)
|
||||
|
||||
foreach(ScriptTestCase IN ITEMS ${ScriptTestCases})
|
||||
file(GLOB SCRIPT_TEST_CASES ${CMAKE_SOURCE_DIR}/tests/*.test.sh)
|
||||
foreach(SCRIPT_TEST_CASE IN ITEMS ${SCRIPT_TEST_CASES})
|
||||
add_test(
|
||||
NAME ${ScriptTestCase}
|
||||
NAME ${SCRIPT_TEST_CASE}
|
||||
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
|
||||
COMMAND ${ScriptTestCase} ${CMAKE_BINARY_DIR}/newserv)
|
||||
COMMAND ${SCRIPT_TEST_CASE} ${CMAKE_BINARY_DIR}/newserv)
|
||||
endforeach()
|
||||
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ import subprocess
|
||||
import sys
|
||||
from dataclasses import dataclass
|
||||
|
||||
|
||||
version_tokens = ("3OJ2", "3OJ3", "3OJ4", "3OJ5", "3OE0", "3OE1", "3OE2", "3OP0")
|
||||
|
||||
|
||||
@@ -62,7 +61,7 @@ def write_patches_for_code(
|
||||
f.write("reloc0:\n")
|
||||
f.write(" .offsetof start\n")
|
||||
f.write("start:\n")
|
||||
f.write(" .include WriteCodeBlocksGC\n")
|
||||
f.write(" .include WriteCodeBlocks\n")
|
||||
for region in write_regions:
|
||||
f.write(
|
||||
f" # region @ {region.address:08X} ({len(region.data) * 4} bytes)\n"
|
||||
|
||||
@@ -127,7 +127,7 @@ MOVEMENT DATA 1E (YOWIE_DESERT)
|
||||
fparam1 = TODO: 59NL:005AEBC5; looks like an angle in degrees (range [0, 359])
|
||||
fparam2 = TODO: 59NL:005AEBEE
|
||||
|
||||
MOVEMENT DATA 0D (DARK_BRINGER)
|
||||
MOVEMENT DATA 0D (CHAOS_BRINGER)
|
||||
fparam1 = TODO: 3OE1:FUN_80097F98; NNF: charge speed
|
||||
fparam2 = TODO: 3OE1:FUN_800983F8; NNF: movement speed
|
||||
fparam6 = TODO: 3OE1:80097F3C; NNF: Regular attack cooldown. Delay between going red and shooting.
|
||||
|
||||
+224
-12
@@ -9,6 +9,191 @@
|
||||
|
||||
using namespace std;
|
||||
|
||||
BattleParamsIndex::AttackData BattleParamsIndex::AttackData::from_json(const phosg::JSON& json) {
|
||||
return AttackData{
|
||||
json.get_int("MinATP"),
|
||||
json.get_int("MaxATP"),
|
||||
json.get_int("MinATA"),
|
||||
json.get_int("MaxATA"),
|
||||
json.get_float("DistanceX"),
|
||||
json.get_int("Angle"),
|
||||
json.get_float("DistanceY"),
|
||||
json.get_int("UnknownA8"),
|
||||
json.get_int("UnknownA9"),
|
||||
json.get_int("UnknownA10"),
|
||||
json.get_int("UnknownA11"),
|
||||
json.get_int("UnknownA12"),
|
||||
json.get_int("UnknownA13"),
|
||||
json.get_int("UnknownA14"),
|
||||
json.get_int("UnknownA15"),
|
||||
json.get_int("UnknownA16"),
|
||||
};
|
||||
}
|
||||
phosg::JSON BattleParamsIndex::AttackData::json() const {
|
||||
return phosg::JSON::dict({
|
||||
{"MinATP", this->min_atp.load()},
|
||||
{"MaxATP", this->max_atp.load()},
|
||||
{"MinATA", this->min_ata.load()},
|
||||
{"MaxATA", this->max_ata.load()},
|
||||
{"DistanceX", this->distance_x.load()},
|
||||
{"Angle", this->angle.load()},
|
||||
{"DistanceY", this->distance_y.load()},
|
||||
{"UnknownA8", this->unknown_a8.load()},
|
||||
{"UnknownA9", this->unknown_a9.load()},
|
||||
{"UnknownA10", this->unknown_a10.load()},
|
||||
{"UnknownA11", this->unknown_a11.load()},
|
||||
{"UnknownA12", this->unknown_a12.load()},
|
||||
{"UnknownA13", this->unknown_a13.load()},
|
||||
{"UnknownA14", this->unknown_a14.load()},
|
||||
{"UnknownA15", this->unknown_a15.load()},
|
||||
{"UnknownA16", this->unknown_a16.load()},
|
||||
});
|
||||
}
|
||||
|
||||
BattleParamsIndex::ResistData BattleParamsIndex::ResistData::from_json(const phosg::JSON& json) {
|
||||
return BattleParamsIndex::ResistData{
|
||||
json.get_int("EVPBonus"),
|
||||
json.get_int("EFR"),
|
||||
json.get_int("EIC"),
|
||||
json.get_int("ETH"),
|
||||
json.get_int("ELT"),
|
||||
json.get_int("EDK"),
|
||||
json.get_int("UnknownA6"),
|
||||
json.get_int("UnknownA7"),
|
||||
json.get_int("UnknownA8"),
|
||||
json.get_int("UnknownA9"),
|
||||
json.get_int("DFPBonus"),
|
||||
};
|
||||
}
|
||||
phosg::JSON BattleParamsIndex::ResistData::json() const {
|
||||
return phosg::JSON::dict({
|
||||
{"EVPBonus", this->evp_bonus.load()},
|
||||
{"EFR", this->efr.load()},
|
||||
{"EIC", this->eic.load()},
|
||||
{"ETH", this->eth.load()},
|
||||
{"ELT", this->elt.load()},
|
||||
{"EDK", this->edk.load()},
|
||||
{"UnknownA6", this->unknown_a6.load()},
|
||||
{"UnknownA7", this->unknown_a7.load()},
|
||||
{"UnknownA8", this->unknown_a8.load()},
|
||||
{"UnknownA9", this->unknown_a9.load()},
|
||||
{"DFPBonus", this->dfp_bonus.load()},
|
||||
});
|
||||
}
|
||||
|
||||
BattleParamsIndex::MovementData BattleParamsIndex::MovementData::from_json(const phosg::JSON& json) {
|
||||
const auto& fparams_json = json.at("FParams").as_list();
|
||||
const auto& iparams_json = json.at("IParams").as_list();
|
||||
return BattleParamsIndex::MovementData{
|
||||
fparams_json.at(0)->as_float(),
|
||||
fparams_json.at(1)->as_float(),
|
||||
fparams_json.at(2)->as_float(),
|
||||
fparams_json.at(3)->as_float(),
|
||||
fparams_json.at(4)->as_float(),
|
||||
fparams_json.at(5)->as_float(),
|
||||
iparams_json.at(0)->as_float(),
|
||||
iparams_json.at(1)->as_float(),
|
||||
iparams_json.at(2)->as_float(),
|
||||
iparams_json.at(3)->as_float(),
|
||||
iparams_json.at(4)->as_float(),
|
||||
iparams_json.at(5)->as_float(),
|
||||
};
|
||||
}
|
||||
phosg::JSON BattleParamsIndex::MovementData::json() const {
|
||||
auto fparams_list = phosg::JSON::list({
|
||||
this->fparam1.load(),
|
||||
this->fparam2.load(),
|
||||
this->fparam3.load(),
|
||||
this->fparam4.load(),
|
||||
this->fparam5.load(),
|
||||
this->fparam6.load(),
|
||||
});
|
||||
auto iparams_list = phosg::JSON::list({
|
||||
this->iparam1.load(),
|
||||
this->iparam2.load(),
|
||||
this->iparam3.load(),
|
||||
this->iparam4.load(),
|
||||
this->iparam5.load(),
|
||||
this->iparam6.load(),
|
||||
});
|
||||
return phosg::JSON::dict({{"FParams", std::move(fparams_list)}, {"IParams", std::move(iparams_list)}});
|
||||
}
|
||||
|
||||
BattleParamsIndex::Table BattleParamsIndex::Table::from_json(const phosg::JSON& json) {
|
||||
BattleParamsIndex::Table ret;
|
||||
for (Difficulty difficulty : ALL_DIFFICULTIES_V234) {
|
||||
const auto& diff_json = json.at(name_for_difficulty(difficulty));
|
||||
for (size_t z = 0; z < 0x60; z++) {
|
||||
const auto& entry_json = diff_json.at(z);
|
||||
ret.stats[static_cast<size_t>(difficulty)][z] = PlayerStats::from_json(entry_json.at("Stats"));
|
||||
ret.attack_data[static_cast<size_t>(difficulty)][z] = AttackData::from_json(entry_json.at("AttackData"));
|
||||
ret.resist_data[static_cast<size_t>(difficulty)][z] = ResistData::from_json(entry_json.at("ResistData"));
|
||||
ret.movement_data[static_cast<size_t>(difficulty)][z] = MovementData::from_json(entry_json.at("MovementData"));
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
phosg::JSON BattleParamsIndex::Table::json() const {
|
||||
auto ret = phosg::JSON::dict();
|
||||
for (Difficulty difficulty : ALL_DIFFICULTIES_V234) {
|
||||
auto diff_ret = phosg::JSON::list();
|
||||
for (size_t z = 0; z < 0x60; z++) {
|
||||
auto stats_json = this->stats_for_index(difficulty, z).json();
|
||||
auto attack_data_json = this->attack_data_for_index(difficulty, z).json();
|
||||
auto resist_data_json = this->resist_data_for_index(difficulty, z).json();
|
||||
auto movement_data_json = this->movement_data_for_index(difficulty, z).json();
|
||||
std::set<EnemyType> stats_names;
|
||||
std::set<EnemyType> attack_data_names;
|
||||
std::set<EnemyType> resist_data_names;
|
||||
std::set<EnemyType> movement_data_names;
|
||||
for (Episode episode : ALL_EPISODES_V4) {
|
||||
for (const auto& enemy_type : enemy_types_for_battle_param_stats_index(episode, z)) {
|
||||
stats_names.emplace(enemy_type);
|
||||
}
|
||||
for (const auto& enemy_type : enemy_types_for_battle_param_attack_data_index(episode, z)) {
|
||||
attack_data_names.emplace(enemy_type);
|
||||
}
|
||||
for (const auto& enemy_type : enemy_types_for_battle_param_resist_data_index(episode, z)) {
|
||||
resist_data_names.emplace(enemy_type);
|
||||
}
|
||||
for (const auto& enemy_type : enemy_types_for_battle_param_movement_data_index(episode, z)) {
|
||||
movement_data_names.emplace(enemy_type);
|
||||
}
|
||||
}
|
||||
auto stats_names_json = phosg::JSON::list();
|
||||
for (EnemyType enemy_type : stats_names) {
|
||||
stats_names_json.emplace_back(phosg::name_for_enum(enemy_type));
|
||||
}
|
||||
auto attack_data_names_json = phosg::JSON::list();
|
||||
for (EnemyType enemy_type : attack_data_names) {
|
||||
attack_data_names_json.emplace_back(phosg::name_for_enum(enemy_type));
|
||||
}
|
||||
auto resist_data_names_json = phosg::JSON::list();
|
||||
for (EnemyType enemy_type : resist_data_names) {
|
||||
resist_data_names_json.emplace_back(phosg::name_for_enum(enemy_type));
|
||||
}
|
||||
auto movement_data_names_json = phosg::JSON::list();
|
||||
for (EnemyType enemy_type : movement_data_names) {
|
||||
movement_data_names_json.emplace_back(phosg::name_for_enum(enemy_type));
|
||||
}
|
||||
stats_json.emplace("Enemies", std::move(stats_names_json));
|
||||
attack_data_json.emplace("Enemies", std::move(attack_data_names_json));
|
||||
resist_data_json.emplace("Enemies", std::move(resist_data_names_json));
|
||||
movement_data_json.emplace("Enemies", std::move(movement_data_names_json));
|
||||
diff_ret.emplace_back(phosg::JSON::dict({
|
||||
{"BPIndex", z},
|
||||
{"Stats", std::move(stats_json)},
|
||||
{"AttackData", std::move(attack_data_json)},
|
||||
{"ResistData", std::move(resist_data_json)},
|
||||
{"MovementData", std::move(movement_data_json)},
|
||||
}));
|
||||
}
|
||||
ret.emplace(name_for_difficulty(difficulty), std::move(diff_ret));
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void BattleParamsIndex::Table::print(FILE* stream, Episode episode) const {
|
||||
phosg::fwrite_fmt(stream, "========== STATS\n");
|
||||
for (Difficulty difficulty : ALL_DIFFICULTIES_V234) {
|
||||
@@ -27,7 +212,7 @@ void BattleParamsIndex::Table::print(FILE* stream, Episode episode) const {
|
||||
phosg::fwrite_fmt(stream,
|
||||
"{:5} {:5} {:5} {:5} {:5} {:5} {:5} {:5} {:5} {:5} {}",
|
||||
e.char_stats.atp, e.char_stats.mst, e.char_stats.evp, e.char_stats.hp, e.char_stats.dfp, e.char_stats.ata,
|
||||
e.char_stats.lck, e.esp, e.experience, e.meseta, names_str);
|
||||
e.char_stats.lck, e.esp, e.exp, e.meseta, names_str);
|
||||
fputc('\n', stream);
|
||||
}
|
||||
}
|
||||
@@ -76,7 +261,40 @@ void BattleParamsIndex::Table::print(FILE* stream, Episode episode) const {
|
||||
}
|
||||
}
|
||||
|
||||
BattleParamsIndex::BattleParamsIndex(
|
||||
phosg::JSON BattleParamsIndex::json() const {
|
||||
return phosg::JSON::dict({
|
||||
{"Episode1-Online", this->get_table(false, Episode::EP1).json()},
|
||||
{"Episode2-Online", this->get_table(false, Episode::EP2).json()},
|
||||
{"Episode4-Online", this->get_table(false, Episode::EP4).json()},
|
||||
{"Episode1-Solo", this->get_table(true, Episode::EP1).json()},
|
||||
{"Episode2-Solo", this->get_table(true, Episode::EP2).json()},
|
||||
{"Episode4-Solo", this->get_table(true, Episode::EP4).json()},
|
||||
});
|
||||
}
|
||||
|
||||
JSONBattleParamsIndex::JSONBattleParamsIndex(const phosg::JSON& json) {
|
||||
this->tables[0][0] = Table::from_json(json.at("Episode1-Online"));
|
||||
this->tables[0][1] = Table::from_json(json.at("Episode2-Online"));
|
||||
this->tables[0][2] = Table::from_json(json.at("Episode4-Online"));
|
||||
this->tables[1][0] = Table::from_json(json.at("Episode1-Solo"));
|
||||
this->tables[1][1] = Table::from_json(json.at("Episode2-Solo"));
|
||||
this->tables[1][2] = Table::from_json(json.at("Episode4-Solo"));
|
||||
}
|
||||
|
||||
const BattleParamsIndex::Table& JSONBattleParamsIndex::get_table(bool solo, Episode episode) const {
|
||||
switch (episode) {
|
||||
case Episode::EP1:
|
||||
return this->tables[!!solo][0];
|
||||
case Episode::EP2:
|
||||
return this->tables[!!solo][1];
|
||||
case Episode::EP4:
|
||||
return this->tables[!!solo][2];
|
||||
default:
|
||||
throw invalid_argument("invalid episode");
|
||||
}
|
||||
}
|
||||
|
||||
BinaryBattleParamsIndex::BinaryBattleParamsIndex(
|
||||
shared_ptr<const string> data_on_ep1,
|
||||
shared_ptr<const string> data_on_ep2,
|
||||
shared_ptr<const string> data_on_ep4,
|
||||
@@ -103,21 +321,15 @@ BattleParamsIndex::BattleParamsIndex(
|
||||
}
|
||||
}
|
||||
|
||||
const BattleParamsIndex::Table& BattleParamsIndex::get_table(bool solo, Episode episode) const {
|
||||
uint8_t ep_index;
|
||||
const BattleParamsIndex::Table& BinaryBattleParamsIndex::get_table(bool solo, Episode episode) const {
|
||||
switch (episode) {
|
||||
case Episode::EP1:
|
||||
ep_index = 0;
|
||||
break;
|
||||
return *this->files[!!solo][0].table;
|
||||
case Episode::EP2:
|
||||
ep_index = 1;
|
||||
break;
|
||||
return *this->files[!!solo][1].table;
|
||||
case Episode::EP4:
|
||||
ep_index = 2;
|
||||
break;
|
||||
return *this->files[!!solo][2].table;
|
||||
default:
|
||||
throw invalid_argument("invalid episode");
|
||||
}
|
||||
|
||||
return *this->files[!!solo][ep_index].table;
|
||||
}
|
||||
|
||||
+38
-10
@@ -35,7 +35,8 @@ public:
|
||||
/* 24 */ le_uint32_t unknown_a14;
|
||||
/* 28 */ le_uint32_t unknown_a15;
|
||||
/* 2C */ le_uint32_t unknown_a16;
|
||||
/* 30 */
|
||||
static AttackData from_json(const phosg::JSON& json);
|
||||
phosg::JSON json() const;
|
||||
} __packed_ws__(AttackData, 0x30);
|
||||
|
||||
struct ResistData {
|
||||
@@ -50,7 +51,8 @@ public:
|
||||
/* 14 */ le_uint32_t unknown_a8;
|
||||
/* 18 */ le_uint32_t unknown_a9;
|
||||
/* 1C */ le_int32_t dfp_bonus;
|
||||
/* 20 */
|
||||
static ResistData from_json(const phosg::JSON& json);
|
||||
phosg::JSON json() const;
|
||||
} __packed_ws__(ResistData, 0x20);
|
||||
|
||||
struct MovementData {
|
||||
@@ -66,7 +68,8 @@ public:
|
||||
/* 24 */ le_uint32_t iparam4;
|
||||
/* 28 */ le_uint32_t iparam5;
|
||||
/* 2C */ le_uint32_t iparam6;
|
||||
/* 30 */
|
||||
static MovementData from_json(const phosg::JSON& json);
|
||||
phosg::JSON json() const;
|
||||
} __packed_ws__(MovementData, 0x30);
|
||||
|
||||
struct Table {
|
||||
@@ -76,23 +79,48 @@ public:
|
||||
/* AE00 */ parray<parray<MovementData, 0x60>, 4> movement_data; // [difficulty][bp_index]
|
||||
/* F600 */
|
||||
|
||||
const PlayerStats& stats_for_index(Difficulty difficulty, uint8_t index) const {
|
||||
inline const PlayerStats& stats_for_index(Difficulty difficulty, uint8_t index) const {
|
||||
return this->stats.at(static_cast<size_t>(difficulty)).at(index);
|
||||
}
|
||||
const AttackData& attack_data_for_index(Difficulty difficulty, uint8_t index) const {
|
||||
inline const AttackData& attack_data_for_index(Difficulty difficulty, uint8_t index) const {
|
||||
return this->attack_data.at(static_cast<size_t>(difficulty)).at(index);
|
||||
}
|
||||
const ResistData& resist_data_for_index(Difficulty difficulty, uint8_t index) const {
|
||||
inline const ResistData& resist_data_for_index(Difficulty difficulty, uint8_t index) const {
|
||||
return this->resist_data.at(static_cast<size_t>(difficulty)).at(index);
|
||||
}
|
||||
const MovementData& movement_data_for_index(Difficulty difficulty, uint8_t index) const {
|
||||
inline const MovementData& movement_data_for_index(Difficulty difficulty, uint8_t index) const {
|
||||
return this->movement_data.at(static_cast<size_t>(difficulty)).at(index);
|
||||
}
|
||||
|
||||
static Table from_json(const phosg::JSON& json);
|
||||
phosg::JSON json() const;
|
||||
|
||||
void print(FILE* stream, Episode episode) const;
|
||||
} __packed_ws__(Table, 0xF600);
|
||||
|
||||
BattleParamsIndex(
|
||||
virtual ~BattleParamsIndex() = default;
|
||||
|
||||
virtual const Table& get_table(bool solo, Episode episode) const = 0;
|
||||
phosg::JSON json() const;
|
||||
|
||||
protected:
|
||||
BattleParamsIndex() = default;
|
||||
};
|
||||
|
||||
class JSONBattleParamsIndex : public BattleParamsIndex {
|
||||
public:
|
||||
explicit JSONBattleParamsIndex(const phosg::JSON& json);
|
||||
|
||||
virtual const Table& get_table(bool solo, Episode episode) const;
|
||||
|
||||
protected:
|
||||
// Indexed as [online/offline][episode]
|
||||
std::array<std::array<Table, 3>, 2> tables;
|
||||
};
|
||||
|
||||
class BinaryBattleParamsIndex : public BattleParamsIndex {
|
||||
public:
|
||||
BinaryBattleParamsIndex(
|
||||
std::shared_ptr<const std::string> data_on_ep1, // BattleParamEntry_on.dat
|
||||
std::shared_ptr<const std::string> data_on_ep2, // BattleParamEntry_lab_on.dat
|
||||
std::shared_ptr<const std::string> data_on_ep4, // BattleParamEntry_ep4_on.dat
|
||||
@@ -100,9 +128,9 @@ public:
|
||||
std::shared_ptr<const std::string> data_off_ep2, // BattleParamEntry_lab.dat
|
||||
std::shared_ptr<const std::string> data_off_ep4); // BattleParamEntry_ep4.dat
|
||||
|
||||
const Table& get_table(bool solo, Episode episode) const;
|
||||
virtual const Table& get_table(bool solo, Episode episode) const;
|
||||
|
||||
private:
|
||||
protected:
|
||||
struct File {
|
||||
std::shared_ptr<const std::string> data;
|
||||
const Table* table = nullptr;
|
||||
|
||||
+29
-27
@@ -547,7 +547,7 @@ static asio::awaitable<void> server_command_bbchar_savechar(const Args& a, bool
|
||||
bb_player->disp.stats.char_stats.dfp += bb_player->get_material_usage(PSOBBCharacterFile::MaterialType::DEF) * 2;
|
||||
bb_player->disp.stats.char_stats.lck += bb_player->get_material_usage(PSOBBCharacterFile::MaterialType::LUCK) * 2;
|
||||
bb_player->disp.stats.char_stats.hp += bb_player->get_material_usage(PSOBBCharacterFile::MaterialType::HP) * 2;
|
||||
bb_player->disp.stats.experience = ch.character->disp.stats.experience;
|
||||
bb_player->disp.stats.exp = ch.character->disp.stats.exp;
|
||||
bb_player->disp.stats.meseta = ch.character->disp.stats.meseta;
|
||||
}
|
||||
bb_player->disp.technique_levels_v1 = ch.character->disp.technique_levels_v1;
|
||||
@@ -922,7 +922,7 @@ ChatCommandDefinition cc_edit(
|
||||
} else if (tokens.at(0) == "meseta" && (cheats_allowed || !s->cheat_flags.edit_stats)) {
|
||||
p->disp.stats.meseta = stoul(tokens.at(1));
|
||||
} else if (tokens.at(0) == "exp" && (cheats_allowed || !s->cheat_flags.edit_stats)) {
|
||||
p->disp.stats.experience = stoul(tokens.at(1));
|
||||
p->disp.stats.exp = stoul(tokens.at(1));
|
||||
} else if (tokens.at(0) == "level" && (cheats_allowed || !s->cheat_flags.edit_stats)) {
|
||||
p->disp.stats.level = stoul(tokens.at(1)) - 1;
|
||||
p->recompute_stats(s->level_table(a.c->version()), true);
|
||||
@@ -1118,9 +1118,9 @@ ChatCommandDefinition cc_exit(
|
||||
a.c->check_flag(Client::Flag::SEND_FUNCTION_CALL_ACTUALLY_RUNS_CODE)) {
|
||||
co_await prepare_client_for_patches(a.c);
|
||||
auto s = a.c->require_server_state();
|
||||
shared_ptr<const CompiledFunctionCode> fn;
|
||||
shared_ptr<const ClientFunctionIndex::Function> fn;
|
||||
try {
|
||||
fn = s->function_code_index->get_patch("ExitAnywhere", a.c->specific_version);
|
||||
fn = s->client_functions->get("ExitAnywhere", a.c->specific_version);
|
||||
} catch (const out_of_range&) {
|
||||
}
|
||||
if (fn) {
|
||||
@@ -1596,7 +1596,7 @@ ChatCommandDefinition cc_loadchar(
|
||||
auto send_set_extended_player_info = [&a, &s, &send_proxy_lobby_refresh]<typename CharT>(const CharT& char_file) -> asio::awaitable<void> {
|
||||
co_await prepare_client_for_patches(a.c);
|
||||
try {
|
||||
auto fn = s->function_code_index->get_patch("SetExtendedPlayerInfo", a.c->specific_version);
|
||||
auto fn = s->client_functions->get("SetExtendedPlayerInfo", a.c->specific_version);
|
||||
co_await send_function_call(a.c, fn, {}, &char_file, sizeof(CharT));
|
||||
auto l = a.c->lobby.lock();
|
||||
if (l) {
|
||||
@@ -1738,7 +1738,7 @@ ChatCommandDefinition cc_makeobj(
|
||||
|
||||
co_await prepare_client_for_patches(a.c);
|
||||
auto s = a.c->require_server_state();
|
||||
auto fn = s->function_code_index->get_patch("CreateObject", a.c->specific_version);
|
||||
auto fn = s->client_functions->get("CreateObject", a.c->specific_version);
|
||||
co_await send_function_call(a.c, fn, label_writes);
|
||||
});
|
||||
|
||||
@@ -1878,14 +1878,31 @@ ChatCommandDefinition cc_patch(
|
||||
try {
|
||||
auto s = a.c->require_server_state();
|
||||
// Note: We can't look this up before prepare_client_for_patches because specific_version may not be set
|
||||
auto fn = s->function_code_index->get_patch(patch_name, a.c->specific_version);
|
||||
auto fn = s->client_functions->get(patch_name, a.c->specific_version);
|
||||
|
||||
switch (fn->visibility) {
|
||||
case ClientFunctionIndex::Function::Visibility::DEBUG_ONLY:
|
||||
case ClientFunctionIndex::Function::Visibility::PATCHES_MENU_ONLY:
|
||||
a.check_debug_enabled();
|
||||
break;
|
||||
case ClientFunctionIndex::Function::Visibility::CHAT_COMMAND_ONLY_WITH_CHEAT_MODE:
|
||||
a.check_cheats_enabled_or_allowed(true);
|
||||
break;
|
||||
case ClientFunctionIndex::Function::Visibility::CHAT_COMMAND_ONLY:
|
||||
case ClientFunctionIndex::Function::Visibility::PATCHES_MENU_AND_CHAT_COMMAND:
|
||||
break;
|
||||
default:
|
||||
throw std::logic_error("Invalid client function visibility");
|
||||
}
|
||||
|
||||
auto ret = co_await send_function_call(a.c, fn, label_writes);
|
||||
if (fn->show_return_value) {
|
||||
send_text_message_fmt(a.c, "$C6Return value:$C7\nInt: {}\nHex: {:08X}\nFloat: {:g}",
|
||||
ret.return_value.load(), ret.return_value.load(), std::bit_cast<float>(ret.return_value.load()));
|
||||
}
|
||||
|
||||
} catch (const out_of_range&) {
|
||||
send_text_message(a.c, "$C6Invalid patch name");
|
||||
send_text_message(a.c, "$C6Invalid function");
|
||||
}
|
||||
co_return;
|
||||
});
|
||||
@@ -2308,15 +2325,10 @@ ChatCommandDefinition cc_readmem(
|
||||
|
||||
co_await prepare_client_for_patches(a.c);
|
||||
|
||||
shared_ptr<const CompiledFunctionCode> fn;
|
||||
shared_ptr<const ClientFunctionIndex::Function> fn;
|
||||
try {
|
||||
auto s = a.c->require_server_state();
|
||||
const char* function_name = is_dc(a.c->version())
|
||||
? "ReadMemoryWordDC"
|
||||
: is_gc(a.c->version())
|
||||
? "ReadMemoryWordGC"
|
||||
: "ReadMemoryWordX86";
|
||||
fn = s->function_code_index->name_to_function.at(function_name);
|
||||
fn = s->client_functions->get("ReadMemoryWord", a.c->specific_version);
|
||||
} catch (const out_of_range&) {
|
||||
throw precondition_failed("Invalid patch name");
|
||||
}
|
||||
@@ -3170,12 +3182,7 @@ ChatCommandDefinition cc_writemem(
|
||||
|
||||
try {
|
||||
auto s = a.c->require_server_state();
|
||||
const char* function_name = is_dc(a.c->version())
|
||||
? "WriteMemoryDC"
|
||||
: is_gc(a.c->version())
|
||||
? "WriteMemoryGC"
|
||||
: "WriteMemoryX86";
|
||||
auto fn = s->function_code_index->name_to_function.at(function_name);
|
||||
auto fn = s->client_functions->get("WriteMemory", a.c->specific_version);
|
||||
unordered_map<string, uint32_t> label_writes{{"dest_addr", addr}, {"size", data.size()}};
|
||||
co_await send_function_call(a.c, fn, label_writes, data.data(), data.size());
|
||||
} catch (const out_of_range&) {
|
||||
@@ -3215,12 +3222,7 @@ ChatCommandDefinition cc_nativecall(
|
||||
|
||||
try {
|
||||
auto s = a.c->require_server_state();
|
||||
const char* function_name = is_dc(a.c->version())
|
||||
? "CallNativeFunctionDC"
|
||||
: is_gc(a.c->version())
|
||||
? "CallNativeFunctionGC"
|
||||
: "CallNativeFunctionX86";
|
||||
auto fn = s->function_code_index->name_to_function.at(function_name);
|
||||
auto fn = s->client_functions->get("CallNativeFunction", a.c->specific_version);
|
||||
co_await send_function_call(a.c, fn, label_writes);
|
||||
} catch (const out_of_range&) {
|
||||
throw precondition_failed("Invalid patch name");
|
||||
|
||||
+1
-1
@@ -703,7 +703,7 @@ void Client::create_challenge_overlay(
|
||||
overlay->disp.visual.char_class, overlay->disp.stats.level);
|
||||
overlay->disp.stats.esp = 40;
|
||||
overlay->disp.stats.attack_range = 10.0;
|
||||
overlay->disp.stats.experience = stats_delta.experience;
|
||||
overlay->disp.stats.exp = stats_delta.exp;
|
||||
overlay->disp.stats.meseta = 0;
|
||||
overlay->clear_all_material_usage();
|
||||
for (size_t z = 0; z < 0x13; z++) {
|
||||
|
||||
+1
-1
@@ -6,11 +6,11 @@
|
||||
#include "Account.hh"
|
||||
#include "AsyncUtils.hh"
|
||||
#include "Channel.hh"
|
||||
#include "ClientFunctionIndex.hh"
|
||||
#include "CommandFormats.hh"
|
||||
#include "Episode3/BattleRecord.hh"
|
||||
#include "Episode3/Tournament.hh"
|
||||
#include "FileContentsCache.hh"
|
||||
#include "FunctionCompiler.hh"
|
||||
#include "PSOEncryption.hh"
|
||||
#include "PSOProtocol.hh"
|
||||
#include "PatchFileIndex.hh"
|
||||
|
||||
@@ -0,0 +1,558 @@
|
||||
#include "ClientFunctionIndex.hh"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <filesystem>
|
||||
#include <phosg/Filesystem.hh>
|
||||
#include <phosg/Hash.hh>
|
||||
#include <phosg/Time.hh>
|
||||
#include <stdexcept>
|
||||
|
||||
#include <resource_file/Emulators/PPC32Emulator.hh>
|
||||
#include <resource_file/Emulators/SH4Emulator.hh>
|
||||
#include <resource_file/Emulators/X86Emulator.hh>
|
||||
|
||||
#include "CommandFormats.hh"
|
||||
#include "CommonFileFormats.hh"
|
||||
#include "Compression.hh"
|
||||
#include "Loggers.hh"
|
||||
|
||||
using namespace std;
|
||||
|
||||
using Arch = ClientFunctionIndex::Function::Architecture;
|
||||
|
||||
const char* name_for_architecture(Arch arch) {
|
||||
switch (arch) {
|
||||
case Arch::SH4:
|
||||
return "SH-4";
|
||||
case Arch::POWERPC:
|
||||
return "PowerPC";
|
||||
case Arch::X86:
|
||||
return "x86";
|
||||
default:
|
||||
throw logic_error("invalid architecture");
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t specific_version_for_architecture(Arch arch) {
|
||||
switch (arch) {
|
||||
case Arch::SH4:
|
||||
return SPECIFIC_VERSION_SH4_INDETERMINATE;
|
||||
case Arch::POWERPC:
|
||||
return SPECIFIC_VERSION_PPC_INDETERMINATE;
|
||||
case Arch::X86:
|
||||
return SPECIFIC_VERSION_X86_INDETERMINATE;
|
||||
default:
|
||||
throw logic_error("invalid architecture");
|
||||
}
|
||||
}
|
||||
|
||||
Arch architecture_for_specific_version(uint32_t specific_version) {
|
||||
if (specific_version == SPECIFIC_VERSION_SH4_INDETERMINATE) {
|
||||
return Arch::SH4;
|
||||
} else if (specific_version == SPECIFIC_VERSION_PPC_INDETERMINATE) {
|
||||
return Arch::POWERPC;
|
||||
} else if (specific_version == SPECIFIC_VERSION_X86_INDETERMINATE) {
|
||||
return Arch::X86;
|
||||
} else if (specific_version_is_dc(specific_version)) {
|
||||
return Arch::SH4;
|
||||
} else if (specific_version_is_gc(specific_version)) {
|
||||
return Arch::POWERPC;
|
||||
} else {
|
||||
return Arch::X86;
|
||||
}
|
||||
}
|
||||
|
||||
static inline std::string cache_key(const std::string& name, uint32_t specific_version) {
|
||||
return std::format("{}-{:08X}", name, specific_version);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
const T& get_with_sv_fallback(
|
||||
const std::unordered_map<std::string, T>& index, const std::string& name, uint32_t specific_version) {
|
||||
try {
|
||||
return index.at(cache_key(name, specific_version));
|
||||
} catch (const std::out_of_range&) {
|
||||
}
|
||||
uint32_t arch_specific_version = specific_version_for_architecture(architecture_for_specific_version(
|
||||
specific_version));
|
||||
if (arch_specific_version != specific_version) {
|
||||
try {
|
||||
return index.at(cache_key(name, arch_specific_version));
|
||||
} catch (const std::out_of_range&) {
|
||||
}
|
||||
}
|
||||
return index.at(name);
|
||||
}
|
||||
|
||||
template <bool BE>
|
||||
string ClientFunctionIndex::Function::generate_client_command_t(
|
||||
const unordered_map<string, uint32_t>& label_writes,
|
||||
const void* suffix_data,
|
||||
size_t suffix_size,
|
||||
uint32_t override_relocations_offset) const {
|
||||
using FooterT = RELFileFooterT<BE>;
|
||||
|
||||
FooterT footer;
|
||||
footer.num_relocations = this->relocation_deltas.size();
|
||||
footer.unused1.clear(0);
|
||||
footer.root_offset = this->entrypoint_offset_offset;
|
||||
footer.unused2.clear(0);
|
||||
|
||||
phosg::StringWriter w;
|
||||
if (!label_writes.empty()) {
|
||||
string modified_code = this->code;
|
||||
for (const auto& it : label_writes) {
|
||||
size_t offset = this->label_offsets.at(it.first);
|
||||
if (offset > modified_code.size() - 4) {
|
||||
throw runtime_error("label out of range");
|
||||
}
|
||||
*reinterpret_cast<U32T<FooterT::IsBE>*>(modified_code.data() + offset) = it.second;
|
||||
}
|
||||
w.write(modified_code);
|
||||
} else {
|
||||
w.write(this->code);
|
||||
}
|
||||
if (suffix_size) {
|
||||
w.write(suffix_data, suffix_size);
|
||||
}
|
||||
while (w.size() & 3) {
|
||||
w.put_u8(0);
|
||||
}
|
||||
|
||||
footer.relocations_offset = w.size();
|
||||
|
||||
// Always write at least 4 bytes even if there are no relocations
|
||||
if (this->relocation_deltas.empty()) {
|
||||
w.put_u32(0);
|
||||
}
|
||||
|
||||
if (override_relocations_offset) {
|
||||
footer.relocations_offset = override_relocations_offset;
|
||||
} else {
|
||||
for (uint16_t delta : this->relocation_deltas) {
|
||||
w.put<U16T<FooterT::IsBE>>(delta);
|
||||
}
|
||||
if (this->relocation_deltas.size() & 1) {
|
||||
w.put_u16(0);
|
||||
}
|
||||
}
|
||||
|
||||
w.put(footer);
|
||||
return std::move(w.str());
|
||||
}
|
||||
|
||||
string ClientFunctionIndex::Function::generate_client_command(
|
||||
const unordered_map<string, uint32_t>& label_writes,
|
||||
const void* suffix_data,
|
||||
size_t suffix_size,
|
||||
uint32_t override_relocations_offset) const {
|
||||
if (this->is_big_endian()) {
|
||||
return this->generate_client_command_t<true>(label_writes, suffix_data, suffix_size, override_relocations_offset);
|
||||
} else if ((this->arch == Architecture::X86) || (this->arch == Architecture::SH4)) {
|
||||
return this->generate_client_command_t<false>(label_writes, suffix_data, suffix_size, override_relocations_offset);
|
||||
} else {
|
||||
throw logic_error("invalid architecture");
|
||||
}
|
||||
}
|
||||
|
||||
static unordered_map<uint32_t, std::string> preprocess_function_code(const std::string& text) {
|
||||
std::unordered_set<uint32_t> all_specific_versions;
|
||||
struct Line {
|
||||
std::string text;
|
||||
std::unordered_map<uint32_t, size_t> new_specific_versions; // Nonempty iff line is a .versions directive
|
||||
bool enable_all_versions = false;
|
||||
};
|
||||
|
||||
std::vector<Line> lines;
|
||||
for (auto& line_text : phosg::split(text, '\n')) {
|
||||
auto& line = lines.emplace_back();
|
||||
line.text = std::move(line_text);
|
||||
|
||||
string stripped_line = line.text;
|
||||
phosg::strip_whitespace(stripped_line);
|
||||
|
||||
if (stripped_line == ".all_versions") {
|
||||
line.enable_all_versions = true;
|
||||
} else if (stripped_line.starts_with(".versions ")) {
|
||||
for (auto& vers_token : phosg::split(stripped_line.substr(10), ' ')) {
|
||||
phosg::strip_whitespace(vers_token);
|
||||
if (!vers_token.empty()) {
|
||||
uint32_t specific_version = specific_version_for_str(vers_token);
|
||||
size_t version_index = line.new_specific_versions.size();
|
||||
all_specific_versions.emplace(specific_version);
|
||||
line.new_specific_versions.emplace(std::move(specific_version), version_index);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static const std::string empty_str = "";
|
||||
|
||||
unordered_map<uint32_t, std::string> ret;
|
||||
for (uint32_t specific_version : all_specific_versions) {
|
||||
std::deque<std::string> version_lines;
|
||||
bool include_current_line = true;
|
||||
size_t current_vers_index = all_specific_versions.size();
|
||||
for (size_t line_znum = 0; line_znum < lines.size(); line_znum++) {
|
||||
const auto& line = lines[line_znum];
|
||||
|
||||
if (line.enable_all_versions) {
|
||||
include_current_line = true;
|
||||
current_vers_index = all_specific_versions.size();
|
||||
version_lines.emplace_back(empty_str);
|
||||
|
||||
} else if (!line.new_specific_versions.empty()) {
|
||||
auto it = line.new_specific_versions.find(specific_version);
|
||||
if (it == line.new_specific_versions.end()) {
|
||||
include_current_line = false;
|
||||
current_vers_index = all_specific_versions.size();
|
||||
} else {
|
||||
include_current_line = true;
|
||||
current_vers_index = it->second;
|
||||
}
|
||||
version_lines.emplace_back(empty_str);
|
||||
|
||||
} else if (!include_current_line) {
|
||||
version_lines.emplace_back(empty_str);
|
||||
|
||||
} else {
|
||||
std::string line_text = line.text;
|
||||
size_t vers_offset = line_text.find("<VERS ");
|
||||
while (vers_offset != string::npos) {
|
||||
size_t end_offset = line_text.find('>', vers_offset + 6);
|
||||
if (end_offset == string::npos) {
|
||||
throw runtime_error(std::format("(version {}) (line {}) unterminated <VERS> replacement",
|
||||
str_for_specific_version(specific_version), line_znum + 1));
|
||||
}
|
||||
auto tokens = phosg::split(line_text.substr(vers_offset + 6, end_offset - vers_offset - 6), ' ');
|
||||
if (current_vers_index >= tokens.size()) {
|
||||
throw runtime_error(std::format("(version {}) (line {}) invalid <VERS> replacement",
|
||||
str_for_specific_version(specific_version), line_znum + 1));
|
||||
}
|
||||
line_text = line_text.substr(0, vers_offset) + tokens[current_vers_index] + line_text.substr(end_offset + 1);
|
||||
vers_offset = line_text.find("<VERS ");
|
||||
}
|
||||
version_lines.emplace_back(std::move(line_text));
|
||||
}
|
||||
}
|
||||
ret.emplace(specific_version, phosg::join(version_lines, "\n"));
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
ClientFunctionIndex::ClientFunctionIndex(const string& root_dir, bool raise_on_any_failure) {
|
||||
map<string, string> source_files;
|
||||
std::function<void(const std::string&)> add_directory = [&](const std::string& dir) -> void {
|
||||
for (const auto& item : std::filesystem::directory_iterator(dir)) {
|
||||
string item_name = item.path().filename().string();
|
||||
string item_path = dir.ends_with("/") ? (dir + item_name) : (dir + "/" + item_name);
|
||||
if (std::filesystem::is_directory(item_path)) {
|
||||
add_directory(item_path);
|
||||
} else if (item_path.ends_with(".s") && std::filesystem::is_regular_file(item_path)) {
|
||||
client_functions_log.debug_f("Adding {} from {}", item_name, item_path);
|
||||
if (!source_files.emplace(item_name, phosg::load_file(item_path)).second) {
|
||||
throw std::runtime_error(std::format("Duplicate source filename: {}", item_name));
|
||||
}
|
||||
} else if (item_path.ends_with(".bin") && std::filesystem::is_regular_file(item_path)) {
|
||||
client_functions_log.debug_f("Adding {} from {}", item_name, item_path);
|
||||
if (!source_files.emplace(item_name, phosg::load_file(item_path)).second) {
|
||||
throw std::runtime_error(std::format("Duplicate binary filename: {}", item_name));
|
||||
}
|
||||
} else {
|
||||
client_functions_log.debug_f("Ignoring {}", item_path);
|
||||
}
|
||||
}
|
||||
};
|
||||
add_directory(root_dir);
|
||||
|
||||
unordered_map<string, string> include_cache;
|
||||
uint32_t last_menu_item_id = 0;
|
||||
for (const auto& [source_filename, source] : source_files) {
|
||||
if (!source_filename.ends_with(".s")) {
|
||||
client_functions_log.debug_f("Skipping root compile for {} because it is not a .s file", source_filename);
|
||||
continue;
|
||||
}
|
||||
if (source_filename.ends_with(".inc.s")) {
|
||||
client_functions_log.debug_f("Skipping root compile for {} because it is an include", source_filename);
|
||||
continue;
|
||||
}
|
||||
|
||||
std::unordered_map<uint32_t, std::string> preprocessed;
|
||||
try {
|
||||
preprocessed = preprocess_function_code(source);
|
||||
} catch (const std::exception& e) {
|
||||
throw std::runtime_error(std::format("({} preprocessing) {}", source_filename, e.what()));
|
||||
}
|
||||
|
||||
for (const auto& [specific_version, source] : preprocessed) {
|
||||
shared_ptr<Function> fn = make_shared<Function>();
|
||||
fn->short_name = source_filename.substr(0, source_filename.size() - 2);
|
||||
fn->specific_version = specific_version;
|
||||
fn->menu_item_id = ++last_menu_item_id;
|
||||
fn->arch = architecture_for_specific_version(fn->specific_version);
|
||||
|
||||
try {
|
||||
unordered_set<string> get_include_stack;
|
||||
function<string(const string&, uint32_t)> get_include_for_sv = [&include_cache, &source_files, &get_include_stack, &get_include_for_sv](const string& name, uint32_t specific_version) -> string {
|
||||
try {
|
||||
return get_with_sv_fallback(include_cache, name, specific_version);
|
||||
} catch (const std::out_of_range&) {
|
||||
}
|
||||
if (client_functions_log.should_log(phosg::LogLevel::L_DEBUG)) {
|
||||
client_functions_log.debug_f("({}) Include {}-{} needs to be compiled",
|
||||
get_include_stack.size(), name, str_for_specific_version(specific_version));
|
||||
}
|
||||
|
||||
auto it = source_files.find(name + ".inc.s");
|
||||
if (it != source_files.end()) {
|
||||
if (!get_include_stack.emplace(name).second) {
|
||||
throw runtime_error("Mutual recursion between includes: " + name);
|
||||
}
|
||||
for (const auto& [include_specific_version, include_source] : preprocess_function_code(it->second)) {
|
||||
ResourceDASM::EmulatorBase::AssembleResult ret;
|
||||
auto get_include = std::bind(get_include_for_sv, std::placeholders::_1, include_specific_version);
|
||||
switch (architecture_for_specific_version(include_specific_version)) {
|
||||
case Arch::POWERPC:
|
||||
ret = ResourceDASM::PPC32Emulator::assemble(include_source, get_include);
|
||||
break;
|
||||
case Arch::X86:
|
||||
ret = ResourceDASM::X86Emulator::assemble(include_source, get_include);
|
||||
break;
|
||||
case Arch::SH4:
|
||||
ret = ResourceDASM::SH4Emulator::assemble(include_source, get_include);
|
||||
break;
|
||||
default:
|
||||
throw runtime_error("unknown architecture");
|
||||
}
|
||||
if (client_functions_log.should_log(phosg::LogLevel::L_DEBUG)) {
|
||||
client_functions_log.debug_f("({}) Compiled include {}-{}",
|
||||
get_include_stack.size(), name, str_for_specific_version(include_specific_version));
|
||||
}
|
||||
include_cache.emplace(cache_key(name, include_specific_version), std::move(ret.code));
|
||||
}
|
||||
get_include_stack.erase(name);
|
||||
|
||||
} else {
|
||||
it = source_files.find(name + ".inc.bin");
|
||||
if (it != source_files.end()) {
|
||||
include_cache.emplace(name, it->second).first->second;
|
||||
client_functions_log.debug_f("({}) Cached binary include {}", get_include_stack.size(), name);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
return get_with_sv_fallback(include_cache, name, specific_version);
|
||||
} catch (const std::out_of_range&) {
|
||||
}
|
||||
throw runtime_error(std::format(
|
||||
"Data not found for include {} ({})", name, str_for_specific_version(specific_version)));
|
||||
};
|
||||
|
||||
try {
|
||||
ResourceDASM::EmulatorBase::AssembleResult assembled;
|
||||
auto get_include = std::bind(get_include_for_sv, std::placeholders::_1, specific_version);
|
||||
switch (fn->arch) {
|
||||
case Arch::POWERPC:
|
||||
assembled = ResourceDASM::PPC32Emulator::assemble(source, get_include);
|
||||
break;
|
||||
case Arch::X86:
|
||||
assembled = ResourceDASM::X86Emulator::assemble(source, get_include);
|
||||
break;
|
||||
case Arch::SH4:
|
||||
assembled = ResourceDASM::SH4Emulator::assemble(source, get_include);
|
||||
break;
|
||||
default:
|
||||
throw runtime_error("invalid architecture");
|
||||
}
|
||||
|
||||
fn->code = std::move(assembled.code);
|
||||
fn->label_offsets = std::move(assembled.label_offsets);
|
||||
for (const auto& [key, value] : assembled.metadata_keys) {
|
||||
if (key == "visibility") {
|
||||
if (value == "hidden") {
|
||||
fn->visibility = Function::Visibility::DEBUG_ONLY;
|
||||
} else if (value == "cheat") {
|
||||
fn->visibility = Function::Visibility::CHAT_COMMAND_ONLY_WITH_CHEAT_MODE;
|
||||
} else if (value == "chat") {
|
||||
fn->visibility = Function::Visibility::CHAT_COMMAND_ONLY;
|
||||
} else if (value == "menu") {
|
||||
fn->visibility = Function::Visibility::PATCHES_MENU_ONLY;
|
||||
} else if (value == "all") {
|
||||
fn->visibility = Function::Visibility::PATCHES_MENU_AND_CHAT_COMMAND;
|
||||
} else {
|
||||
throw std::runtime_error("Invalid visibility value");
|
||||
}
|
||||
} else if (key == "key") {
|
||||
fn->short_name = value;
|
||||
} else if (key == "name") {
|
||||
fn->long_name = value;
|
||||
} else if (key == "description") {
|
||||
fn->description = value;
|
||||
} else if (key == "client_flag") {
|
||||
fn->client_flag = stoull(value, nullptr, 0);
|
||||
} else if (key == "show_return_value") {
|
||||
fn->show_return_value = true;
|
||||
} else {
|
||||
throw runtime_error("unknown metadata key: " + key);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
fn->entrypoint_offset_offset = fn->label_offsets.at("entry_ptr");
|
||||
} catch (const out_of_range&) {
|
||||
throw runtime_error("code does not contain entry_ptr label");
|
||||
}
|
||||
|
||||
set<uint32_t> reloc_indexes;
|
||||
for (const auto& it : fn->label_offsets) {
|
||||
if (it.first.starts_with("reloc")) {
|
||||
reloc_indexes.emplace(it.second / 4);
|
||||
}
|
||||
}
|
||||
uint32_t prev_index = 0;
|
||||
for (const auto& it : reloc_indexes) {
|
||||
uint32_t delta = it - prev_index;
|
||||
if (delta > 0xFFFF) {
|
||||
throw runtime_error("relocation delta too far away");
|
||||
}
|
||||
fn->relocation_deltas.emplace_back(delta);
|
||||
prev_index = it;
|
||||
}
|
||||
|
||||
} catch (const exception& e) {
|
||||
if (raise_on_any_failure) {
|
||||
throw;
|
||||
}
|
||||
client_functions_log.warning_f("Failed to compile function {} ({}): {}",
|
||||
fn->short_name, str_for_specific_version(specific_version), e.what());
|
||||
}
|
||||
|
||||
auto key = cache_key(fn->short_name, specific_version);
|
||||
if (!this->all_functions.emplace(key, fn).second) {
|
||||
throw std::runtime_error("Duplicate function key: " + key);
|
||||
}
|
||||
this->functions_by_specific_version[specific_version].emplace(key, fn);
|
||||
this->functions_by_menu_item_id.emplace(fn->menu_item_id, fn);
|
||||
|
||||
client_functions_log.debug_f("Compiled function {} ({}; {}; {})",
|
||||
fn->short_name, str_for_specific_version(fn->specific_version), name_for_architecture(fn->arch),
|
||||
phosg::name_for_enum(fn->visibility));
|
||||
} catch (const std::exception& e) {
|
||||
throw std::runtime_error(std::format(
|
||||
"({}-{}) {}", fn->short_name, str_for_specific_version(specific_version), e.what()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
shared_ptr<const Menu> ClientFunctionIndex::patch_switches_menu(
|
||||
uint32_t specific_version,
|
||||
const std::unordered_set<std::string>& server_auto_patches_enabled,
|
||||
const std::unordered_set<std::string>& client_auto_patches_enabled) const {
|
||||
auto ret = make_shared<Menu>(MenuID::PATCH_SWITCHES, "Patches");
|
||||
ret->items.emplace_back(PatchesMenuItemID::GO_BACK, "Go back", "Return to the\nmain menu", 0);
|
||||
|
||||
auto map_it = this->functions_by_specific_version.find(specific_version);
|
||||
if (map_it != this->functions_by_specific_version.end()) {
|
||||
for (auto [name, fn] : map_it->second) {
|
||||
if (fn->appears_in_patches_menu() && !server_auto_patches_enabled.count(fn->short_name)) {
|
||||
string item_text;
|
||||
item_text.push_back(client_auto_patches_enabled.count(fn->short_name) ? '*' : '-');
|
||||
item_text += fn->long_name.empty() ? fn->short_name : fn->long_name;
|
||||
ret->items.emplace_back(
|
||||
fn->menu_item_id, item_text, fn->description, MenuItem::Flag::REQUIRES_SEND_FUNCTION_CALL_RUNS_CODE);
|
||||
}
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool ClientFunctionIndex::patch_menu_empty(uint32_t specific_version) const {
|
||||
uint32_t mask = specific_version_is_indeterminate(specific_version) ? 0xFF000000 : 0xFFFFFFFF;
|
||||
auto it = this->functions_by_specific_version.lower_bound(specific_version & mask);
|
||||
return ((it == this->functions_by_specific_version.end()) || ((it->first & mask) != (specific_version & mask)));
|
||||
}
|
||||
|
||||
std::shared_ptr<const ClientFunctionIndex::Function> ClientFunctionIndex::get(
|
||||
const std::string& name, uint32_t specific_version) const {
|
||||
return get_with_sv_fallback(this->all_functions, name, specific_version);
|
||||
}
|
||||
|
||||
std::shared_ptr<const ClientFunctionIndex::Function> ClientFunctionIndex::get(
|
||||
const std::string& name, Arch arch) const {
|
||||
return get_with_sv_fallback(this->all_functions, name, specific_version_for_architecture(arch));
|
||||
}
|
||||
|
||||
std::shared_ptr<const ClientFunctionIndex::Function> ClientFunctionIndex::get(const std::string& name) const {
|
||||
return this->all_functions.at(name);
|
||||
}
|
||||
|
||||
std::shared_ptr<const ClientFunctionIndex::Function> ClientFunctionIndex::get_by_menu_item_id(
|
||||
uint32_t menu_item_id) const {
|
||||
return this->functions_by_menu_item_id.at(menu_item_id);
|
||||
}
|
||||
|
||||
uint32_t specific_version_for_gc_header_checksum(uint32_t header_checksum) {
|
||||
static unordered_map<uint32_t, uint32_t> checksum_to_specific_version;
|
||||
if (checksum_to_specific_version.empty()) {
|
||||
struct {
|
||||
char system_code = 'G';
|
||||
char game_code1 = 'P';
|
||||
char game_code2;
|
||||
char region_code;
|
||||
char developer_code1 = '8';
|
||||
char developer_code2 = 'P';
|
||||
uint8_t disc_number = 0;
|
||||
uint8_t version_code;
|
||||
} __attribute__((packed)) data;
|
||||
for (const char* game_code2 = "OS"; *game_code2; game_code2++) {
|
||||
data.game_code2 = *game_code2;
|
||||
for (const char* region_code = "JEP"; *region_code; region_code++) {
|
||||
data.region_code = *region_code;
|
||||
for (uint8_t version_code = 0; version_code < 8; version_code++) {
|
||||
data.version_code = version_code;
|
||||
uint32_t checksum = phosg::crc32(&data, sizeof(data));
|
||||
uint32_t specific_version = 0x33000030 | (*game_code2 << 16) | (*region_code << 8) | version_code;
|
||||
if (!checksum_to_specific_version.emplace(checksum, specific_version).second) {
|
||||
throw logic_error("multiple specific_versions have same header checksum");
|
||||
}
|
||||
}
|
||||
}
|
||||
{
|
||||
// Generate entries for Trial Editions
|
||||
data.region_code = 'J';
|
||||
data.system_code = 'D';
|
||||
data.version_code = 0;
|
||||
uint32_t checksum = phosg::crc32(&data, sizeof(data));
|
||||
uint32_t specific_version = 0x33004A54 | (*game_code2 << 16);
|
||||
if (!checksum_to_specific_version.emplace(checksum, specific_version).second) {
|
||||
throw logic_error("multiple specific_versions have same header checksum");
|
||||
}
|
||||
data.system_code = 'G';
|
||||
}
|
||||
}
|
||||
}
|
||||
return checksum_to_specific_version.at(header_checksum);
|
||||
}
|
||||
|
||||
template <>
|
||||
const char* phosg::name_for_enum<ClientFunctionIndex::Function::Visibility>(
|
||||
ClientFunctionIndex::Function::Visibility vis) {
|
||||
switch (vis) {
|
||||
case ClientFunctionIndex::Function::Visibility::DEBUG_ONLY:
|
||||
return "DEBUG_ONLY";
|
||||
case ClientFunctionIndex::Function::Visibility::CHAT_COMMAND_ONLY_WITH_CHEAT_MODE:
|
||||
return "CHAT_COMMAND_ONLY_WITH_CHEAT_MODE";
|
||||
case ClientFunctionIndex::Function::Visibility::CHAT_COMMAND_ONLY:
|
||||
return "CHAT_COMMAND_ONLY";
|
||||
case ClientFunctionIndex::Function::Visibility::PATCHES_MENU_ONLY:
|
||||
return "PATCHES_MENU_ONLY";
|
||||
case ClientFunctionIndex::Function::Visibility::PATCHES_MENU_AND_CHAT_COMMAND:
|
||||
return "PATCHES_MENU_AND_CHAT_COMMAND";
|
||||
default:
|
||||
throw std::logic_error("Invalid client function visibility");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
#pragma once
|
||||
|
||||
#include <inttypes.h>
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <phosg/Strings.hh>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
#include "Menu.hh"
|
||||
|
||||
class ClientFunctionIndex {
|
||||
public:
|
||||
struct Function {
|
||||
enum class Architecture {
|
||||
UNKNOWN = 0,
|
||||
POWERPC, // GC
|
||||
X86, // PC, XB, BB
|
||||
SH4, // Dreamcast
|
||||
};
|
||||
Architecture arch = Architecture::UNKNOWN;
|
||||
std::string code;
|
||||
std::vector<uint16_t> relocation_deltas;
|
||||
std::unordered_map<std::string, uint32_t> label_offsets;
|
||||
uint32_t entrypoint_offset_offset = 0;
|
||||
std::string short_name; // Based on filename
|
||||
std::string long_name; // From .meta name directive
|
||||
std::string description; // From .meta description directive
|
||||
uint64_t client_flag = 0; // From .meta client_flag directive
|
||||
uint32_t menu_item_id = 0;
|
||||
enum class Visibility {
|
||||
DEBUG_ONLY = 0,
|
||||
CHAT_COMMAND_ONLY_WITH_CHEAT_MODE,
|
||||
CHAT_COMMAND_ONLY,
|
||||
PATCHES_MENU_ONLY,
|
||||
PATCHES_MENU_AND_CHAT_COMMAND,
|
||||
};
|
||||
Visibility visibility;
|
||||
bool show_return_value = false;
|
||||
uint32_t specific_version;
|
||||
|
||||
inline bool appears_in_patches_menu() const {
|
||||
return (this->visibility == Visibility::PATCHES_MENU_ONLY) ||
|
||||
(this->visibility == Visibility::PATCHES_MENU_AND_CHAT_COMMAND);
|
||||
}
|
||||
inline bool allowed_via_chat_command(bool cheat_mode_enabled) const {
|
||||
return (cheat_mode_enabled && (this->visibility == Visibility::CHAT_COMMAND_ONLY_WITH_CHEAT_MODE)) ||
|
||||
(this->visibility == Visibility::CHAT_COMMAND_ONLY) ||
|
||||
(this->visibility == Visibility::PATCHES_MENU_AND_CHAT_COMMAND);
|
||||
}
|
||||
|
||||
inline bool is_big_endian() const {
|
||||
return (this->arch == Architecture::POWERPC);
|
||||
}
|
||||
|
||||
template <bool BE>
|
||||
std::string generate_client_command_t(
|
||||
const std::unordered_map<std::string, uint32_t>& label_writes,
|
||||
const void* suffix_data = nullptr,
|
||||
size_t suffix_size = 0,
|
||||
uint32_t override_relocations_offset = 0) const;
|
||||
std::string generate_client_command(
|
||||
const std::unordered_map<std::string, uint32_t>& label_writes = {},
|
||||
const void* suffix_data = nullptr,
|
||||
size_t suffix_size = 0,
|
||||
uint32_t override_relocations_offset = 0) const;
|
||||
};
|
||||
|
||||
ClientFunctionIndex() = default;
|
||||
ClientFunctionIndex(const std::string& directory, bool raise_on_any_failure);
|
||||
|
||||
std::unordered_map<std::string, std::shared_ptr<Function>> all_functions; // Key is "PatchName-SpecificVersion"
|
||||
std::map<uint32_t, std::map<std::string, std::shared_ptr<Function>>> functions_by_specific_version;
|
||||
std::map<uint32_t, std::shared_ptr<Function>> functions_by_menu_item_id;
|
||||
|
||||
std::shared_ptr<const Menu> patch_switches_menu(
|
||||
uint32_t specific_version,
|
||||
const std::unordered_set<std::string>& server_auto_patches_enabled,
|
||||
const std::unordered_set<std::string>& client_auto_patches_enabled) const;
|
||||
bool patch_menu_empty(uint32_t specific_version) const;
|
||||
|
||||
std::shared_ptr<const Function> get(const std::string& name, uint32_t specific_version) const;
|
||||
std::shared_ptr<const Function> get(const std::string& name, Function::Architecture arch) const;
|
||||
std::shared_ptr<const Function> get(const std::string& name) const;
|
||||
std::shared_ptr<const Function> get_by_menu_item_id(uint32_t menu_item_id) const;
|
||||
};
|
||||
|
||||
const char* name_for_architecture(ClientFunctionIndex::Function::Architecture arch);
|
||||
uint32_t specific_version_for_gc_header_checksum(uint32_t header_checksum);
|
||||
|
||||
template <>
|
||||
const char* phosg::name_for_enum<ClientFunctionIndex::Function::Visibility>(
|
||||
ClientFunctionIndex::Function::Visibility vis);
|
||||
@@ -4269,7 +4269,9 @@ struct G_Attack_6x43_6x44_6x45 {
|
||||
le_uint16_t unknown_a2 = 0;
|
||||
} __packed_ws__(G_Attack_6x43_6x44_6x45, 8);
|
||||
|
||||
// 6x46: Attack finished (sent after each of 43, 44, and 45) (protected on GC NTE/V3/V4)
|
||||
// 6x46: Set attack strike targets (sent after each of 43, 44, and 45) (protected on GC NTE/V3/V4)
|
||||
// This command sets the targets of each strike of an attack (e.g. each pair of mechgun bullets, or each swing of a
|
||||
// pair of daggers). For multi-strike attacks, this is sent multiple times.
|
||||
// The number of targets is not bounds-checked during byteswapping on GC clients. The client only expects up to 10
|
||||
// entries here, so if the number of targets is too large, the client will byteswap the function's return address on
|
||||
// the stack, and it will crash.
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
#include <phosg/Encoding.hh>
|
||||
#include <phosg/Strings.hh>
|
||||
#include <phosg/Vector.hh>
|
||||
#include <set>
|
||||
|
||||
#include "Text.hh"
|
||||
|
||||
@@ -160,3 +162,114 @@ struct RELFileFooterT {
|
||||
} __packed_ws_be__(RELFileFooterT, 0x20);
|
||||
using RELFileFooter = RELFileFooterT<false>;
|
||||
using RELFileFooterBE = RELFileFooterT<true>;
|
||||
|
||||
template <bool BE>
|
||||
std::set<uint32_t> all_relocation_offsets_for_rel_file(const void* data, size_t size) {
|
||||
phosg::StringReader r(data, size);
|
||||
|
||||
std::set<uint32_t> ret;
|
||||
ret.emplace(r.size() - 0x20); // REL footer
|
||||
ret.emplace(r.pget<U32T<BE>>(r.size() - 0x10)); // root
|
||||
ret.emplace(r.pget<U32T<BE>>(r.size() - 0x20)); // relocations
|
||||
|
||||
const auto& footer = r.pget<RELFileFooterT<BE>>(r.size() - sizeof(RELFileFooterT<BE>));
|
||||
auto sub_r = r.sub(footer.relocations_offset, footer.num_relocations * sizeof(U16T<BE>));
|
||||
uint32_t offset = 0;
|
||||
while (!sub_r.eof()) {
|
||||
offset += sub_r.template get<U16T<BE>>() * 4;
|
||||
ret.emplace(r.pget<U32T<BE>>(offset));
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
size_t get_rel_array_count(const std::set<uint32_t>& offsets, size_t start_offset) {
|
||||
auto it = offsets.lower_bound(start_offset);
|
||||
if (it == offsets.end()) {
|
||||
throw std::out_of_range("start offset out of range");
|
||||
}
|
||||
if (*it == start_offset) {
|
||||
it++;
|
||||
}
|
||||
if (it == offsets.end()) {
|
||||
throw std::out_of_range("no further offset beyond start offset");
|
||||
}
|
||||
return (*it - start_offset) / sizeof(T);
|
||||
}
|
||||
|
||||
template <bool BE>
|
||||
class RELFileWriter {
|
||||
public:
|
||||
RELFileWriter() = default;
|
||||
~RELFileWriter() = default;
|
||||
|
||||
template <typename T>
|
||||
uint32_t put(const T& obj) {
|
||||
uint32_t ret = this->w.size();
|
||||
this->w.put<T>(obj);
|
||||
return ret;
|
||||
}
|
||||
|
||||
uint32_t write(const void* data, size_t size) {
|
||||
uint32_t ret = this->w.size();
|
||||
this->w.write(data, size);
|
||||
return ret;
|
||||
}
|
||||
|
||||
uint32_t write(const std::string& data) {
|
||||
uint32_t ret = this->w.size();
|
||||
this->w.write(data);
|
||||
return ret;
|
||||
}
|
||||
|
||||
uint32_t write_offset(uint32_t value) {
|
||||
uint32_t ret = this->w.size();
|
||||
this->relocations.emplace(ret);
|
||||
this->w.put<U32T<BE>>(value);
|
||||
return ret;
|
||||
}
|
||||
|
||||
uint32_t write_ref(const ArrayRefT<BE>& ref) {
|
||||
uint32_t ret = this->w.size();
|
||||
this->w.put<ArrayRefT<BE>>(ref);
|
||||
this->relocations.emplace(ret + offsetof(ArrayRefT<BE>, offset));
|
||||
return ret;
|
||||
}
|
||||
|
||||
void align(size_t alignment) {
|
||||
while (this->w.size() & (alignment - 1)) {
|
||||
this->w.put_u8(0);
|
||||
}
|
||||
}
|
||||
|
||||
std::string finalize(uint32_t root_offset) {
|
||||
RELFileFooterT<BE> footer;
|
||||
footer.root_offset = root_offset;
|
||||
|
||||
this->align(0x20);
|
||||
footer.relocations_offset = this->w.size();
|
||||
footer.num_relocations = this->relocations.size();
|
||||
footer.unused1[0] = 1;
|
||||
uint32_t last_offset = 0;
|
||||
for (uint32_t reloc_offset : this->relocations) {
|
||||
if (reloc_offset & 3) {
|
||||
throw std::logic_error("Relocation is not 4-byte aligned");
|
||||
}
|
||||
size_t reloc_value = (reloc_offset - last_offset) >> 2;
|
||||
if (reloc_value > 0xFFFF) {
|
||||
throw std::runtime_error("Relocation offset is too far away from previous");
|
||||
}
|
||||
this->w.put<U16T<BE>>(reloc_value);
|
||||
last_offset = reloc_offset;
|
||||
}
|
||||
|
||||
align(0x20);
|
||||
this->w.put<RELFileFooterT<BE>>(footer);
|
||||
|
||||
return std::move(this->w.str());
|
||||
}
|
||||
|
||||
phosg::StringWriter w;
|
||||
std::set<uint32_t> relocations;
|
||||
};
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
#include "DOLFileIndex.hh"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <filesystem>
|
||||
#include <phosg/Filesystem.hh>
|
||||
#include <phosg/Hash.hh>
|
||||
#include <phosg/Time.hh>
|
||||
#include <stdexcept>
|
||||
|
||||
#include <resource_file/Emulators/PPC32Emulator.hh>
|
||||
#include <resource_file/Emulators/SH4Emulator.hh>
|
||||
#include <resource_file/Emulators/X86Emulator.hh>
|
||||
|
||||
#include "CommandFormats.hh"
|
||||
#include "CommonFileFormats.hh"
|
||||
#include "Compression.hh"
|
||||
#include "Loggers.hh"
|
||||
|
||||
using namespace std;
|
||||
|
||||
DOLFileIndex::DOLFileIndex(const string& directory) {
|
||||
if (!std::filesystem::is_directory(directory)) {
|
||||
client_functions_log.info_f("DOL file directory is missing");
|
||||
return;
|
||||
}
|
||||
|
||||
auto menu = make_shared<Menu>(MenuID::PROGRAMS, "Programs");
|
||||
this->menu = menu;
|
||||
menu->items.emplace_back(ProgramsMenuItemID::GO_BACK, "Go back", "Return to the\nmain menu", 0);
|
||||
|
||||
std::vector<std::string> filenames;
|
||||
for (const auto& item : std::filesystem::directory_iterator(directory)) {
|
||||
filenames.emplace_back(item.path().filename().string());
|
||||
}
|
||||
std::sort(filenames.begin(), filenames.end());
|
||||
|
||||
uint32_t next_menu_item_id = 0;
|
||||
for (const auto& filename : filenames) {
|
||||
bool is_dol = filename.ends_with(".dol");
|
||||
bool is_compressed_dol = filename.ends_with(".dol.prs");
|
||||
if (!is_dol && !is_compressed_dol) {
|
||||
continue;
|
||||
}
|
||||
string name = filename.substr(0, filename.size() - (is_compressed_dol ? 8 : 4));
|
||||
|
||||
try {
|
||||
auto dol = make_shared<File>();
|
||||
dol->menu_item_id = next_menu_item_id++;
|
||||
dol->name = name;
|
||||
|
||||
string path = directory + "/" + filename;
|
||||
string file_data = phosg::load_file(path);
|
||||
|
||||
string description;
|
||||
if (is_compressed_dol) {
|
||||
size_t decompressed_size = prs_decompress_size(file_data);
|
||||
|
||||
phosg::StringWriter w;
|
||||
w.put_u32b(file_data.size());
|
||||
w.put_u32b(decompressed_size);
|
||||
w.write(file_data);
|
||||
while (w.size() & 3) {
|
||||
w.put_u8(0);
|
||||
}
|
||||
dol->data = std::move(w.str());
|
||||
|
||||
string compressed_size_str = phosg::format_size(file_data.size());
|
||||
string decompressed_size_str = phosg::format_size(decompressed_size);
|
||||
client_functions_log.debug_f("Loaded compressed DOL file {} ({} -> {})",
|
||||
dol->name, compressed_size_str, decompressed_size_str);
|
||||
description = std::format("$C6{}$C7\n{}\n{} (orig)", dol->name, compressed_size_str, decompressed_size_str);
|
||||
|
||||
} else {
|
||||
phosg::StringWriter w;
|
||||
w.put_u32b(0);
|
||||
w.put_u32b(file_data.size());
|
||||
w.write(file_data);
|
||||
while (w.size() & 3) {
|
||||
w.put_u8(0);
|
||||
}
|
||||
dol->data = std::move(w.str());
|
||||
|
||||
string size_str = phosg::format_size(dol->data.size());
|
||||
client_functions_log.debug_f("Loaded DOL file {} ({})", filename, size_str);
|
||||
description = std::format("$C6{}$C7\n{}", dol->name, size_str);
|
||||
}
|
||||
|
||||
this->item_id_to_file.emplace_back(dol);
|
||||
menu->items.emplace_back(dol->menu_item_id, dol->name, description, MenuItem::Flag::REQUIRES_SEND_FUNCTION_CALL_RUNS_CODE);
|
||||
|
||||
} catch (const exception& e) {
|
||||
client_functions_log.warning_f("Failed to load DOL file {}: {}", filename, e.what());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
#pragma once
|
||||
|
||||
#include <inttypes.h>
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
#include "Menu.hh"
|
||||
|
||||
struct DOLFileIndex {
|
||||
struct File {
|
||||
uint32_t menu_item_id;
|
||||
std::string name;
|
||||
std::string data;
|
||||
bool is_compressed;
|
||||
};
|
||||
|
||||
std::vector<std::shared_ptr<File>> item_id_to_file;
|
||||
std::shared_ptr<const Menu> menu;
|
||||
|
||||
DOLFileIndex() = default;
|
||||
explicit DOLFileIndex(const std::string& directory);
|
||||
|
||||
inline bool empty() const {
|
||||
return this->item_id_to_file.empty();
|
||||
}
|
||||
};
|
||||
+207
-147
@@ -2,6 +2,7 @@
|
||||
|
||||
#include <phosg/Filesystem.hh>
|
||||
#include <phosg/Strings.hh>
|
||||
#include <set>
|
||||
|
||||
#include "Loggers.hh"
|
||||
#include "PSOEncryption.hh"
|
||||
@@ -18,140 +19,140 @@ static constexpr uint8_t BOSS = EnemyTypeDefinition::Flag::IS_BOSS;
|
||||
static constexpr uint8_t NONE = 0xFF;
|
||||
static const vector<EnemyTypeDefinition> type_defs{
|
||||
// clang-format off
|
||||
// TYPE FLAGS RT OLDRT BP-STATS BP-ATTACK BP-RESIST ENUM NAME IN-GAME NAME ULTIMATE NAME
|
||||
{EnemyType::UNKNOWN, 0, NONE, NONE, {}, {}, {}, "UNKNOWN", "__UNKNOWN__", nullptr},
|
||||
{EnemyType::NONE, 0, NONE, NONE, {}, {}, {}, "NONE", "__NONE__", nullptr},
|
||||
{EnemyType::NON_ENEMY_NPC, EP1 | EP2 | EP4, NONE, NONE, {}, {}, {}, "NON_ENEMY_NPC", "__NPC__", nullptr},
|
||||
{EnemyType::AL_RAPPY, EP1 | RARE, 0x06, 0x06, {0x19}, {0x19}, {0x19}, "AL_RAPPY", "Al Rappy", "Pal Rappy"},
|
||||
{EnemyType::ASTARK, EP4, 0x58, 0x41, {0x09}, {0x0B, 0x0A, 0x0C}, {0x09}, "ASTARK", "Astark", nullptr},
|
||||
{EnemyType::BA_BOOTA, EP4, 0x62, 0x4F, {0x03}, {0x03, 0x02, 0x04}, {0x03}, "BA_BOOTA", "Ba Boota", nullptr},
|
||||
{EnemyType::BARBA_RAY, EP2 | BOSS, 0x49, 0x49, {0x0F}, {0x0F}, {0x0F}, "BARBA_RAY", "Barba Ray", nullptr},
|
||||
{EnemyType::BARBA_RAY_JOINT, EP2 | BOSS, 0x49, 0x49, {0x10}, {0x0F}, {0x10}, "BARBA_RAY_JOINT", "Barba Ray (joint)", nullptr},
|
||||
{EnemyType::BARBAROUS_WOLF, EP1 | EP2, 0x08, 0x08, {0x03}, {0x03}, {0x03}, "BARBAROUS_WOLF", "Barbarous Wolf", "Gulgus-gue"},
|
||||
{EnemyType::BEE_L, EP1 | EP2, NONE, NONE, {0x0C}, {0x0C}, {0x0C}, "BEE_L", "Bee L", "Gee L"},
|
||||
{EnemyType::BEE_R, EP1 | EP2, NONE, NONE, {0x0B}, {0x0B}, {0x0B}, "BEE_R", "Bee R", "Gee R"},
|
||||
{EnemyType::BOOMA, EP1, 0x09, 0x09, {0x4B}, {0x4E}, {0x4A}, "BOOMA", "Booma", "Bartle"},
|
||||
{EnemyType::BOOTA, EP4, 0x60, 0x4D, {0x00}, {0x00, 0x02, 0x04}, {0x00}, "BOOTA", "Boota", nullptr},
|
||||
{EnemyType::BULCLAW, EP1, 0x28, 0x28, {0x1F}, {0x1F}, {0x1F}, "BULCLAW", "Bulclaw", nullptr},
|
||||
{EnemyType::BULK, EP1, 0x27, 0x27, {0x1F}, {0x1F}, {0x1F}, "BULK", "Bulk", nullptr},
|
||||
{EnemyType::CANADINE, EP1, 0x1C, 0x1C, {0x07}, {0x07}, {0x07}, "CANADINE", "Canadine", "Canabin"},
|
||||
{EnemyType::CANADINE_GROUP, EP1, 0x1C, 0x1C, {0x08}, {0x08}, {0x08}, "CANADINE_GROUP", "Canadine (group)", "Canabin (group)"},
|
||||
{EnemyType::CANANE, EP1, 0x1D, 0x1D, {0x09}, {0x09}, {0x09}, "CANANE", "Canane", "Canune"},
|
||||
{EnemyType::CHAOS_BRINGER, EP1, 0x24, 0x24, {0x0D}, {0x0D}, {0x0D}, "CHAOS_BRINGER", "Chaos Bringer", "Dark Bringer"},
|
||||
{EnemyType::CHAOS_SORCERER, EP1 | EP2, 0x1F, 0x1F, {0x0A}, {0x0A}, {0x0A}, "CHAOS_SORCERER", "Chaos Sorceror", "Gran Sorceror"},
|
||||
{EnemyType::CLAW, EP1, 0x26, 0x26, {0x20}, {0x20}, {0x20}, "CLAW", "Claw", nullptr},
|
||||
{EnemyType::DARK_BELRA, EP1 | EP2, 0x25, 0x25, {0x0E}, {0x0E, 0x13}, {0x0E}, "DARK_BELRA", "Dark Belra", "Indi Belra"},
|
||||
{EnemyType::DARK_FALZ_1, EP1 | BOSS, NONE, NONE, {0x36}, {0x36}, {0x36}, "DARK_FALZ_1", "Dark Falz (phase 1)", nullptr},
|
||||
{EnemyType::DARK_FALZ_2, EP1 | BOSS, 0x2F, 0x2F, {0x37}, {0x37}, {0x37}, "DARK_FALZ_2", "Dark Falz (phase 2)", nullptr},
|
||||
{EnemyType::DARK_FALZ_3, EP1 | BOSS, 0x2F, 0x2F, {0x38}, {0x38}, {0x38}, "DARK_FALZ_3", "Dark Falz (phase 3)", nullptr},
|
||||
{EnemyType::DARK_GUNNER, EP1, 0x22, 0x22, {0x1E}, {0x1E}, {0x1E}, "DARK_GUNNER", "Dark Gunner", nullptr},
|
||||
{EnemyType::DARK_GUNNER_CONTROL, EP1, NONE, NONE, {}, {}, {}, "DARK_GUNNER_CONTROL", "Dark Gunner (control)", nullptr},
|
||||
{EnemyType::DARVANT, EP1, NONE, NONE, {0x35}, {0x35}, {0x35}, "DARVANT", "Darvant", nullptr},
|
||||
{EnemyType::DE_ROL_LE, EP1 | BOSS, 0x2D, 0x2D, {0x0F}, {0x0F}, {0x0F}, "DE_ROL_LE", "De Rol Le", "Dal Ral Lie"},
|
||||
{EnemyType::DE_ROL_LE_BODY, EP1 | BOSS, NONE, NONE, {0x10}, {0x0F}, {0x10}, "DE_ROL_LE_BODY", "De Rol Le (body)", "Dal Ral Lie (body)"},
|
||||
{EnemyType::DE_ROL_LE_MINE, EP1 | BOSS, NONE, NONE, {0x11}, {0x0F}, {0x11}, "DE_ROL_LE_MINE", "De Rol Le (mine)", "Dal Ral Lie (mine)"},
|
||||
{EnemyType::DEATH_GUNNER, EP1, 0x23, 0x23, {0x1E}, {0x1E}, {0x1E}, "DEATH_GUNNER", "Death Gunner", nullptr},
|
||||
{EnemyType::DEL_LILY, EP2, 0x53, 0x53, {0x25}, {0x25}, {0x25}, "DEL_LILY", "Del Lily", nullptr},
|
||||
{EnemyType::DEL_RAPPY_CRATER, EP4, 0x69, 0x57, {0x06}, {0x06}, {0x06}, "DEL_RAPPY_CRATER", "Del Rappy (crater)", nullptr},
|
||||
{EnemyType::DEL_RAPPY_DESERT, EP4, 0x69, 0x58, {0x18}, {0x18}, {0x18}, "DEL_RAPPY_DESERT", "Del Rappy (desert)", nullptr},
|
||||
{EnemyType::DELBITER, EP2, 0x48, 0x48, {0x0D}, {0x0D}, {0x0D}, "DELBITER", "Delbiter", nullptr},
|
||||
{EnemyType::DELDEPTH, EP2, 0x47, 0x47, {0x30}, {0x30}, {0x30}, "DELDEPTH", "Deldepth", nullptr},
|
||||
{EnemyType::DELSABER, EP1 | EP2, 0x1E, 0x1E, {0x52}, {0x57, 0x58, 0x59}, {0x51}, "DELSABER", "Delsaber", nullptr},
|
||||
{EnemyType::DIMENIAN, EP1 | EP2, 0x29, 0x29, {0x53}, {0x5A}, {0x52}, "DIMENIAN", "Dimenian", "Arlan"},
|
||||
{EnemyType::DOLMDARL, EP2, 0x41, 0x41, {0x50}, {0x55}, {0x4F}, "DOLMDARL", "Dolmdarl", nullptr},
|
||||
{EnemyType::DOLMOLM, EP2, 0x40, 0x40, {0x4F}, {0x54}, {0x4E}, "DOLMOLM", "Dolmolm", nullptr},
|
||||
{EnemyType::DORPHON, EP4, 0x63, 0x50, {0x0F}, {0x0F}, {0x0F}, "DORPHON", "Dorphon", nullptr},
|
||||
{EnemyType::DORPHON_ECLAIR, EP4 | RARE, 0x64, 0x51, {0x10}, {0x10}, {0x10}, "DORPHON_ECLAIR", "Dorphon Eclair", nullptr},
|
||||
{EnemyType::DRAGON, EP1 | BOSS, 0x2C, 0x2C, {0x12}, {0x12, 0x14, 0x15, 0x16, 0x17}, {0x12}, "DRAGON", "Dragon", "Sil Dragon"},
|
||||
{EnemyType::DUBCHIC, EP1 | EP2, 0x18, 0x18, {0x1B}, {0x1B}, {0x1B}, "DUBCHIC", "Dubchic", "Dubchich"},
|
||||
{EnemyType::DUBWITCH, EP1 | EP2, NONE, NONE, {}, {}, {}, "DUBWITCH", "Dubwitch", "Duvuik"},
|
||||
{EnemyType::EGG_RAPPY, EP2, 0x51, 0x51, {0x19}, {0x19}, {0x19}, "EGG_RAPPY", "Egg Rappy", nullptr},
|
||||
{EnemyType::EPSIGARD, EP2, NONE, NONE, {0x24}, {0x24}, {0x24}, "EPSIGARD", "Episgard", nullptr},
|
||||
{EnemyType::EPSILON, EP2, 0x54, 0x54, {0x23}, {0x23}, {0x23}, "EPSILON", "Epsilon", nullptr},
|
||||
{EnemyType::EVIL_SHARK, EP1, 0x10, 0x10, {0x4F}, {0x54}, {0x4E}, "EVIL_SHARK", "Evil Shark", "Vulmer"},
|
||||
{EnemyType::GAEL_OR_GIEL, EP2, NONE, NONE, {0x2E}, {0x2E}, {0x2E}, "GAEL_OR_GIEL", "Gael/Giel", nullptr},
|
||||
{EnemyType::GAL_GRYPHON, EP2 | BOSS, 0x4D, 0x4D, {0x1E}, {0x1E, 0x1F, 0x20, 0x21, 0x22}, {0x1E}, "GAL_GRYPHON", "Gal Gryphon", nullptr},
|
||||
{EnemyType::GARANZ, EP1 | EP2, 0x19, 0x19, {0x1D}, {0x1D}, {0x1D}, "GARANZ", "Garanz", "Baranz"},
|
||||
{EnemyType::GEE, EP2, 0x36, 0x36, {0x07}, {0x07}, {0x07}, "GEE", "Gee", nullptr},
|
||||
{EnemyType::GI_GUE, EP2, 0x37, 0x37, {0x1A}, {0x1A}, {0x1A}, "GI_GUE", "Gi Gue", nullptr},
|
||||
{EnemyType::GIBBLES, EP2, 0x3D, 0x3D, {0x3D}, {0x3D, 0x3E, 0x3F}, {0x3D}, "GIBBLES", "Gibbles", nullptr},
|
||||
{EnemyType::GIGOBOOMA, EP1, 0x0B, 0x0B, {0x4D}, {0x50}, {0x4C}, "GIGOBOOMA", "Gigobooma", "Tollaw"},
|
||||
{EnemyType::GILLCHIC, EP1 | EP2, 0x32, 0x32, {0x1C}, {0x1C}, {0x1C}, "GILLCHIC", "Gillchic", "Gillchich"},
|
||||
{EnemyType::GIRTABLULU, EP4, 0x5D, 0x48, {0x1F}, {0x1F}, {0x1F}, "GIRTABLULU", "Girtablulu", nullptr},
|
||||
{EnemyType::GOBOOMA, EP1, 0x0A, 0x0A, {0x4C}, {0x4F}, {0x4B}, "GOBOOMA", "Gobooma", "Barble"},
|
||||
{EnemyType::GOL_DRAGON, EP2 | BOSS, 0x4C, 0x4C, {0x12}, {0x12, 0x14, 0x15, 0x16, 0x17}, {0x12}, "GOL_DRAGON", "Gol Dragon", nullptr},
|
||||
{EnemyType::GORAN, EP4, 0x65, 0x52, {0x11}, {0x11, 0x14}, {0x11}, "GORAN", "Goran", nullptr},
|
||||
{EnemyType::GORAN_DETONATOR, EP4, 0x66, 0x53, {0x13}, {0x13, 0x16}, {0x13}, "GORAN_DETONATOR", "Goran Detonator", nullptr},
|
||||
{EnemyType::GRASS_ASSASSIN, EP1 | EP2, 0x0C, 0x0C, {0x4E}, {0x51, 0x52, 0x53}, {0x4D}, "GRASS_ASSASSIN", "Grass Assassin", "Crimson Assassin"},
|
||||
{EnemyType::GUIL_SHARK, EP1, 0x12, 0x12, {0x51}, {0x56}, {0x50}, "GUIL_SHARK", "Guil Shark", "Melqueek"},
|
||||
{EnemyType::HALLO_RAPPY, EP2, 0x50, 0x50, {0x19}, {0x19}, {0x19}, "HALLO_RAPPY", "Hallo Rappy", nullptr},
|
||||
{EnemyType::HIDOOM, EP1 | EP2, 0x17, 0x17, {0x32}, {0x32}, {0x32}, "HIDOOM", "Hidoom", nullptr},
|
||||
{EnemyType::HILDEBEAR, EP1 | EP2, 0x01, 0x01, {0x49}, {0x48, 0x49, 0x4A}, {0x48}, "HILDEBEAR", "Hildebear", "Hildelt"},
|
||||
{EnemyType::HILDEBLUE, EP1 | EP2 | RARE, 0x02, 0x02, {0x4A}, {0x4B, 0x4C, 0x4D}, {0x49}, "HILDEBLUE", "Hildeblue", "Hildetorr"},
|
||||
{EnemyType::ILL_GILL, EP2, 0x52, 0x52, {0x26}, {0x26, 0x27, 0x28, 0x29}, {0x26}, "ILL_GILL", "Ill Gill", nullptr},
|
||||
{EnemyType::KONDRIEU, EP4 | RARE | BOSS, 0x6C, 0x5B, {0x28, 0x2A}, {0x28, 0x2A}, {0x28, 0x2A}, "KONDRIEU", "Kondrieu", nullptr},
|
||||
{EnemyType::KONDRIEU_SPINNER, EP4 | RARE | BOSS, 0x6C, 0x5B, {0x29, 0x2B}, {0x29, 0x2B}, {0x29, 0x2B}, "KONDRIEU_SPINNER", "Kondrieu (spinner)", nullptr},
|
||||
{EnemyType::LA_DIMENIAN, EP1 | EP2, 0x2A, 0x2A, {0x54}, {0x5B}, {0x53}, "LA_DIMENIAN", "La Dimenian", "Merlan"},
|
||||
{EnemyType::LOVE_RAPPY, EP2, 0x33, 0x33, {0x19}, {0x19}, {0x19}, "LOVE_RAPPY", "Love Rappy", nullptr},
|
||||
{EnemyType::MERICARAND, EP2, 0x38, 0x38, {0x3A}, {0x3A}, {0x3A}, "MERICARAND", "Mericarand", nullptr},
|
||||
{EnemyType::MERICAROL, EP2, 0x38, 0x38, {0x3A}, {0x3A}, {0x3A}, "MERICAROL", "Mericarol", nullptr},
|
||||
{EnemyType::MERICUS, EP2 | RARE, 0x3A, 0x3A, {0x46}, {0x46}, {0x46}, "MERICUS", "Mericus", nullptr},
|
||||
{EnemyType::MERIKLE, EP2 | RARE, 0x39, 0x39, {0x45}, {0x45}, {0x45}, "MERIKLE", "Merikle", nullptr},
|
||||
{EnemyType::MERILLIA, EP2, 0x34, 0x34, {0x4B}, {0x4E}, {0x4A}, "MERILLIA", "Merillia", nullptr},
|
||||
{EnemyType::MERILTAS, EP2, 0x35, 0x35, {0x4C}, {0x4F}, {0x4B}, "MERILTAS", "Meriltas", nullptr},
|
||||
{EnemyType::MERISSA_A, EP4, 0x5B, 0x46, {0x19}, {0x19}, {0x19}, "MERISSA_A", "Merissa A", nullptr},
|
||||
{EnemyType::MERISSA_AA, EP4 | RARE, 0x5C, 0x47, {0x1A}, {0x1A}, {0x1A}, "MERISSA_AA", "Merissa AA", nullptr},
|
||||
{EnemyType::MIGIUM, EP1 | EP2, 0x16, 0x16, {0x33}, {0x33}, {0x33}, "MIGIUM", "Migium", nullptr},
|
||||
{EnemyType::MONEST, EP1 | EP2, 0x04, 0x04, {0x01}, {0x01}, {0x01}, "MONEST", "Monest", "Mothvist"},
|
||||
{EnemyType::MORFOS, EP2, 0x42, 0x42, {0x40}, {0x40, 0x50}, {0x40}, "MORFOS", "Morfos", nullptr},
|
||||
{EnemyType::MOTHMANT, EP1 | EP2, 0x03, 0x03, {0x00}, {0x00}, {0x00}, "MOTHMANT", "Mothmant", "Mothvert"},
|
||||
{EnemyType::NANO_DRAGON, EP1, 0x0F, 0x0F, {0x1A}, {0x1A}, {0x1A}, "NANO_DRAGON", "Nano Dragon", nullptr},
|
||||
{EnemyType::NAR_LILY, EP1 | EP2 | RARE, 0x0E, 0x0E, {0x05}, {0x05}, {0x05}, "NAR_LILY", "Nar Lily", "Mil Lily"},
|
||||
{EnemyType::OLGA_FLOW_1, EP2 | BOSS, NONE, NONE, {0x2B}, {0x2B}, {0x2B}, "OLGA_FLOW_1", "Olga Flow (phase 1)", nullptr},
|
||||
{EnemyType::OLGA_FLOW_2, EP2 | BOSS, 0x4E, 0x4E, {0x2C}, {0x2C}, {0x2C}, "OLGA_FLOW_2", "Olga Flow (phase 2)", nullptr},
|
||||
{EnemyType::PAL_SHARK, EP1, 0x11, 0x11, {0x50}, {0x55}, {0x4F}, "PAL_SHARK", "Pal Shark", "Govulmer"},
|
||||
{EnemyType::PAN_ARMS, EP1 | EP2, 0x15, 0x15, {0x31}, {0x31}, {0x31}, "PAN_ARMS", "Pan Arms", nullptr},
|
||||
{EnemyType::PAZUZU_CRATER, EP4 | RARE, 0x5F, 0x4B, {0x08}, {0x08}, {0x08}, "PAZUZU_CRATER", "Pazuzu (crater)", nullptr},
|
||||
{EnemyType::PAZUZU_DESERT, EP4 | RARE, 0x5F, 0x4C, {0x1C}, {0x1C}, {0x1C}, "PAZUZU_DESERT", "Pazuzu (desert)", nullptr},
|
||||
{EnemyType::PIG_RAY, EP2, 0x4A, NONE, {0x08}, {0x08}, {0x08}, "PIG_RAY", "Pig Ray", nullptr},
|
||||
{EnemyType::POFUILLY_SLIME, EP1, 0x13, 0x13, {0x30}, {0x30}, {0x30}, "POFUILLY_SLIME", "Pofuilly Slime", nullptr},
|
||||
{EnemyType::POUILLY_SLIME, EP1 | RARE, 0x14, 0x14, {0x34}, {0x34}, {0x34}, "POUILLY_SLIME", "Pouilly Slime", nullptr},
|
||||
{EnemyType::POISON_LILY, EP1 | EP2, 0x0D, 0x0D, {0x04}, {0x04}, {0x04}, "POISON_LILY", "Poison Lily", "Ob Lily"},
|
||||
{EnemyType::PYRO_GORAN, EP4, 0x67, 0x54, {0x12}, {0x12, 0x15}, {0x12}, "PYRO_GORAN", "Pyro Goran", nullptr},
|
||||
{EnemyType::RAG_RAPPY, EP1 | EP2, 0x05, 0x05, {0x18}, {0x18}, {0x18}, "RAG_RAPPY", "Rag Rappy", "El Rappy"},
|
||||
{EnemyType::RECOBOX, EP2, 0x43, 0x43, {0x41}, {0x41}, {0x41}, "RECOBOX", "Recobox", nullptr},
|
||||
{EnemyType::RECON, EP2, 0x44, 0x44, {0x42}, {0x42}, {0x42}, "RECON", "Recon", nullptr},
|
||||
{EnemyType::SAINT_MILION, EP4 | BOSS, 0x6A, 0x59, {0x20, 0x22}, {0x20, 0x22}, {0x20, 0x22}, "SAINT_MILION", "Saint-Milion", nullptr},
|
||||
{EnemyType::SAINT_MILION_SPINNER, EP4 | BOSS, 0x6A, 0x59, {0x21, 0x23}, {0x21, 0x23}, {0x21, 0x23}, "SAINT_MILION_SPINNER", "Saint-Milion (spinner)", nullptr},
|
||||
{EnemyType::SAINT_RAPPY, EP2, 0x4F, 0x4F, {0x19}, {0x19}, {0x19}, "SAINT_RAPPY", "Saint Rappy", nullptr},
|
||||
{EnemyType::SAND_RAPPY_CRATER, EP4, 0x68, 0x55, {0x05}, {0x05}, {0x05}, "SAND_RAPPY_CRATER", "Sand Rappy (crater)", nullptr},
|
||||
{EnemyType::SAND_RAPPY_DESERT, EP4, 0x68, 0x56, {0x17}, {0x17}, {0x17}, "SAND_RAPPY_DESERT", "Sand Rappy (desert)", nullptr},
|
||||
{EnemyType::SATELLITE_LIZARD_CRATER, EP4, 0x5A, 0x44, {0x0D}, {0x0D}, {0x0D}, "SATELLITE_LIZARD_CRATER", "Satellite Lizard (crater)", nullptr},
|
||||
{EnemyType::SATELLITE_LIZARD_DESERT, EP4, 0x5A, 0x45, {0x1D}, {0x1D}, {0x1D}, "SATELLITE_LIZARD_DESERT", "Satellite Lizard (desert)", nullptr},
|
||||
{EnemyType::SAVAGE_WOLF, EP1 | EP2, 0x07, 0x07, {0x02}, {0x02}, {0x02}, "SAVAGE_WOLF", "Savage Wolf", "Gulgus"},
|
||||
{EnemyType::SHAMBERTIN, EP4 | BOSS, 0x6B, 0x5A, {0x24, 0x26}, {0x24, 0x26}, {0x24, 0x26}, "SHAMBERTIN", "Shambertin", nullptr},
|
||||
{EnemyType::SHAMBERTIN_SPINNER, EP4 | BOSS, 0x6B, 0x5A, {0x25, 0x27}, {0x25, 0x27}, {0x25, 0x27}, "SHAMBERTIN_SPINNER", "Shambertin (spinner)", nullptr},
|
||||
{EnemyType::SINOW_BEAT, EP1, 0x1A, 0x1A, {0x06}, {0x06}, {0x06}, "SINOW_BEAT", "Sinow Beat", "Sinow Blue"},
|
||||
{EnemyType::SINOW_BERILL, EP2, 0x3E, 0x3E, {0x06}, {0x06}, {0x06}, "SINOW_BERILL", "Sinow Berill", nullptr},
|
||||
{EnemyType::SINOW_GOLD, EP1, 0x1B, 0x1B, {0x13}, {0x47}, {0x13}, "SINOW_GOLD", "Sinow Gold", "Sinow Red"},
|
||||
{EnemyType::SINOW_SPIGELL, EP2, 0x3F, 0x3F, {0x13}, {0x47}, {0x13}, "SINOW_SPIGELL", "Sinow Spigell", nullptr},
|
||||
{EnemyType::SINOW_ZELE, EP2, 0x46, 0x46, {0x44}, {0x44}, {0x44}, "SINOW_ZELE", "Sinow Zele", nullptr},
|
||||
{EnemyType::SINOW_ZOA, EP2, 0x45, 0x45, {0x43}, {0x43}, {0x43}, "SINOW_ZOA", "Sinow Zoa", nullptr},
|
||||
{EnemyType::SO_DIMENIAN, EP1 | EP2, 0x2B, 0x2B, {0x55}, {0x5C}, {0x54}, "SO_DIMENIAN", "So Dimenian", "Del-D"},
|
||||
{EnemyType::UL_GIBBON, EP2, 0x3B, 0x3B, {0x3B}, {0x3B}, {0x3B}, "UL_GIBBON", "Ul Gibbon", nullptr},
|
||||
{EnemyType::UL_RAY, EP2, 0x4B, NONE, {0x09}, {0x09}, {0x09}, "UL_RAY", "Ul Ray", nullptr},
|
||||
{EnemyType::VOL_OPT_1, EP1 | BOSS, NONE, NONE, {0x21}, {0x21}, {0x21}, "VOL_OPT_1", "Vol Opt (phase 1)", "Vol Opt ver.2 (phase 1)"},
|
||||
{EnemyType::VOL_OPT_2, EP1 | BOSS, 0x2E, 0x2E, {0x25}, {0x25}, {0x25}, "VOL_OPT_2", "Vol Opt (phase 2)", "Vol Opt ver.2 (phase 2)"},
|
||||
{EnemyType::VOL_OPT_AMP, EP1 | BOSS, NONE, NONE, {0x24}, {0x24}, {0x24}, "VOL_OPT_AMP", "Vol Opt (amp)", "Vol Opt ver.2 (amp)"},
|
||||
{EnemyType::VOL_OPT_CORE, EP1 | BOSS, NONE, NONE, {0x26}, {0x26}, {0x26}, "VOL_OPT_CORE", "Vol Opt (core)", "Vol Opt ver.2 (core)"},
|
||||
{EnemyType::VOL_OPT_MONITOR, EP1 | BOSS, NONE, NONE, {0x23}, {0x23}, {0x23}, "VOL_OPT_MONITOR", "Vol Opt (monitor)", "Vol Opt ver.2 (monitor)"},
|
||||
{EnemyType::VOL_OPT_PILLAR, EP1 | BOSS, NONE, NONE, {0x22}, {0x22}, {0x22}, "VOL_OPT_PILLAR", "Vol Opt (pillar)", "Vol Opt ver.2 (pillar)"},
|
||||
{EnemyType::YOWIE_CRATER, EP4, 0x59, 0x42, {0x0E}, {0x0E}, {0x0E}, "YOWIE_CRATER", "Yowie (crater)", nullptr},
|
||||
{EnemyType::YOWIE_DESERT, EP4, 0x59, 0x43, {0x1E}, {0x1E}, {0x1E}, "YOWIE_DESERT", "Yowie (desert)", nullptr},
|
||||
{EnemyType::ZE_BOOTA, EP4, 0x61, 0x4E, {0x01}, {0x01, 0x02, 0x04}, {0x01}, "ZE_BOOTA", "Ze Boota", nullptr},
|
||||
{EnemyType::ZOL_GIBBON, EP2, 0x3C, 0x3C, {0x3C}, {0x3C}, {0x3C}, "ZOL_GIBBON", "Zol Gibbon", nullptr},
|
||||
{EnemyType::ZU_CRATER, EP4, 0x5E, 0x49, {0x07}, {0x07}, {0x07}, "ZU_CRATER", "Zu (crater)", nullptr},
|
||||
{EnemyType::ZU_DESERT, EP4, 0x5E, 0x4A, {0x1B}, {0x1B}, {0x1B}, "ZU_DESERT", "Zu (desert)", nullptr},
|
||||
// TYPE FLAGS RT OLDRT BP-STATS BP-ATTACK BP-RESIST BP-MOVEMENT ENUM NAME IN-GAME NAME ULTIMATE NAME
|
||||
{EnemyType::UNKNOWN, 0, NONE, NONE, {}, {}, {}, {}, "UNKNOWN", "__UNKNOWN__", nullptr},
|
||||
{EnemyType::NONE, 0, NONE, NONE, {}, {}, {}, {}, "NONE", "__NONE__", nullptr},
|
||||
{EnemyType::NON_ENEMY_NPC, EP1 | EP2 | EP4, NONE, NONE, {}, {}, {}, {}, "NON_ENEMY_NPC", "__NPC__", nullptr},
|
||||
{EnemyType::AL_RAPPY, EP1 | RARE, 0x06, 0x06, {0x19}, {0x19}, {0x19}, {0x19}, "AL_RAPPY", "Al Rappy", "Pal Rappy"},
|
||||
{EnemyType::ASTARK, EP4, 0x58, 0x41, {0x09}, {0x0B, 0x0A, 0x0C}, {0x09}, {0x09}, "ASTARK", "Astark", nullptr},
|
||||
{EnemyType::BA_BOOTA, EP4, 0x62, 0x4F, {0x03}, {0x03, 0x02, 0x04}, {0x03}, {0x03}, "BA_BOOTA", "Ba Boota", nullptr},
|
||||
{EnemyType::BARBA_RAY, EP2 | BOSS, 0x49, 0x49, {0x0F}, {0x0F}, {0x0F}, {0x0F}, "BARBA_RAY", "Barba Ray", nullptr},
|
||||
{EnemyType::BARBA_RAY_JOINT, EP2 | BOSS, 0x49, 0x49, {0x10}, {0x0F}, {0x10}, {}, "BARBA_RAY_JOINT", "Barba Ray (joint)", nullptr},
|
||||
{EnemyType::BARBAROUS_WOLF, EP1 | EP2, 0x08, 0x08, {0x03}, {0x03}, {0x03}, {0x03}, "BARBAROUS_WOLF", "Barbarous Wolf", "Gulgus-gue"},
|
||||
{EnemyType::BEE_L, EP1 | EP2, NONE, NONE, {0x0C}, {0x0C}, {0x0C}, {0x0C}, "BEE_L", "Bee L", "Gee L"},
|
||||
{EnemyType::BEE_R, EP1 | EP2, NONE, NONE, {0x0B}, {0x0B}, {0x0B}, {0x0B}, "BEE_R", "Bee R", "Gee R"},
|
||||
{EnemyType::BOOMA, EP1, 0x09, 0x09, {0x4B}, {0x4E}, {0x4A}, {0x4A}, "BOOMA", "Booma", "Bartle"},
|
||||
{EnemyType::BOOTA, EP4, 0x60, 0x4D, {0x00}, {0x00, 0x02, 0x04}, {0x00}, {0x00}, "BOOTA", "Boota", nullptr},
|
||||
{EnemyType::BULCLAW, EP1, 0x28, 0x28, {0x1F}, {0x1F}, {0x1F}, {0x1F, 0x20}, "BULCLAW", "Bulclaw", nullptr},
|
||||
{EnemyType::BULK, EP1, 0x27, 0x27, {0x1F}, {0x1F}, {0x1F}, {0x1F, 0x20}, "BULK", "Bulk", nullptr},
|
||||
{EnemyType::CANADINE, EP1, 0x1C, 0x1C, {0x07}, {0x07}, {0x07}, {0x07}, "CANADINE", "Canadine", "Canabin"},
|
||||
{EnemyType::CANADINE_GROUP, EP1, 0x1C, 0x1C, {0x08}, {0x08}, {0x08}, {0x08}, "CANADINE_GROUP", "Canadine (group)", "Canabin (group)"},
|
||||
{EnemyType::CANANE, EP1, 0x1D, 0x1D, {0x09}, {0x09}, {0x09}, {0x09}, "CANANE", "Canane", "Canune"},
|
||||
{EnemyType::CHAOS_BRINGER, EP1, 0x24, 0x24, {0x0D}, {0x0D}, {0x0D}, {0x0A, 0x0D}, "CHAOS_BRINGER", "Chaos Bringer", "Dark Bringer"},
|
||||
{EnemyType::CHAOS_SORCERER, EP1 | EP2, 0x1F, 0x1F, {0x0A}, {0x0A}, {0x0A}, {}, "CHAOS_SORCERER", "Chaos Sorceror", "Gran Sorceror"},
|
||||
{EnemyType::CLAW, EP1, 0x26, 0x26, {0x20}, {0x20}, {0x20}, {}, "CLAW", "Claw", nullptr},
|
||||
{EnemyType::DARK_BELRA, EP1 | EP2, 0x25, 0x25, {0x0E}, {0x0E, 0x13}, {0x0E}, {0x0E}, "DARK_BELRA", "Dark Belra", "Indi Belra"},
|
||||
{EnemyType::DARK_FALZ_1, EP1 | BOSS, NONE, NONE, {0x36}, {0x36}, {0x36}, {0x36, 0x39}, "DARK_FALZ_1", "Dark Falz (phase 1)", nullptr},
|
||||
{EnemyType::DARK_FALZ_2, EP1 | BOSS, 0x2F, 0x2F, {0x37}, {0x37}, {0x37}, {0x37}, "DARK_FALZ_2", "Dark Falz (phase 2)", nullptr},
|
||||
{EnemyType::DARK_FALZ_3, EP1 | BOSS, 0x2F, 0x2F, {0x38}, {0x38}, {0x38}, {0x38}, "DARK_FALZ_3", "Dark Falz (phase 3)", nullptr},
|
||||
{EnemyType::DARK_GUNNER, EP1, 0x22, 0x22, {0x1E}, {0x1E}, {0x1E}, {0x1E}, "DARK_GUNNER", "Dark Gunner", nullptr},
|
||||
{EnemyType::DARK_GUNNER_CONTROL, EP1, NONE, NONE, {}, {}, {}, {}, "DARK_GUNNER_CONTROL", "Dark Gunner (control)", nullptr},
|
||||
{EnemyType::DARVANT, EP1, NONE, NONE, {0x35}, {0x35}, {0x35}, {0x35, 0x39}, "DARVANT", "Darvant", nullptr},
|
||||
{EnemyType::DE_ROL_LE, EP1 | BOSS, 0x2D, 0x2D, {0x0F}, {0x0F}, {0x0F}, {0x0F}, "DE_ROL_LE", "De Rol Le", "Dal Ral Lie"},
|
||||
{EnemyType::DE_ROL_LE_BODY, EP1 | BOSS, NONE, NONE, {0x10}, {0x0F}, {0x10}, {0x0F}, "DE_ROL_LE_BODY", "De Rol Le (body)", "Dal Ral Lie (body)"},
|
||||
{EnemyType::DE_ROL_LE_MINE, EP1 | BOSS, NONE, NONE, {0x11}, {0x0F}, {0x11}, {0x0F}, "DE_ROL_LE_MINE", "De Rol Le (mine)", "Dal Ral Lie (mine)"},
|
||||
{EnemyType::DEATH_GUNNER, EP1, 0x23, 0x23, {0x1E}, {0x1E}, {0x1E}, {0x1E}, "DEATH_GUNNER", "Death Gunner", nullptr},
|
||||
{EnemyType::DEL_LILY, EP2, 0x53, 0x53, {0x25}, {0x25}, {0x25}, {0x25}, "DEL_LILY", "Del Lily", nullptr},
|
||||
{EnemyType::DEL_RAPPY_CRATER, EP4, 0x69, 0x57, {0x06}, {0x06}, {0x06}, {0x06}, "DEL_RAPPY_CRATER", "Del Rappy (crater)", nullptr},
|
||||
{EnemyType::DEL_RAPPY_DESERT, EP4, 0x69, 0x58, {0x18}, {0x18}, {0x18}, {0x18}, "DEL_RAPPY_DESERT", "Del Rappy (desert)", nullptr},
|
||||
{EnemyType::DELBITER, EP2, 0x48, 0x48, {0x0D}, {0x0D}, {0x0D}, {0x0D}, "DELBITER", "Delbiter", nullptr},
|
||||
{EnemyType::DELDEPTH, EP2, 0x47, 0x47, {0x30}, {0x30}, {0x30}, {0x30}, "DELDEPTH", "Deldepth", nullptr},
|
||||
{EnemyType::DELSABER, EP1 | EP2, 0x1E, 0x1E, {0x52}, {0x57, 0x58, 0x59}, {0x51}, {0x51}, "DELSABER", "Delsaber", nullptr},
|
||||
{EnemyType::DIMENIAN, EP1 | EP2, 0x29, 0x29, {0x53}, {0x5A}, {0x52}, {0x52}, "DIMENIAN", "Dimenian", "Arlan"},
|
||||
{EnemyType::DOLMDARL, EP2, 0x41, 0x41, {0x50}, {0x55}, {0x4F}, {0x4F}, "DOLMDARL", "Dolmdarl", nullptr},
|
||||
{EnemyType::DOLMOLM, EP2, 0x40, 0x40, {0x4F}, {0x54}, {0x4E}, {0x4E}, "DOLMOLM", "Dolmolm", nullptr},
|
||||
{EnemyType::DORPHON, EP4, 0x63, 0x50, {0x0F}, {0x0F}, {0x0F}, {0x0F}, "DORPHON", "Dorphon", nullptr},
|
||||
{EnemyType::DORPHON_ECLAIR, EP4 | RARE, 0x64, 0x51, {0x10}, {0x10}, {0x10}, {0x10}, "DORPHON_ECLAIR", "Dorphon Eclair", nullptr},
|
||||
{EnemyType::DRAGON, EP1 | BOSS, 0x2C, 0x2C, {0x12}, {0x12, 0x14, 0x15, 0x16, 0x17}, {0x12}, {0x11}, "DRAGON", "Dragon", "Sil Dragon"},
|
||||
{EnemyType::DUBCHIC, EP1 | EP2, 0x18, 0x18, {0x1B}, {0x1B}, {0x1B}, {0x1B}, "DUBCHIC", "Dubchic", "Dubchich"},
|
||||
{EnemyType::DUBWITCH, EP1 | EP2, NONE, NONE, {}, {}, {}, {}, "DUBWITCH", "Dubwitch", "Duvuik"},
|
||||
{EnemyType::EGG_RAPPY, EP2, 0x51, 0x51, {0x19}, {0x19}, {0x19}, {0x19}, "EGG_RAPPY", "Egg Rappy", nullptr},
|
||||
{EnemyType::EPSIGARD, EP2, NONE, NONE, {0x24}, {0x24}, {0x24}, {0x24}, "EPSIGARD", "Episgard", nullptr},
|
||||
{EnemyType::EPSILON, EP2, 0x54, 0x54, {0x23}, {0x23}, {0x23}, {0x23}, "EPSILON", "Epsilon", nullptr},
|
||||
{EnemyType::EVIL_SHARK, EP1, 0x10, 0x10, {0x4F}, {0x54}, {0x4E}, {0x4E}, "EVIL_SHARK", "Evil Shark", "Vulmer"},
|
||||
{EnemyType::GAEL_OR_GIEL, EP2, NONE, NONE, {0x2E}, {0x2E}, {0x2E}, {}, "GAEL_OR_GIEL", "Gael/Giel", nullptr},
|
||||
{EnemyType::GAL_GRYPHON, EP2 | BOSS, 0x4D, 0x4D, {0x1E}, {0x1E, 0x1F, 0x20, 0x21, 0x22}, {0x1E}, {0x1E, 0x1F, 0x20}, "GAL_GRYPHON", "Gal Gryphon", nullptr},
|
||||
{EnemyType::GARANZ, EP1 | EP2, 0x19, 0x19, {0x1D}, {0x1D}, {0x1D}, {0x1D}, "GARANZ", "Garanz", "Baranz"},
|
||||
{EnemyType::GEE, EP2, 0x36, 0x36, {0x07}, {0x07}, {0x07}, {0x07}, "GEE", "Gee", nullptr},
|
||||
{EnemyType::GI_GUE, EP2, 0x37, 0x37, {0x1A}, {0x1A}, {0x1A}, {0x1A}, "GI_GUE", "Gi Gue", nullptr},
|
||||
{EnemyType::GIBBLES, EP2, 0x3D, 0x3D, {0x3D}, {0x3D, 0x3E, 0x3F}, {0x3D}, {0x3D}, "GIBBLES", "Gibbles", nullptr},
|
||||
{EnemyType::GIGOBOOMA, EP1, 0x0B, 0x0B, {0x4D}, {0x50}, {0x4C}, {0x4C}, "GIGOBOOMA", "Gigobooma", "Tollaw"},
|
||||
{EnemyType::GILLCHIC, EP1 | EP2, 0x32, 0x32, {0x1C}, {0x1C}, {0x1C}, {0x1C}, "GILLCHIC", "Gillchic", "Gillchich"},
|
||||
{EnemyType::GIRTABLULU, EP4, 0x5D, 0x48, {0x1F}, {0x1F}, {0x1F}, {0x1F}, "GIRTABLULU", "Girtablulu", nullptr},
|
||||
{EnemyType::GOBOOMA, EP1, 0x0A, 0x0A, {0x4C}, {0x4F}, {0x4B}, {0x4B}, "GOBOOMA", "Gobooma", "Barble"},
|
||||
{EnemyType::GOL_DRAGON, EP2 | BOSS, 0x4C, 0x4C, {0x12}, {0x12, 0x14, 0x15, 0x16, 0x17}, {0x12}, {0x11, 0x12, 0x13}, "GOL_DRAGON", "Gol Dragon", nullptr},
|
||||
{EnemyType::GORAN, EP4, 0x65, 0x52, {0x11}, {0x11, 0x14}, {0x11}, {0x11}, "GORAN", "Goran", nullptr},
|
||||
{EnemyType::GORAN_DETONATOR, EP4, 0x66, 0x53, {0x13}, {0x13, 0x16}, {0x13}, {0x13}, "GORAN_DETONATOR", "Goran Detonator", nullptr},
|
||||
{EnemyType::GRASS_ASSASSIN, EP1 | EP2, 0x0C, 0x0C, {0x4E}, {0x51, 0x52, 0x53}, {0x4D}, {0x4D}, "GRASS_ASSASSIN", "Grass Assassin", "Crimson Assassin"},
|
||||
{EnemyType::GUIL_SHARK, EP1, 0x12, 0x12, {0x51}, {0x56}, {0x50}, {0x50}, "GUIL_SHARK", "Guil Shark", "Melqueek"},
|
||||
{EnemyType::HALLO_RAPPY, EP2, 0x50, 0x50, {0x19}, {0x19}, {0x19}, {0x19}, "HALLO_RAPPY", "Hallo Rappy", nullptr},
|
||||
{EnemyType::HIDOOM, EP1 | EP2, 0x17, 0x17, {0x32}, {0x32}, {0x32}, {0x32}, "HIDOOM", "Hidoom", nullptr},
|
||||
{EnemyType::HILDEBEAR, EP1 | EP2, 0x01, 0x01, {0x49}, {0x48, 0x49, 0x4A}, {0x48}, {0x48}, "HILDEBEAR", "Hildebear", "Hildelt"},
|
||||
{EnemyType::HILDEBLUE, EP1 | EP2 | RARE, 0x02, 0x02, {0x4A}, {0x4B, 0x4C, 0x4D}, {0x49}, {0x49}, "HILDEBLUE", "Hildeblue", "Hildetorr"},
|
||||
{EnemyType::ILL_GILL, EP2, 0x52, 0x52, {0x26}, {0x26, 0x27, 0x28, 0x29}, {0x26}, {0x26}, "ILL_GILL", "Ill Gill", nullptr},
|
||||
{EnemyType::KONDRIEU, EP4 | RARE | BOSS, 0x6C, 0x5B, {0x28, 0x2A}, {0x28, 0x2A}, {0x28, 0x2A}, {0x28, 0x2A}, "KONDRIEU", "Kondrieu", nullptr},
|
||||
{EnemyType::KONDRIEU_SPINNER, EP4 | RARE | BOSS, 0x6C, 0x5B, {0x29, 0x2B}, {0x29, 0x2B}, {0x29, 0x2B}, {0x29, 0x2B}, "KONDRIEU_SPINNER", "Kondrieu (spinner)", nullptr},
|
||||
{EnemyType::LA_DIMENIAN, EP1 | EP2, 0x2A, 0x2A, {0x54}, {0x5B}, {0x53}, {0x53}, "LA_DIMENIAN", "La Dimenian", "Merlan"},
|
||||
{EnemyType::LOVE_RAPPY, EP2, 0x33, 0x33, {0x19}, {0x19}, {0x19}, {0x19}, "LOVE_RAPPY", "Love Rappy", nullptr},
|
||||
{EnemyType::MERICARAND, EP2, 0x38, 0x38, {0x3A}, {0x3A}, {0x3A}, {0x3A}, "MERICARAND", "Mericarand", nullptr},
|
||||
{EnemyType::MERICAROL, EP2, 0x38, 0x38, {0x3A}, {0x3A}, {0x3A}, {0x3A}, "MERICAROL", "Mericarol", nullptr},
|
||||
{EnemyType::MERICUS, EP2 | RARE, 0x3A, 0x3A, {0x46}, {0x46}, {0x46}, {0x46}, "MERICUS", "Mericus", nullptr},
|
||||
{EnemyType::MERIKLE, EP2 | RARE, 0x39, 0x39, {0x45}, {0x45}, {0x45}, {0x45}, "MERIKLE", "Merikle", nullptr},
|
||||
{EnemyType::MERILLIA, EP2, 0x34, 0x34, {0x4B}, {0x4E}, {0x4A}, {0x4A}, "MERILLIA", "Merillia", nullptr},
|
||||
{EnemyType::MERILTAS, EP2, 0x35, 0x35, {0x4C}, {0x4F}, {0x4B}, {0x4B}, "MERILTAS", "Meriltas", nullptr},
|
||||
{EnemyType::MERISSA_A, EP4, 0x5B, 0x46, {0x19}, {0x19}, {0x19}, {0x19}, "MERISSA_A", "Merissa A", nullptr},
|
||||
{EnemyType::MERISSA_AA, EP4 | RARE, 0x5C, 0x47, {0x1A}, {0x1A}, {0x1A}, {0x1A}, "MERISSA_AA", "Merissa AA", nullptr},
|
||||
{EnemyType::MIGIUM, EP1 | EP2, 0x16, 0x16, {0x33}, {0x33}, {0x33}, {0x33}, "MIGIUM", "Migium", nullptr},
|
||||
{EnemyType::MONEST, EP1 | EP2, 0x04, 0x04, {0x01}, {0x01}, {0x01}, {0x01}, "MONEST", "Monest", "Mothvist"},
|
||||
{EnemyType::MORFOS, EP2, 0x42, 0x42, {0x40}, {0x40, 0x50}, {0x40}, {0x40}, "MORFOS", "Morfos", nullptr},
|
||||
{EnemyType::MOTHMANT, EP1 | EP2, 0x03, 0x03, {0x00}, {0x00}, {0x00}, {0x00}, "MOTHMANT", "Mothmant", "Mothvert"},
|
||||
{EnemyType::NANO_DRAGON, EP1, 0x0F, 0x0F, {0x1A}, {0x1A}, {0x1A}, {0x1A}, "NANO_DRAGON", "Nano Dragon", nullptr},
|
||||
{EnemyType::NAR_LILY, EP1 | EP2 | RARE, 0x0E, 0x0E, {0x05}, {0x05}, {0x05}, {0x05}, "NAR_LILY", "Nar Lily", "Mil Lily"},
|
||||
{EnemyType::OLGA_FLOW_1, EP2 | BOSS, NONE, NONE, {0x2B}, {0x2B}, {0x2B}, {0x2B, 0x2D, 0x2F}, "OLGA_FLOW_1", "Olga Flow (phase 1)", nullptr},
|
||||
{EnemyType::OLGA_FLOW_2, EP2 | BOSS, 0x4E, 0x4E, {0x2C}, {0x2C}, {0x2C}, {0x2C, 0x2D, 0x3E, 0x2F}, "OLGA_FLOW_2", "Olga Flow (phase 2)", nullptr},
|
||||
{EnemyType::PAL_SHARK, EP1, 0x11, 0x11, {0x50}, {0x55}, {0x4F}, {0x4F}, "PAL_SHARK", "Pal Shark", "Govulmer"},
|
||||
{EnemyType::PAN_ARMS, EP1 | EP2, 0x15, 0x15, {0x31}, {0x31}, {0x31}, {0x31}, "PAN_ARMS", "Pan Arms", nullptr},
|
||||
{EnemyType::PAZUZU_CRATER, EP4 | RARE, 0x5F, 0x4B, {0x08}, {0x08}, {0x08}, {0x08}, "PAZUZU_CRATER", "Pazuzu (crater)", nullptr},
|
||||
{EnemyType::PAZUZU_DESERT, EP4 | RARE, 0x5F, 0x4C, {0x1C}, {0x1C}, {0x1C}, {0x1C}, "PAZUZU_DESERT", "Pazuzu (desert)", nullptr},
|
||||
{EnemyType::PIG_RAY, EP2, 0x4A, NONE, {0x08}, {0x08}, {0x08}, {0x08}, "PIG_RAY", "Pig Ray", nullptr},
|
||||
{EnemyType::POFUILLY_SLIME, EP1, 0x13, 0x13, {0x30}, {0x30}, {0x30}, {0x30}, "POFUILLY_SLIME", "Pofuilly Slime", nullptr},
|
||||
{EnemyType::POISON_LILY, EP1 | EP2, 0x0D, 0x0D, {0x04}, {0x04}, {0x04}, {0x04}, "POISON_LILY", "Poison Lily", "Ob Lily"},
|
||||
{EnemyType::POUILLY_SLIME, EP1 | RARE, 0x14, 0x14, {0x34}, {0x34}, {0x34}, {0x34}, "POUILLY_SLIME", "Pouilly Slime", nullptr},
|
||||
{EnemyType::PYRO_GORAN, EP4, 0x67, 0x54, {0x12}, {0x12, 0x15}, {0x12}, {0x12}, "PYRO_GORAN", "Pyro Goran", nullptr},
|
||||
{EnemyType::RAG_RAPPY, EP1 | EP2, 0x05, 0x05, {0x18}, {0x18}, {0x18}, {0x18}, "RAG_RAPPY", "Rag Rappy", "El Rappy"},
|
||||
{EnemyType::RECOBOX, EP2, 0x43, 0x43, {0x41}, {0x41}, {0x41}, {0x41}, "RECOBOX", "Recobox", nullptr},
|
||||
{EnemyType::RECON, EP2, 0x44, 0x44, {0x42}, {0x42}, {0x42}, {0x42}, "RECON", "Recon", nullptr},
|
||||
{EnemyType::SAINT_MILION, EP4 | BOSS, 0x6A, 0x59, {0x20, 0x22}, {0x20, 0x22}, {0x20, 0x22}, {0x20, 0x22}, "SAINT_MILION", "Saint-Milion", nullptr},
|
||||
{EnemyType::SAINT_MILION_SPINNER, EP4 | BOSS, 0x6A, 0x59, {0x21, 0x23}, {0x21, 0x23}, {0x21, 0x23}, {0x21, 0x23}, "SAINT_MILION_SPINNER", "Saint-Milion (spinner)", nullptr},
|
||||
{EnemyType::SAINT_RAPPY, EP2, 0x4F, 0x4F, {0x19}, {0x19}, {0x19}, {0x19}, "SAINT_RAPPY", "Saint Rappy", nullptr},
|
||||
{EnemyType::SAND_RAPPY_CRATER, EP4, 0x68, 0x55, {0x05}, {0x05}, {0x05}, {0x05}, "SAND_RAPPY_CRATER", "Sand Rappy (crater)", nullptr},
|
||||
{EnemyType::SAND_RAPPY_DESERT, EP4, 0x68, 0x56, {0x17}, {0x17}, {0x17}, {0x17}, "SAND_RAPPY_DESERT", "Sand Rappy (desert)", nullptr},
|
||||
{EnemyType::SATELLITE_LIZARD_CRATER, EP4, 0x5A, 0x44, {0x0D}, {0x0D}, {0x0D}, {0x0D}, "SATELLITE_LIZARD_CRATER", "Satellite Lizard (crater)", nullptr},
|
||||
{EnemyType::SATELLITE_LIZARD_DESERT, EP4, 0x5A, 0x45, {0x1D}, {0x1D}, {0x1D}, {0x1D}, "SATELLITE_LIZARD_DESERT", "Satellite Lizard (desert)", nullptr},
|
||||
{EnemyType::SAVAGE_WOLF, EP1 | EP2, 0x07, 0x07, {0x02}, {0x02}, {0x02}, {0x02}, "SAVAGE_WOLF", "Savage Wolf", "Gulgus"},
|
||||
{EnemyType::SHAMBERTIN, EP4 | BOSS, 0x6B, 0x5A, {0x24, 0x26}, {0x24, 0x26}, {0x24, 0x26}, {0x24, 0x26}, "SHAMBERTIN", "Shambertin", nullptr},
|
||||
{EnemyType::SHAMBERTIN_SPINNER, EP4 | BOSS, 0x6B, 0x5A, {0x25, 0x27}, {0x25, 0x27}, {0x25, 0x27}, {0x25, 0x27}, "SHAMBERTIN_SPINNER", "Shambertin (spinner)", nullptr},
|
||||
{EnemyType::SINOW_BEAT, EP1, 0x1A, 0x1A, {0x06}, {0x06}, {0x06}, {0x06}, "SINOW_BEAT", "Sinow Beat", "Sinow Blue"},
|
||||
{EnemyType::SINOW_BERILL, EP2, 0x3E, 0x3E, {0x06}, {0x06}, {0x06}, {0x06}, "SINOW_BERILL", "Sinow Berill", nullptr},
|
||||
{EnemyType::SINOW_GOLD, EP1, 0x1B, 0x1B, {0x13}, {0x47}, {0x13}, {0x10}, "SINOW_GOLD", "Sinow Gold", "Sinow Red"},
|
||||
{EnemyType::SINOW_SPIGELL, EP2, 0x3F, 0x3F, {0x13}, {0x47}, {0x13}, {0x10}, "SINOW_SPIGELL", "Sinow Spigell", nullptr},
|
||||
{EnemyType::SINOW_ZELE, EP2, 0x46, 0x46, {0x44}, {0x44}, {0x44}, {0x44}, "SINOW_ZELE", "Sinow Zele", nullptr},
|
||||
{EnemyType::SINOW_ZOA, EP2, 0x45, 0x45, {0x43}, {0x43}, {0x43}, {0x43}, "SINOW_ZOA", "Sinow Zoa", nullptr},
|
||||
{EnemyType::SO_DIMENIAN, EP1 | EP2, 0x2B, 0x2B, {0x55}, {0x5C}, {0x54}, {0x54}, "SO_DIMENIAN", "So Dimenian", "Del-D"},
|
||||
{EnemyType::UL_GIBBON, EP2, 0x3B, 0x3B, {0x3B}, {0x3B}, {0x3B}, {0x3B}, "UL_GIBBON", "Ul Gibbon", nullptr},
|
||||
{EnemyType::UL_RAY, EP2, 0x4B, NONE, {0x09}, {0x09}, {0x09}, {0x09}, "UL_RAY", "Ul Ray", nullptr},
|
||||
{EnemyType::VOL_OPT_1, EP1 | BOSS, NONE, NONE, {0x21}, {0x21}, {0x21}, {0x21, 0x22, 0x23}, "VOL_OPT_1", "Vol Opt (phase 1)", "Vol Opt ver.2 (phase 1)"},
|
||||
{EnemyType::VOL_OPT_2, EP1 | BOSS, 0x2E, 0x2E, {0x25}, {0x25}, {0x25}, {0x25, 0x26, 0x28, 0x29, 0x2A}, "VOL_OPT_2", "Vol Opt (phase 2)", "Vol Opt ver.2 (phase 2)"},
|
||||
{EnemyType::VOL_OPT_AMP, EP1 | BOSS, NONE, NONE, {0x24}, {0x24}, {0x24}, {}, "VOL_OPT_AMP", "Vol Opt (amp)", "Vol Opt ver.2 (amp)"},
|
||||
{EnemyType::VOL_OPT_CORE, EP1 | BOSS, NONE, NONE, {0x26}, {0x26}, {0x26}, {}, "VOL_OPT_CORE", "Vol Opt (core)", "Vol Opt ver.2 (core)"},
|
||||
{EnemyType::VOL_OPT_MONITOR, EP1 | BOSS, NONE, NONE, {0x23}, {0x23}, {0x23}, {}, "VOL_OPT_MONITOR", "Vol Opt (monitor)", "Vol Opt ver.2 (monitor)"},
|
||||
{EnemyType::VOL_OPT_PILLAR, EP1 | BOSS, NONE, NONE, {0x22}, {0x22}, {0x22}, {}, "VOL_OPT_PILLAR", "Vol Opt (pillar)", "Vol Opt ver.2 (pillar)"},
|
||||
{EnemyType::YOWIE_CRATER, EP4, 0x59, 0x42, {0x0E}, {0x0E}, {0x0E}, {0x0E}, "YOWIE_CRATER", "Yowie (crater)", nullptr},
|
||||
{EnemyType::YOWIE_DESERT, EP4, 0x59, 0x43, {0x1E}, {0x1E}, {0x1E}, {0x1E}, "YOWIE_DESERT", "Yowie (desert)", nullptr},
|
||||
{EnemyType::ZE_BOOTA, EP4, 0x61, 0x4E, {0x01}, {0x01, 0x02, 0x04}, {0x01}, {0x01}, "ZE_BOOTA", "Ze Boota", nullptr},
|
||||
{EnemyType::ZOL_GIBBON, EP2, 0x3C, 0x3C, {0x3C}, {0x3C}, {0x3C}, {0x3C}, "ZOL_GIBBON", "Zol Gibbon", nullptr},
|
||||
{EnemyType::ZU_CRATER, EP4, 0x5E, 0x49, {0x07}, {0x07}, {0x07}, {0x07}, "ZU_CRATER", "Zu (crater)", nullptr},
|
||||
{EnemyType::ZU_DESERT, EP4, 0x5E, 0x4A, {0x1B}, {0x1B}, {0x1B}, {0x1B}, "ZU_DESERT", "Zu (desert)", nullptr},
|
||||
// clang-format on
|
||||
};
|
||||
|
||||
@@ -201,26 +202,85 @@ const vector<EnemyType>& enemy_types_for_rare_table_index(Episode episode, uint8
|
||||
}
|
||||
}
|
||||
|
||||
const vector<EnemyType>& enemy_types_for_battle_param_stats_index(Episode episode, uint8_t bp_index) {
|
||||
static array<vector<vector<EnemyType>>, 5> data;
|
||||
auto& ret = data.at(static_cast<size_t>(episode));
|
||||
if (ret.empty()) {
|
||||
struct BPIndexCacheEntry {
|
||||
std::set<EnemyType> stats;
|
||||
std::set<EnemyType> attack_data;
|
||||
std::set<EnemyType> resist_data;
|
||||
std::set<EnemyType> movement_data;
|
||||
};
|
||||
|
||||
static const BPIndexCacheEntry& get_bp_index_cache_entry(Episode episode, uint8_t bp_index) {
|
||||
static bool cache_populated = false;
|
||||
static array<vector<BPIndexCacheEntry>, 5> data;
|
||||
if (!cache_populated) {
|
||||
cache_populated = true;
|
||||
for (const auto& def : type_defs) {
|
||||
if (def.valid_in_episode(episode) && !def.bp_stats_indexes.empty()) {
|
||||
// Some enemies (e.g. Ep4 bosses) have multiple stats structures; we use the last one, since that's the only
|
||||
// one used when giving EXP
|
||||
size_t bp_index = def.bp_stats_indexes.back();
|
||||
if (bp_index >= ret.size()) {
|
||||
ret.resize(bp_index + 1);
|
||||
for (const auto& episode : ALL_EPISODES_V4) {
|
||||
if (!def.valid_in_episode(episode)) {
|
||||
continue;
|
||||
}
|
||||
ret[bp_index].emplace_back(def.type);
|
||||
auto& ep_index = data[static_cast<size_t>(episode)];
|
||||
for (const auto& bp_index : def.bp_stats_indexes) {
|
||||
if (bp_index >= ep_index.size()) {
|
||||
ep_index.resize(bp_index + 1);
|
||||
}
|
||||
ep_index[bp_index].stats.emplace(def.type);
|
||||
}
|
||||
for (const auto& bp_index : def.bp_attack_data_indexes) {
|
||||
if (bp_index >= ep_index.size()) {
|
||||
ep_index.resize(bp_index + 1);
|
||||
}
|
||||
ep_index[bp_index].attack_data.emplace(def.type);
|
||||
}
|
||||
for (const auto& bp_index : def.bp_resist_data_indexes) {
|
||||
if (bp_index >= ep_index.size()) {
|
||||
ep_index.resize(bp_index + 1);
|
||||
}
|
||||
ep_index[bp_index].resist_data.emplace(def.type);
|
||||
}
|
||||
for (const auto& bp_index : def.bp_movement_data_indexes) {
|
||||
if (bp_index >= ep_index.size()) {
|
||||
ep_index.resize(bp_index + 1);
|
||||
}
|
||||
ep_index[bp_index].movement_data.emplace(def.type);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
auto& ep_index = data.at(static_cast<size_t>(episode));
|
||||
return ep_index.at(bp_index);
|
||||
}
|
||||
|
||||
static const std::set<EnemyType> empty_vec;
|
||||
|
||||
const std::set<EnemyType>& enemy_types_for_battle_param_stats_index(Episode episode, uint8_t bp_index) {
|
||||
try {
|
||||
return ret.at(bp_index);
|
||||
return get_bp_index_cache_entry(episode, bp_index).stats;
|
||||
} catch (const out_of_range&) {
|
||||
return empty_vec;
|
||||
}
|
||||
}
|
||||
|
||||
const std::set<EnemyType>& enemy_types_for_battle_param_attack_data_index(Episode episode, uint8_t bp_index) {
|
||||
try {
|
||||
return get_bp_index_cache_entry(episode, bp_index).attack_data;
|
||||
} catch (const out_of_range&) {
|
||||
return empty_vec;
|
||||
}
|
||||
}
|
||||
|
||||
const std::set<EnemyType>& enemy_types_for_battle_param_resist_data_index(Episode episode, uint8_t bp_index) {
|
||||
try {
|
||||
return get_bp_index_cache_entry(episode, bp_index).resist_data;
|
||||
} catch (const out_of_range&) {
|
||||
return empty_vec;
|
||||
}
|
||||
}
|
||||
|
||||
const std::set<EnemyType>& enemy_types_for_battle_param_movement_data_index(Episode episode, uint8_t bp_index) {
|
||||
try {
|
||||
return get_bp_index_cache_entry(episode, bp_index).movement_data;
|
||||
} catch (const out_of_range&) {
|
||||
static const vector<EnemyType> empty_vec;
|
||||
return empty_vec;
|
||||
}
|
||||
}
|
||||
|
||||
+6
-1
@@ -3,6 +3,7 @@
|
||||
#include <inttypes.h>
|
||||
|
||||
#include <phosg/Tools.hh>
|
||||
#include <set>
|
||||
|
||||
#include "StaticGameData.hh"
|
||||
#include "Types.hh"
|
||||
@@ -165,6 +166,7 @@ struct EnemyTypeDefinition {
|
||||
std::vector<uint8_t> bp_stats_indexes;
|
||||
std::vector<uint8_t> bp_attack_data_indexes;
|
||||
std::vector<uint8_t> bp_resist_data_indexes;
|
||||
std::vector<uint8_t> bp_movement_data_indexes;
|
||||
// Note: movement data isn't bound as strongly to the enemy types; some enemies use many entries and some use none at
|
||||
// all, so we don't list them here. See notes/movement-data.txt for a listing of which enemies use which entries.
|
||||
const char* enum_name;
|
||||
@@ -200,4 +202,7 @@ template <>
|
||||
EnemyType phosg::enum_for_name<EnemyType>(const char* name);
|
||||
|
||||
const std::vector<EnemyType>& enemy_types_for_rare_table_index(Episode episode, uint8_t rt_index);
|
||||
const std::vector<EnemyType>& enemy_types_for_battle_param_stats_index(Episode episode, uint8_t bp_index);
|
||||
const std::set<EnemyType>& enemy_types_for_battle_param_stats_index(Episode episode, uint8_t bp_index);
|
||||
const std::set<EnemyType>& enemy_types_for_battle_param_attack_data_index(Episode episode, uint8_t bp_index);
|
||||
const std::set<EnemyType>& enemy_types_for_battle_param_resist_data_index(Episode episode, uint8_t bp_index);
|
||||
const std::set<EnemyType>& enemy_types_for_battle_param_movement_data_index(Episode episode, uint8_t bp_index);
|
||||
|
||||
@@ -323,7 +323,7 @@ void PlayerState::apply_assist_card_effect_on_set(shared_ptr<PlayerState> setter
|
||||
if (!s->options.is_nte()) {
|
||||
s->team_num_cards_destroyed[this->team_id] = 0;
|
||||
for (size_t client_id = 0; client_id < 4; client_id++) {
|
||||
const auto other_ps = s->get_player_state(client_id);
|
||||
auto other_ps = s->get_player_state(client_id);
|
||||
if (other_ps && (this->team_id == other_ps->get_team_id())) {
|
||||
auto card = other_ps->get_sc_card();
|
||||
if (card) {
|
||||
|
||||
@@ -1,612 +0,0 @@
|
||||
#include "FunctionCompiler.hh"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <filesystem>
|
||||
#include <phosg/Filesystem.hh>
|
||||
#include <phosg/Hash.hh>
|
||||
#include <phosg/Time.hh>
|
||||
#include <stdexcept>
|
||||
|
||||
#include <resource_file/Emulators/PPC32Emulator.hh>
|
||||
#include <resource_file/Emulators/SH4Emulator.hh>
|
||||
#include <resource_file/Emulators/X86Emulator.hh>
|
||||
|
||||
#include "CommandFormats.hh"
|
||||
#include "CommonFileFormats.hh"
|
||||
#include "Compression.hh"
|
||||
#include "Loggers.hh"
|
||||
|
||||
using namespace std;
|
||||
|
||||
const char* name_for_architecture(CompiledFunctionCode::Architecture arch) {
|
||||
switch (arch) {
|
||||
case CompiledFunctionCode::Architecture::POWERPC:
|
||||
return "PowerPC";
|
||||
case CompiledFunctionCode::Architecture::X86:
|
||||
return "x86";
|
||||
case CompiledFunctionCode::Architecture::SH4:
|
||||
return "SH-4";
|
||||
default:
|
||||
throw logic_error("invalid architecture");
|
||||
}
|
||||
}
|
||||
|
||||
template <bool BE>
|
||||
string CompiledFunctionCode::generate_client_command_t(
|
||||
const unordered_map<string, uint32_t>& label_writes,
|
||||
const void* suffix_data,
|
||||
size_t suffix_size,
|
||||
uint32_t override_relocations_offset) const {
|
||||
using FooterT = RELFileFooterT<BE>;
|
||||
|
||||
FooterT footer;
|
||||
footer.num_relocations = this->relocation_deltas.size();
|
||||
footer.unused1.clear(0);
|
||||
footer.root_offset = this->entrypoint_offset_offset;
|
||||
footer.unused2.clear(0);
|
||||
|
||||
phosg::StringWriter w;
|
||||
if (!label_writes.empty()) {
|
||||
string modified_code = this->code;
|
||||
for (const auto& it : label_writes) {
|
||||
size_t offset = this->label_offsets.at(it.first);
|
||||
if (offset > modified_code.size() - 4) {
|
||||
throw runtime_error("label out of range");
|
||||
}
|
||||
*reinterpret_cast<U32T<FooterT::IsBE>*>(modified_code.data() + offset) = it.second;
|
||||
}
|
||||
w.write(modified_code);
|
||||
} else {
|
||||
w.write(this->code);
|
||||
}
|
||||
if (suffix_size) {
|
||||
w.write(suffix_data, suffix_size);
|
||||
}
|
||||
while (w.size() & 3) {
|
||||
w.put_u8(0);
|
||||
}
|
||||
|
||||
footer.relocations_offset = w.size();
|
||||
|
||||
// Always write at least 4 bytes even if there are no relocations
|
||||
if (this->relocation_deltas.empty()) {
|
||||
w.put_u32(0);
|
||||
}
|
||||
|
||||
if (override_relocations_offset) {
|
||||
footer.relocations_offset = override_relocations_offset;
|
||||
} else {
|
||||
for (uint16_t delta : this->relocation_deltas) {
|
||||
w.put<U16T<FooterT::IsBE>>(delta);
|
||||
}
|
||||
if (this->relocation_deltas.size() & 1) {
|
||||
w.put_u16(0);
|
||||
}
|
||||
}
|
||||
|
||||
w.put(footer);
|
||||
return std::move(w.str());
|
||||
}
|
||||
|
||||
string CompiledFunctionCode::generate_client_command(
|
||||
const unordered_map<string, uint32_t>& label_writes,
|
||||
const void* suffix_data,
|
||||
size_t suffix_size,
|
||||
uint32_t override_relocations_offset) const {
|
||||
if (this->arch == Architecture::POWERPC) {
|
||||
return this->generate_client_command_t<true>(label_writes, suffix_data, suffix_size, override_relocations_offset);
|
||||
} else if ((this->arch == Architecture::X86) || (this->arch == Architecture::SH4)) {
|
||||
return this->generate_client_command_t<false>(label_writes, suffix_data, suffix_size, override_relocations_offset);
|
||||
} else {
|
||||
throw logic_error("invalid architecture");
|
||||
}
|
||||
}
|
||||
|
||||
bool CompiledFunctionCode::is_big_endian() const {
|
||||
return (this->arch == Architecture::POWERPC);
|
||||
}
|
||||
|
||||
static unordered_map<uint32_t, std::string> preprocess_function_code(const std::string& text) {
|
||||
auto parse_specific_version_list = +[](std::string&& text) -> vector<uint32_t> {
|
||||
phosg::strip_whitespace(text);
|
||||
vector<uint32_t> ret;
|
||||
for (auto& vers_token : phosg::split(text, ' ')) {
|
||||
phosg::strip_whitespace(vers_token);
|
||||
if (vers_token.empty()) {
|
||||
continue;
|
||||
}
|
||||
if (vers_token.size() != 4) {
|
||||
throw std::runtime_error("invalid specific_version: " + vers_token);
|
||||
}
|
||||
ret.emplace_back(*reinterpret_cast<const be_uint32_t*>(vers_token.data()));
|
||||
}
|
||||
return ret;
|
||||
};
|
||||
|
||||
// Find a .versions directive and populate specific_versions
|
||||
vector<uint32_t> specific_versions;
|
||||
auto lines = phosg::split(text, '\n');
|
||||
for (auto& line : lines) {
|
||||
if (line.starts_with(".versions ")) {
|
||||
if (!specific_versions.empty()) {
|
||||
throw std::runtime_error("multiple .versions directives in file");
|
||||
}
|
||||
specific_versions = parse_specific_version_list(line.substr(10));
|
||||
if (specific_versions.empty()) {
|
||||
throw std::runtime_error(".versions directive does not specify any versions");
|
||||
}
|
||||
line.clear();
|
||||
}
|
||||
}
|
||||
|
||||
// If there's no .versions directive, just return the text as-is
|
||||
if (specific_versions.empty()) {
|
||||
return {{0, std::move(text)}};
|
||||
}
|
||||
|
||||
vector<deque<string>> version_lines;
|
||||
version_lines.resize(specific_versions.size());
|
||||
|
||||
size_t line_num = 1;
|
||||
vector<uint32_t> current_only_versions;
|
||||
unordered_set<uint32_t> current_only_versions_set;
|
||||
auto add_blank_line = [&]() -> void {
|
||||
for (size_t vers_index = 0; vers_index < specific_versions.size(); vers_index++) {
|
||||
version_lines[vers_index].emplace_back("");
|
||||
}
|
||||
};
|
||||
for (auto& line : lines) {
|
||||
phosg::strip_whitespace(line);
|
||||
if (line.starts_with(".only_versions ")) {
|
||||
current_only_versions = parse_specific_version_list(line.substr(15));
|
||||
current_only_versions_set.clear();
|
||||
for (uint32_t specific_version : current_only_versions) {
|
||||
current_only_versions_set.emplace(specific_version);
|
||||
}
|
||||
add_blank_line();
|
||||
|
||||
} else if (line == ".all_versions") {
|
||||
current_only_versions.clear();
|
||||
current_only_versions_set.clear();
|
||||
add_blank_line();
|
||||
|
||||
} else {
|
||||
size_t vers_offset = line.find("<VERS ");
|
||||
if (vers_offset == string::npos) {
|
||||
for (size_t vers_index = 0; vers_index < specific_versions.size(); vers_index++) {
|
||||
if (current_only_versions.empty() || current_only_versions_set.count(specific_versions[vers_index])) {
|
||||
version_lines[vers_index].emplace_back(line);
|
||||
} else {
|
||||
version_lines[vers_index].emplace_back("");
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
size_t token_index = 0;
|
||||
for (size_t vers_index = 0; vers_index < specific_versions.size(); vers_index++) {
|
||||
if (current_only_versions.empty() || current_only_versions_set.count(specific_versions[vers_index])) {
|
||||
string version_line = line;
|
||||
size_t vers_offset = line.find("<VERS ");
|
||||
while (vers_offset != string::npos) {
|
||||
size_t end_offset = version_line.find('>', vers_offset + 6);
|
||||
if (end_offset == string::npos) {
|
||||
throw runtime_error(std::format("(line {}) unterminated <VERS> replacement", line_num));
|
||||
}
|
||||
auto tokens = phosg::split(version_line.substr(vers_offset + 6, end_offset - vers_offset - 6), ' ');
|
||||
if (tokens.size() <= token_index) {
|
||||
throw runtime_error(std::format("(line {}) invalid <VERS> replacement", line_num));
|
||||
}
|
||||
version_line = version_line.substr(0, vers_offset) + tokens.at(token_index) + version_line.substr(end_offset + 1);
|
||||
vers_offset = version_line.find("<VERS ");
|
||||
}
|
||||
version_lines[vers_index].emplace_back(version_line);
|
||||
token_index++;
|
||||
} else {
|
||||
version_lines[vers_index].emplace_back("");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
line_num++;
|
||||
}
|
||||
|
||||
unordered_map<uint32_t, string> ret;
|
||||
for (size_t z = 0; z < specific_versions.size(); z++) {
|
||||
ret.emplace(specific_versions[z], phosg::join(version_lines.at(z), "\n"));
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static vector<shared_ptr<CompiledFunctionCode>> compile_function_code(
|
||||
CompiledFunctionCode::Architecture arch,
|
||||
const string& function_directory,
|
||||
const string& system_directory,
|
||||
const string& name,
|
||||
const string& text,
|
||||
bool raise_on_any_failure) {
|
||||
unordered_set<string> get_include_stack;
|
||||
function<string(const string&)> get_include = [&](const string& name) -> string {
|
||||
const char* arch_name_token;
|
||||
switch (arch) {
|
||||
case CompiledFunctionCode::Architecture::POWERPC:
|
||||
arch_name_token = "ppc";
|
||||
break;
|
||||
case CompiledFunctionCode::Architecture::X86:
|
||||
arch_name_token = "x86";
|
||||
break;
|
||||
case CompiledFunctionCode::Architecture::SH4:
|
||||
arch_name_token = "sh4";
|
||||
break;
|
||||
default:
|
||||
throw runtime_error("unknown architecture");
|
||||
}
|
||||
|
||||
// Look in the function directory first, then the system directory
|
||||
string asm_filename = std::format("{}/{}.{}.inc.s", function_directory, name, arch_name_token);
|
||||
if (!std::filesystem::is_regular_file(asm_filename)) {
|
||||
asm_filename = std::format("{}/{}.{}.inc.s", system_directory, name, arch_name_token);
|
||||
}
|
||||
if (std::filesystem::is_regular_file(asm_filename)) {
|
||||
if (!get_include_stack.emplace(name).second) {
|
||||
throw runtime_error("mutual recursion between includes: " + name);
|
||||
}
|
||||
ResourceDASM::EmulatorBase::AssembleResult ret;
|
||||
switch (arch) {
|
||||
case CompiledFunctionCode::Architecture::POWERPC:
|
||||
ret = ResourceDASM::PPC32Emulator::assemble(phosg::load_file(asm_filename), get_include);
|
||||
break;
|
||||
case CompiledFunctionCode::Architecture::X86:
|
||||
ret = ResourceDASM::X86Emulator::assemble(phosg::load_file(asm_filename), get_include);
|
||||
break;
|
||||
case CompiledFunctionCode::Architecture::SH4:
|
||||
ret = ResourceDASM::SH4Emulator::assemble(phosg::load_file(asm_filename), get_include);
|
||||
break;
|
||||
default:
|
||||
throw runtime_error("unknown architecture");
|
||||
}
|
||||
get_include_stack.erase(name);
|
||||
return ret.code;
|
||||
}
|
||||
|
||||
string bin_filename = function_directory + "/" + name + ".inc.bin";
|
||||
if (std::filesystem::is_regular_file(bin_filename)) {
|
||||
return phosg::load_file(bin_filename);
|
||||
}
|
||||
bin_filename = system_directory + "/" + name + ".inc.bin";
|
||||
if (std::filesystem::is_regular_file(bin_filename)) {
|
||||
return phosg::load_file(bin_filename);
|
||||
}
|
||||
throw runtime_error("data not found for include: " + name + " (from " + asm_filename + " or " + bin_filename + ")");
|
||||
};
|
||||
|
||||
auto version_texts = preprocess_function_code(text);
|
||||
|
||||
vector<shared_ptr<CompiledFunctionCode>> ret;
|
||||
for (const auto& [specific_version, version_text] : version_texts) {
|
||||
try {
|
||||
ResourceDASM::EmulatorBase::AssembleResult assembled;
|
||||
if (arch == CompiledFunctionCode::Architecture::POWERPC) {
|
||||
assembled = ResourceDASM::PPC32Emulator::assemble(version_text, get_include);
|
||||
} else if (arch == CompiledFunctionCode::Architecture::X86) {
|
||||
assembled = ResourceDASM::X86Emulator::assemble(version_text, get_include);
|
||||
} else if (arch == CompiledFunctionCode::Architecture::SH4) {
|
||||
assembled = ResourceDASM::SH4Emulator::assemble(version_text, get_include);
|
||||
} else {
|
||||
throw runtime_error("invalid architecture");
|
||||
}
|
||||
|
||||
auto compiled = ret.emplace_back(make_shared<CompiledFunctionCode>());
|
||||
compiled->arch = arch;
|
||||
compiled->short_name = name;
|
||||
compiled->specific_version = specific_version;
|
||||
compiled->code = std::move(assembled.code);
|
||||
compiled->label_offsets = std::move(assembled.label_offsets);
|
||||
for (const auto& it : assembled.metadata_keys) {
|
||||
if (it.first == "hide_from_patches_menu") {
|
||||
compiled->hide_from_patches_menu = true;
|
||||
} else if (it.first == "name") {
|
||||
compiled->long_name = it.second;
|
||||
} else if (it.first == "description") {
|
||||
compiled->description = it.second;
|
||||
} else if (it.first == "client_flag") {
|
||||
compiled->client_flag = stoull(it.second, nullptr, 0);
|
||||
} else if (it.first == "show_return_value") {
|
||||
compiled->show_return_value = true;
|
||||
} else {
|
||||
throw runtime_error("unknown metadata key: " + it.first);
|
||||
}
|
||||
}
|
||||
|
||||
set<uint32_t> reloc_indexes;
|
||||
for (const auto& it : compiled->label_offsets) {
|
||||
if (it.first.starts_with("reloc")) {
|
||||
reloc_indexes.emplace(it.second / 4);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
compiled->entrypoint_offset_offset = compiled->label_offsets.at("entry_ptr");
|
||||
} catch (const out_of_range&) {
|
||||
throw runtime_error("code does not contain entry_ptr label");
|
||||
}
|
||||
|
||||
uint32_t prev_index = 0;
|
||||
for (const auto& it : reloc_indexes) {
|
||||
uint32_t delta = it - prev_index;
|
||||
if (delta > 0xFFFF) {
|
||||
throw runtime_error("relocation delta too far away");
|
||||
}
|
||||
compiled->relocation_deltas.emplace_back(delta);
|
||||
prev_index = it;
|
||||
}
|
||||
|
||||
} catch (const exception& e) {
|
||||
string version_str = specific_version ? (" (" + str_for_specific_version(specific_version) + ")") : "";
|
||||
if (raise_on_any_failure) {
|
||||
throw;
|
||||
}
|
||||
function_compiler_log.warning_f("Failed to compile function {}{}: {}", name, version_str, e.what());
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
FunctionCodeIndex::FunctionCodeIndex(const string& directory, bool raise_on_any_failure) {
|
||||
string system_dir_path = directory.ends_with("/") ? (directory + "System") : (directory + "/System");
|
||||
|
||||
uint32_t next_menu_item_id = 1;
|
||||
for (const auto& item : std::filesystem::directory_iterator(directory)) {
|
||||
string subdir_name = item.path().filename().string();
|
||||
string subdir_path = directory.ends_with("/") ? (directory + subdir_name) : (directory + "/" + subdir_name);
|
||||
|
||||
auto add_file = [&](string filename) -> void {
|
||||
try {
|
||||
if (!filename.ends_with(".s")) {
|
||||
return;
|
||||
}
|
||||
|
||||
string name = filename.substr(0, filename.size() - 2);
|
||||
if (name.ends_with(".inc")) {
|
||||
return;
|
||||
}
|
||||
|
||||
bool is_patch = name.ends_with(".patch");
|
||||
if (is_patch) {
|
||||
name.resize(name.size() - 6);
|
||||
}
|
||||
|
||||
// Figure out the version or specific_version
|
||||
CompiledFunctionCode::Architecture arch = CompiledFunctionCode::Architecture::UNKNOWN;
|
||||
uint32_t specific_version = 0;
|
||||
string short_name = name;
|
||||
if (name.ends_with(".ppc")) {
|
||||
arch = CompiledFunctionCode::Architecture::POWERPC;
|
||||
name.resize(name.size() - 4);
|
||||
short_name = name;
|
||||
} else if (name.ends_with(".x86")) {
|
||||
arch = CompiledFunctionCode::Architecture::X86;
|
||||
name.resize(name.size() - 4);
|
||||
short_name = name;
|
||||
} else if (name.ends_with(".sh4")) {
|
||||
arch = CompiledFunctionCode::Architecture::SH4;
|
||||
name.resize(name.size() - 4);
|
||||
short_name = name;
|
||||
} else if (is_patch && (name.size() >= 5) && (name[name.size() - 5] == '.')) {
|
||||
specific_version = (name[name.size() - 4] << 24) | (name[name.size() - 3] << 16) | (name[name.size() - 2] << 8) | name[name.size() - 1];
|
||||
if (specific_version_is_dc(specific_version)) {
|
||||
arch = CompiledFunctionCode::Architecture::SH4;
|
||||
} else if (specific_version_is_gc(specific_version)) {
|
||||
arch = CompiledFunctionCode::Architecture::POWERPC;
|
||||
} else if (specific_version_is_pc_v2(specific_version) ||
|
||||
specific_version_is_xb(specific_version) ||
|
||||
specific_version_is_bb(specific_version)) {
|
||||
arch = CompiledFunctionCode::Architecture::X86;
|
||||
} else {
|
||||
throw runtime_error("unable to determine architecture from specific_version");
|
||||
}
|
||||
short_name = name.substr(0, name.size() - 5);
|
||||
}
|
||||
|
||||
if (arch == CompiledFunctionCode::Architecture::UNKNOWN) {
|
||||
throw runtime_error("unable to determine architecture");
|
||||
}
|
||||
|
||||
string path = subdir_path + "/" + filename;
|
||||
string text = phosg::load_file(path);
|
||||
for (auto code : compile_function_code(arch, subdir_path, system_dir_path, name, text, raise_on_any_failure)) {
|
||||
if (code->specific_version == 0) {
|
||||
code->specific_version = specific_version;
|
||||
}
|
||||
code->source_path = path;
|
||||
code->short_name = short_name;
|
||||
this->name_to_function.emplace(name, code);
|
||||
if (is_patch) {
|
||||
code->menu_item_id = next_menu_item_id++;
|
||||
this->menu_item_id_and_specific_version_to_patch_function.emplace(
|
||||
static_cast<uint64_t>(code->menu_item_id) << 32 | code->specific_version, code);
|
||||
this->name_and_specific_version_to_patch_function.emplace(
|
||||
std::format("{}-{:08X}", code->short_name, code->specific_version), code);
|
||||
}
|
||||
|
||||
string patch_prefix = is_patch ? std::format("[{:08X}] ", code->menu_item_id) : "";
|
||||
function_compiler_log.debug_f("Compiled function {}{} ({}; {})",
|
||||
patch_prefix, name, str_for_specific_version(code->specific_version), name_for_architecture(code->arch));
|
||||
}
|
||||
|
||||
} catch (const exception& e) {
|
||||
if (raise_on_any_failure) {
|
||||
throw runtime_error(format("({}) {}", filename, e.what()));
|
||||
}
|
||||
function_compiler_log.warning_f("Failed to compile function {}: {}", filename, e.what());
|
||||
}
|
||||
};
|
||||
|
||||
if (std::filesystem::is_regular_file(subdir_path)) {
|
||||
add_file(subdir_path);
|
||||
} else if (std::filesystem::is_directory(subdir_path)) {
|
||||
for (const auto& item : std::filesystem::directory_iterator(subdir_path)) {
|
||||
string filename = item.path().filename().string();
|
||||
add_file(filename);
|
||||
}
|
||||
} else {
|
||||
function_compiler_log.warning_f("Skipping {} (unknown file type)", subdir_name);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
shared_ptr<const Menu> FunctionCodeIndex::patch_switches_menu(
|
||||
uint32_t specific_version,
|
||||
const std::unordered_set<std::string>& server_auto_patches_enabled,
|
||||
const std::unordered_set<std::string>& client_auto_patches_enabled) const {
|
||||
auto suffix = std::format("-{:08X}", specific_version);
|
||||
|
||||
auto ret = make_shared<Menu>(MenuID::PATCH_SWITCHES, "Patches");
|
||||
ret->items.emplace_back(PatchesMenuItemID::GO_BACK, "Go back", "Return to the\nmain menu", 0);
|
||||
for (const auto& it : this->name_and_specific_version_to_patch_function) {
|
||||
const auto& fn = it.second;
|
||||
if (fn->hide_from_patches_menu || !it.first.ends_with(suffix) || server_auto_patches_enabled.count(fn->short_name)) {
|
||||
continue;
|
||||
}
|
||||
string name;
|
||||
name.push_back(client_auto_patches_enabled.count(fn->short_name) ? '*' : '-');
|
||||
name += fn->long_name.empty() ? fn->short_name : fn->long_name;
|
||||
ret->items.emplace_back(fn->menu_item_id, name, fn->description, MenuItem::Flag::REQUIRES_SEND_FUNCTION_CALL_RUNS_CODE);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool FunctionCodeIndex::patch_menu_empty(uint32_t specific_version) const {
|
||||
uint32_t mask = specific_version_is_indeterminate(specific_version) ? 0xFF000000 : 0xFFFFFFFF;
|
||||
for (const auto& it : this->menu_item_id_and_specific_version_to_patch_function) {
|
||||
if ((it.first & mask) == (specific_version & mask)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
std::shared_ptr<const CompiledFunctionCode> FunctionCodeIndex::get_patch(
|
||||
const std::string& name, uint32_t specific_version) const {
|
||||
return this->name_and_specific_version_to_patch_function.at(std::format("{}-{:08X}", name, specific_version));
|
||||
}
|
||||
|
||||
DOLFileIndex::DOLFileIndex(const string& directory) {
|
||||
if (!std::filesystem::is_directory(directory)) {
|
||||
function_compiler_log.info_f("DOL file directory is missing");
|
||||
return;
|
||||
}
|
||||
|
||||
auto menu = make_shared<Menu>(MenuID::PROGRAMS, "Programs");
|
||||
this->menu = menu;
|
||||
menu->items.emplace_back(ProgramsMenuItemID::GO_BACK, "Go back", "Return to the\nmain menu", 0);
|
||||
|
||||
uint32_t next_menu_item_id = 0;
|
||||
for (const auto& item : std::filesystem::directory_iterator(directory)) {
|
||||
string filename = item.path().filename().string();
|
||||
bool is_dol = filename.ends_with(".dol");
|
||||
bool is_compressed_dol = filename.ends_with(".dol.prs");
|
||||
if (!is_dol && !is_compressed_dol) {
|
||||
continue;
|
||||
}
|
||||
string name = filename.substr(0, filename.size() - (is_compressed_dol ? 8 : 4));
|
||||
|
||||
try {
|
||||
auto dol = make_shared<File>();
|
||||
dol->menu_item_id = next_menu_item_id++;
|
||||
dol->name = name;
|
||||
|
||||
string path = directory + "/" + filename;
|
||||
string file_data = phosg::load_file(path);
|
||||
|
||||
string description;
|
||||
if (is_compressed_dol) {
|
||||
size_t decompressed_size = prs_decompress_size(file_data);
|
||||
|
||||
phosg::StringWriter w;
|
||||
w.put_u32b(file_data.size());
|
||||
w.put_u32b(decompressed_size);
|
||||
w.write(file_data);
|
||||
while (w.size() & 3) {
|
||||
w.put_u8(0);
|
||||
}
|
||||
dol->data = std::move(w.str());
|
||||
|
||||
string compressed_size_str = phosg::format_size(file_data.size());
|
||||
string decompressed_size_str = phosg::format_size(decompressed_size);
|
||||
function_compiler_log.debug_f("Loaded compressed DOL file {} ({} -> {})",
|
||||
dol->name, compressed_size_str, decompressed_size_str);
|
||||
description = std::format("$C6{}$C7\n{}\n{} (orig)", dol->name, compressed_size_str, decompressed_size_str);
|
||||
|
||||
} else {
|
||||
phosg::StringWriter w;
|
||||
w.put_u32b(0);
|
||||
w.put_u32b(file_data.size());
|
||||
w.write(file_data);
|
||||
while (w.size() & 3) {
|
||||
w.put_u8(0);
|
||||
}
|
||||
dol->data = std::move(w.str());
|
||||
|
||||
string size_str = phosg::format_size(dol->data.size());
|
||||
function_compiler_log.debug_f("Loaded DOL file {} ({})", filename, size_str);
|
||||
description = std::format("$C6{}$C7\n{}", dol->name, size_str);
|
||||
}
|
||||
|
||||
this->name_to_file.emplace(dol->name, dol);
|
||||
this->item_id_to_file.emplace_back(dol);
|
||||
|
||||
menu->items.emplace_back(dol->menu_item_id, dol->name, description, MenuItem::Flag::REQUIRES_SEND_FUNCTION_CALL_RUNS_CODE);
|
||||
|
||||
} catch (const exception& e) {
|
||||
function_compiler_log.warning_f("Failed to load DOL file {}: {}", filename, e.what());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t specific_version_for_gc_header_checksum(uint32_t header_checksum) {
|
||||
static unordered_map<uint32_t, uint32_t> checksum_to_specific_version;
|
||||
if (checksum_to_specific_version.empty()) {
|
||||
struct {
|
||||
char system_code = 'G';
|
||||
char game_code1 = 'P';
|
||||
char game_code2;
|
||||
char region_code;
|
||||
char developer_code1 = '8';
|
||||
char developer_code2 = 'P';
|
||||
uint8_t disc_number = 0;
|
||||
uint8_t version_code;
|
||||
} __attribute__((packed)) data;
|
||||
for (const char* game_code2 = "OS"; *game_code2; game_code2++) {
|
||||
data.game_code2 = *game_code2;
|
||||
for (const char* region_code = "JEP"; *region_code; region_code++) {
|
||||
data.region_code = *region_code;
|
||||
for (uint8_t version_code = 0; version_code < 8; version_code++) {
|
||||
data.version_code = version_code;
|
||||
uint32_t checksum = phosg::crc32(&data, sizeof(data));
|
||||
uint32_t specific_version = 0x33000030 | (*game_code2 << 16) | (*region_code << 8) | version_code;
|
||||
if (!checksum_to_specific_version.emplace(checksum, specific_version).second) {
|
||||
throw logic_error("multiple specific_versions have same header checksum");
|
||||
}
|
||||
}
|
||||
}
|
||||
{
|
||||
// Generate entries for Trial Editions
|
||||
data.region_code = 'J';
|
||||
data.system_code = 'D';
|
||||
data.version_code = 0;
|
||||
uint32_t checksum = phosg::crc32(&data, sizeof(data));
|
||||
uint32_t specific_version = 0x33004A54 | (*game_code2 << 16);
|
||||
if (!checksum_to_specific_version.emplace(checksum, specific_version).second) {
|
||||
throw logic_error("multiple specific_versions have same header checksum");
|
||||
}
|
||||
data.system_code = 'G';
|
||||
}
|
||||
}
|
||||
}
|
||||
return checksum_to_specific_version.at(header_checksum);
|
||||
}
|
||||
@@ -1,95 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <inttypes.h>
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
#include "Menu.hh"
|
||||
|
||||
// TODO: Support x86 and SH4 function calls in the future. Currently we only
|
||||
// support PPC32 because I haven't written an appropriate x86 assembler yet.
|
||||
|
||||
struct CompiledFunctionCode {
|
||||
enum class Architecture {
|
||||
UNKNOWN = 0,
|
||||
POWERPC, // GC
|
||||
X86, // PC, XB, BB
|
||||
SH4, // Dreamcast
|
||||
};
|
||||
Architecture arch;
|
||||
std::string code;
|
||||
std::vector<uint16_t> relocation_deltas;
|
||||
std::unordered_map<std::string, uint32_t> label_offsets;
|
||||
uint32_t entrypoint_offset_offset = 0;
|
||||
std::string source_path; // Path to source file from newserv root
|
||||
std::string short_name; // Based on filename
|
||||
std::string long_name; // From .meta name directive
|
||||
std::string description; // From .meta description directive
|
||||
uint64_t client_flag = 0; // From .meta client_flag directive
|
||||
uint32_t menu_item_id = 0;
|
||||
bool hide_from_patches_menu = false;
|
||||
bool show_return_value = false;
|
||||
uint32_t specific_version = 0; // 0 = not a client-selectable patch
|
||||
|
||||
bool is_big_endian() const;
|
||||
|
||||
template <bool BE>
|
||||
std::string generate_client_command_t(
|
||||
const std::unordered_map<std::string, uint32_t>& label_writes,
|
||||
const void* suffix_data = nullptr,
|
||||
size_t suffix_size = 0,
|
||||
uint32_t override_relocations_offset = 0) const;
|
||||
std::string generate_client_command(
|
||||
const std::unordered_map<std::string, uint32_t>& label_writes = {},
|
||||
const void* suffix_data = nullptr,
|
||||
size_t suffix_size = 0,
|
||||
uint32_t override_relocations_offset = 0) const;
|
||||
};
|
||||
|
||||
const char* name_for_architecture(CompiledFunctionCode::Architecture arch);
|
||||
|
||||
struct FunctionCodeIndex {
|
||||
FunctionCodeIndex() = default;
|
||||
FunctionCodeIndex(const std::string& directory, bool raise_on_any_failure);
|
||||
|
||||
std::unordered_map<std::string, std::shared_ptr<CompiledFunctionCode>> name_to_function;
|
||||
std::unordered_map<uint8_t, std::shared_ptr<CompiledFunctionCode>> index_to_function;
|
||||
std::unordered_map<uint64_t, std::shared_ptr<CompiledFunctionCode>> menu_item_id_and_specific_version_to_patch_function;
|
||||
// Key here is e.g. "PATCHNAME-SPECIFICVERSION", with the latter in hex
|
||||
std::map<std::string, std::shared_ptr<CompiledFunctionCode>> name_and_specific_version_to_patch_function;
|
||||
|
||||
std::shared_ptr<const Menu> patch_switches_menu(
|
||||
uint32_t specific_version,
|
||||
const std::unordered_set<std::string>& server_auto_patches_enabled,
|
||||
const std::unordered_set<std::string>& client_auto_patches_enabled) const;
|
||||
bool patch_menu_empty(uint32_t specific_version) const;
|
||||
|
||||
std::shared_ptr<const CompiledFunctionCode> get_patch(const std::string& name, uint32_t specific_version) const;
|
||||
};
|
||||
|
||||
struct DOLFileIndex {
|
||||
struct File {
|
||||
uint32_t menu_item_id;
|
||||
std::string name;
|
||||
std::string data;
|
||||
bool is_compressed;
|
||||
};
|
||||
|
||||
std::vector<std::shared_ptr<File>> item_id_to_file;
|
||||
std::unordered_map<std::string, std::shared_ptr<File>> name_to_file;
|
||||
std::shared_ptr<const Menu> menu;
|
||||
|
||||
DOLFileIndex() = default;
|
||||
explicit DOLFileIndex(const std::string& directory);
|
||||
|
||||
inline bool empty() const {
|
||||
return this->name_to_file.empty() && this->item_id_to_file.empty();
|
||||
}
|
||||
};
|
||||
|
||||
uint32_t specific_version_for_gc_header_checksum(uint32_t header_checksum);
|
||||
+1
-1
@@ -303,7 +303,7 @@ HTTPServer::HTTPServer(shared_ptr<ServerState> state)
|
||||
client_json.emplace("DFP", p->disp.stats.char_stats.dfp.load());
|
||||
client_json.emplace("ATA", p->disp.stats.char_stats.ata.load());
|
||||
client_json.emplace("LCK", p->disp.stats.char_stats.lck.load());
|
||||
client_json.emplace("EXP", p->disp.stats.experience.load());
|
||||
client_json.emplace("EXP", p->disp.stats.exp.load());
|
||||
client_json.emplace("Meseta", p->disp.stats.meseta.load());
|
||||
auto tech_levels_json = phosg::JSON::dict();
|
||||
for (size_t z = 0; z < 0x13; z++) {
|
||||
|
||||
+129
-206
@@ -1134,7 +1134,7 @@ public:
|
||||
return std::make_pair(event_table.data(), event_table.size());
|
||||
}
|
||||
|
||||
virtual const std::unordered_set<uint32_t>& all_unsealable_items() const {
|
||||
virtual const std::set<uint32_t>& all_unsealable_items() const {
|
||||
return this->unsealable_items;
|
||||
}
|
||||
|
||||
@@ -1178,7 +1178,7 @@ protected:
|
||||
std::vector<SoundRemaps> sound_remaps;
|
||||
std::vector<TechBoost> tech_boosts;
|
||||
std::vector<std::vector<EventItem>> unwrap_table;
|
||||
std::unordered_set<uint32_t> unsealable_items;
|
||||
std::set<uint32_t> unsealable_items;
|
||||
std::vector<RangedSpecial> ranged_specials;
|
||||
};
|
||||
|
||||
@@ -2465,7 +2465,7 @@ public:
|
||||
}
|
||||
|
||||
virtual size_t num_weapon_classes() const {
|
||||
return this->get_data_array_count<ArrayRefT<BE>>(this->root->weapon_table);
|
||||
return get_rel_array_count<ArrayRefT<BE>>(this->all_start_offsets(), this->root->weapon_table);
|
||||
}
|
||||
|
||||
virtual size_t num_weapons_in_class(uint8_t data1_1) const {
|
||||
@@ -2512,7 +2512,7 @@ public:
|
||||
}
|
||||
|
||||
virtual size_t num_tool_classes() const {
|
||||
return this->get_data_array_count<ArrayRefT<BE>>(this->root->tool_table);
|
||||
return get_rel_array_count<ArrayRefT<BE>>(this->all_start_offsets(), this->root->tool_table);
|
||||
}
|
||||
|
||||
virtual size_t num_tools_in_class(uint8_t data1_1) const {
|
||||
@@ -2559,7 +2559,7 @@ public:
|
||||
}
|
||||
|
||||
virtual size_t num_weapon_kinds() const {
|
||||
return this->get_data_array_count<uint8_t>(this->root->weapon_kind_table);
|
||||
return get_rel_array_count<uint8_t>(this->all_start_offsets(), this->root->weapon_kind_table);
|
||||
}
|
||||
|
||||
virtual uint8_t get_weapon_kind(uint8_t data1_1) const {
|
||||
@@ -2567,7 +2567,7 @@ public:
|
||||
}
|
||||
|
||||
virtual size_t num_photon_colors() const {
|
||||
return this->get_data_array_count<PhotonColorEntryT<BE>>(this->root->photon_color_table);
|
||||
return get_rel_array_count<PhotonColorEntryT<BE>>(this->all_start_offsets(), this->root->photon_color_table);
|
||||
}
|
||||
|
||||
virtual const PhotonColorEntry& get_photon_color(size_t index) const {
|
||||
@@ -2575,7 +2575,7 @@ public:
|
||||
}
|
||||
|
||||
virtual size_t num_weapon_ranges() const {
|
||||
return this->get_data_array_count<WeaponRangeT<BE>>(this->root->weapon_range_table);
|
||||
return get_rel_array_count<WeaponRangeT<BE>>(this->all_start_offsets(), this->root->weapon_range_table);
|
||||
}
|
||||
|
||||
virtual const WeaponRange& get_weapon_range(size_t index) const {
|
||||
@@ -2584,9 +2584,9 @@ public:
|
||||
|
||||
virtual size_t num_weapon_sale_divisors() const {
|
||||
if constexpr (requires { this->root->weapon_integral_sale_divisor_table; }) {
|
||||
return this->get_data_array_count<uint8_t>(this->root->weapon_integral_sale_divisor_table);
|
||||
return get_rel_array_count<uint8_t>(this->all_start_offsets(), this->root->weapon_integral_sale_divisor_table);
|
||||
} else {
|
||||
return this->get_data_array_count<F32T<BE>>(this->root->weapon_sale_divisor_table);
|
||||
return get_rel_array_count<F32T<BE>>(this->all_start_offsets(), this->root->weapon_sale_divisor_table);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2647,7 +2647,7 @@ public:
|
||||
|
||||
virtual std::pair<uint32_t, uint32_t> get_star_value_index_range() const {
|
||||
return std::make_pair(
|
||||
ItemStarsFirstID, this->get_data_array_count<uint8_t>(this->root->star_value_table) + ItemStarsFirstID);
|
||||
ItemStarsFirstID, get_rel_array_count<uint8_t>(this->all_start_offsets(), this->root->star_value_table) + ItemStarsFirstID);
|
||||
}
|
||||
|
||||
virtual uint32_t get_special_stars_base_index() const {
|
||||
@@ -2667,14 +2667,15 @@ public:
|
||||
|
||||
virtual std::string get_unknown_a1() const {
|
||||
if constexpr (requires { this->root->unknown_a1; }) {
|
||||
return this->r.pread(this->root->unknown_a1, this->get_data_range_size(this->root->unknown_a1));
|
||||
return this->r.pread(
|
||||
this->root->unknown_a1, get_rel_array_count<uint8_t>(this->all_start_offsets(), this->root->unknown_a1));
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
virtual size_t num_specials() const {
|
||||
return this->get_data_array_count<SpecialT<BE>>(this->root->special_table);
|
||||
return get_rel_array_count<SpecialT<BE>>(this->all_start_offsets(), this->root->special_table);
|
||||
}
|
||||
|
||||
virtual const Special& get_special(uint8_t special) const {
|
||||
@@ -2690,7 +2691,7 @@ public:
|
||||
}
|
||||
|
||||
virtual size_t num_weapon_effects() const {
|
||||
return this->get_data_array_count<WeaponEffectT<BE>>(this->root->weapon_effect_table);
|
||||
return get_rel_array_count<WeaponEffectT<BE>>(this->all_start_offsets(), this->root->weapon_effect_table);
|
||||
}
|
||||
|
||||
virtual const WeaponEffect& get_weapon_effect(size_t index) const {
|
||||
@@ -2699,7 +2700,7 @@ public:
|
||||
|
||||
virtual size_t num_weapon_stat_boost_indexes() const {
|
||||
if constexpr (requires { this->root->weapon_stat_boost_index_table; }) {
|
||||
return this->get_data_range_size(this->root->weapon_stat_boost_index_table);
|
||||
return get_rel_array_count<uint8_t>(this->all_start_offsets(), this->root->weapon_stat_boost_index_table);
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
@@ -2715,7 +2716,7 @@ public:
|
||||
|
||||
virtual size_t num_armor_stat_boost_indexes() const {
|
||||
if constexpr (requires { this->root->armor_stat_boost_index_table; }) {
|
||||
return this->get_data_range_size(this->root->armor_stat_boost_index_table);
|
||||
return get_rel_array_count<uint8_t>(this->all_start_offsets(), this->root->armor_stat_boost_index_table);
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
@@ -2731,7 +2732,7 @@ public:
|
||||
|
||||
virtual size_t num_shield_stat_boost_indexes() const {
|
||||
if constexpr (requires { this->root->shield_stat_boost_index_table; }) {
|
||||
return this->get_data_range_size(this->root->shield_stat_boost_index_table);
|
||||
return get_rel_array_count<uint8_t>(this->all_start_offsets(), this->root->shield_stat_boost_index_table);
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
@@ -2746,7 +2747,7 @@ public:
|
||||
}
|
||||
|
||||
virtual size_t num_stat_boosts() const {
|
||||
return this->get_data_array_count<StatBoostT<BE>>(this->root->stat_boost_table);
|
||||
return get_rel_array_count<StatBoostT<BE>>(this->all_start_offsets(), this->root->stat_boost_table);
|
||||
}
|
||||
|
||||
virtual const StatBoost& get_stat_boost(size_t index) const {
|
||||
@@ -2755,7 +2756,7 @@ public:
|
||||
|
||||
virtual size_t num_shield_effects() const {
|
||||
if constexpr (requires { this->root->shield_effect_table; }) {
|
||||
return this->get_data_array_count<ShieldEffectT<BE>>(this->root->shield_effect_table);
|
||||
return get_rel_array_count<ShieldEffectT<BE>>(this->all_start_offsets(), this->root->shield_effect_table);
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
@@ -2826,13 +2827,16 @@ public:
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return *this->sound_remaps;
|
||||
} else {
|
||||
static const std::vector<SoundRemaps> empty_vec{};
|
||||
return empty_vec;
|
||||
}
|
||||
}
|
||||
|
||||
virtual size_t num_tech_boosts() const {
|
||||
if constexpr (requires { this->root->tech_boost_table; }) {
|
||||
return this->get_data_array_count<TechBoostT<BE>>(this->root->tech_boost_table);
|
||||
return get_rel_array_count<TechBoostT<BE>>(this->all_start_offsets(), this->root->tech_boost_table);
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
@@ -2868,7 +2872,7 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
virtual const std::unordered_set<uint32_t>& all_unsealable_items() const {
|
||||
virtual const std::set<uint32_t>& all_unsealable_items() const {
|
||||
if constexpr (requires { this->root->unsealable_table; }) {
|
||||
if (!this->unsealable_table.has_value()) {
|
||||
auto& ret = this->unsealable_table.emplace();
|
||||
@@ -2880,7 +2884,7 @@ public:
|
||||
}
|
||||
return *this->unsealable_table;
|
||||
} else {
|
||||
static const std::unordered_set<uint32_t> empty_set{};
|
||||
static const std::set<uint32_t> empty_set{};
|
||||
return empty_set;
|
||||
}
|
||||
}
|
||||
@@ -2902,262 +2906,211 @@ public:
|
||||
}
|
||||
|
||||
const std::set<uint32_t>& all_start_offsets() const {
|
||||
if (!this->start_offsets.has_value()) {
|
||||
auto& ret = this->start_offsets.emplace();
|
||||
ret.emplace(r.size() - 0x20); // REL footer
|
||||
ret.emplace(BE ? r.pget_u32b(r.size() - 0x10) : r.pget_u32l(r.size() - 0x10)); // root
|
||||
ret.emplace(BE ? r.pget_u32b(r.size() - 0x20) : r.pget_u32l(r.size() - 0x20)); // relocations
|
||||
|
||||
const auto& footer = r.pget<RELFileFooterT<BE>>(r.size() - sizeof(RELFileFooterT<BE>));
|
||||
auto sub_r = r.sub(footer.relocations_offset, footer.num_relocations * sizeof(U16T<BE>));
|
||||
uint32_t offset = 0;
|
||||
while (!sub_r.eof()) {
|
||||
offset += sub_r.template get<U16T<BE>>() * 4;
|
||||
ret.emplace(r.pget<U32T<BE>>(offset));
|
||||
if (this->start_offsets.empty()) {
|
||||
this->start_offsets = all_relocation_offsets_for_rel_file<BE>(r.pgetv(0, r.size()), r.size());
|
||||
}
|
||||
}
|
||||
return *this->start_offsets;
|
||||
}
|
||||
|
||||
size_t get_data_range_size(size_t start_offset) const {
|
||||
const auto& offsets = this->all_start_offsets();
|
||||
auto it = offsets.lower_bound(start_offset);
|
||||
if (it == offsets.end()) {
|
||||
throw std::out_of_range("start offset out of range");
|
||||
}
|
||||
if (*it == start_offset) {
|
||||
it++;
|
||||
}
|
||||
if (it == offsets.end()) {
|
||||
throw std::out_of_range("no further offset beyond start offset");
|
||||
}
|
||||
return *it - start_offset;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
size_t get_data_array_count(size_t start_offset) const {
|
||||
return this->get_data_range_size(start_offset) / sizeof(T);
|
||||
return this->start_offsets;
|
||||
}
|
||||
|
||||
static std::string serialize(const ItemParameterTable& pmt) {
|
||||
set<uint32_t> relocations;
|
||||
RELFileWriter<BE> rel;
|
||||
RootT root;
|
||||
phosg::StringWriter w;
|
||||
|
||||
if constexpr (!std::is_same_v<HeaderT, EmptyHeader>) {
|
||||
w.put<HeaderT>(HeaderT());
|
||||
rel.template put<HeaderT>(HeaderT());
|
||||
}
|
||||
|
||||
auto align = [&w](size_t alignment) -> void {
|
||||
while (w.size() & (alignment - 1)) {
|
||||
w.put_u8(0);
|
||||
}
|
||||
};
|
||||
auto write_ref = [&w, &relocations](const ArrayRefT<BE>& ref) -> void {
|
||||
w.put<ArrayRefT<BE>>(ref);
|
||||
relocations.emplace(w.size() - 4);
|
||||
};
|
||||
|
||||
if constexpr (requires { root.entry_count; }) {
|
||||
root.entry_count = 0x13;
|
||||
}
|
||||
|
||||
align(4);
|
||||
ArrayRefT<BE> shields_ref{pmt.num_armors_or_shields_in_class(2) - HasImplicitPlaceholders, w.size()};
|
||||
rel.align(4);
|
||||
ArrayRefT<BE> shields_ref{pmt.num_armors_or_shields_in_class(2) - HasImplicitPlaceholders, rel.w.size()};
|
||||
for (size_t data1_2 = 0; data1_2 < (shields_ref.count + HasImplicitPlaceholders); data1_2++) {
|
||||
w.put<ArmorOrShieldT>(pmt.get_armor_or_shield(2, data1_2));
|
||||
rel.template put<ArmorOrShieldT>(pmt.get_armor_or_shield(2, data1_2));
|
||||
}
|
||||
if constexpr (requires { root.shield_stat_boost_index_table; }) {
|
||||
root.shield_stat_boost_index_table = w.size();
|
||||
w.write(pmt.get_shield_stat_boost_index_table());
|
||||
root.shield_stat_boost_index_table = rel.write(pmt.get_shield_stat_boost_index_table());
|
||||
}
|
||||
|
||||
align(4);
|
||||
ArrayRefT<BE> armors_ref{pmt.num_armors_or_shields_in_class(1) - HasImplicitPlaceholders, w.size()};
|
||||
rel.align(4);
|
||||
ArrayRefT<BE> armors_ref{pmt.num_armors_or_shields_in_class(1) - HasImplicitPlaceholders, rel.w.size()};
|
||||
for (size_t data1_2 = 0; data1_2 < (armors_ref.count + HasImplicitPlaceholders); data1_2++) {
|
||||
w.put<ArmorOrShieldT>(pmt.get_armor_or_shield(1, data1_2));
|
||||
rel.template put<ArmorOrShieldT>(pmt.get_armor_or_shield(1, data1_2));
|
||||
}
|
||||
if constexpr (requires { root.armor_stat_boost_index_table; }) {
|
||||
root.armor_stat_boost_index_table = w.size();
|
||||
w.write(pmt.get_armor_stat_boost_index_table());
|
||||
root.armor_stat_boost_index_table = rel.write(pmt.get_armor_stat_boost_index_table());
|
||||
}
|
||||
|
||||
align(4);
|
||||
ArrayRefT<BE> units_ref{pmt.num_units() - HasImplicitPlaceholders, w.size()};
|
||||
rel.align(4);
|
||||
ArrayRefT<BE> units_ref{pmt.num_units() - HasImplicitPlaceholders, rel.w.size()};
|
||||
for (size_t data1_2 = 0; data1_2 < (units_ref.count + HasImplicitPlaceholders); data1_2++) {
|
||||
w.put<UnitT>(pmt.get_unit(data1_2));
|
||||
rel.template put<UnitT>(pmt.get_unit(data1_2));
|
||||
}
|
||||
|
||||
align(4);
|
||||
ArrayRefT<BE> mags_ref{pmt.num_mags() - HasImplicitPlaceholders, w.size()};
|
||||
rel.align(4);
|
||||
ArrayRefT<BE> mags_ref{pmt.num_mags() - HasImplicitPlaceholders, rel.w.size()};
|
||||
for (size_t data1_2 = 0; data1_2 < (mags_ref.count + HasImplicitPlaceholders); data1_2++) {
|
||||
w.put<MagT>(pmt.get_mag(data1_2));
|
||||
rel.template put<MagT>(pmt.get_mag(data1_2));
|
||||
}
|
||||
|
||||
align(4);
|
||||
rel.align(4);
|
||||
std::vector<ArrayRefT<BE>> tool_refs;
|
||||
for (size_t data1_1 = 0; data1_1 < pmt.num_tool_classes(); data1_1++) {
|
||||
auto& ref = tool_refs.emplace_back(ArrayRefT<BE>{pmt.num_tools_in_class(data1_1), w.size()});
|
||||
auto& ref = tool_refs.emplace_back(ArrayRefT<BE>{pmt.num_tools_in_class(data1_1), rel.w.size()});
|
||||
for (size_t data1_2 = 0; data1_2 < ref.count; data1_2++) {
|
||||
w.put<ToolT>(pmt.get_tool(data1_1, data1_2));
|
||||
rel.template put<ToolT>(pmt.get_tool(data1_1, data1_2));
|
||||
}
|
||||
}
|
||||
|
||||
align(4);
|
||||
rel.align(4);
|
||||
std::vector<ArrayRefT<BE>> weapon_refs;
|
||||
for (size_t data1_1 = 0; data1_1 < pmt.num_weapon_classes(); data1_1++) {
|
||||
auto& ref = weapon_refs.emplace_back(ArrayRefT<BE>{pmt.num_weapons_in_class(data1_1), w.size()});
|
||||
auto& ref = weapon_refs.emplace_back(ArrayRefT<BE>{pmt.num_weapons_in_class(data1_1), rel.w.size()});
|
||||
for (size_t data1_2 = 0; data1_2 < ref.count; data1_2++) {
|
||||
w.put<WeaponT>(pmt.get_weapon(data1_1, data1_2));
|
||||
rel.template put<WeaponT>(pmt.get_weapon(data1_1, data1_2));
|
||||
}
|
||||
}
|
||||
if constexpr (requires { root.weapon_stat_boost_index_table; }) {
|
||||
root.weapon_stat_boost_index_table = w.size();
|
||||
w.write(pmt.get_weapon_stat_boost_index_table());
|
||||
root.weapon_stat_boost_index_table = rel.write(pmt.get_weapon_stat_boost_index_table());
|
||||
}
|
||||
|
||||
align(4);
|
||||
root.photon_color_table = w.size();
|
||||
rel.align(4);
|
||||
root.photon_color_table = rel.w.size();
|
||||
for (size_t z = 0; z < pmt.num_photon_colors(); z++) {
|
||||
w.put<PhotonColorEntryT<BE>>(pmt.get_photon_color(z));
|
||||
rel.template put<PhotonColorEntryT<BE>>(pmt.get_photon_color(z));
|
||||
}
|
||||
|
||||
align(4);
|
||||
root.weapon_range_table = w.size();
|
||||
rel.align(4);
|
||||
root.weapon_range_table = rel.w.size();
|
||||
for (size_t z = 0; z < pmt.num_weapon_ranges(); z++) {
|
||||
w.put<WeaponRangeT<BE>>(pmt.get_weapon_range(z));
|
||||
rel.template put<WeaponRangeT<BE>>(pmt.get_weapon_range(z));
|
||||
}
|
||||
|
||||
root.weapon_kind_table = w.size();
|
||||
root.weapon_kind_table = rel.w.size();
|
||||
for (size_t z = 0; z < pmt.num_weapon_classes(); z++) {
|
||||
w.put_u8(pmt.get_weapon_kind(z));
|
||||
rel.w.put_u8(pmt.get_weapon_kind(z));
|
||||
}
|
||||
|
||||
if constexpr (requires { root.weapon_integral_sale_divisor_table; }) {
|
||||
root.weapon_integral_sale_divisor_table = w.size();
|
||||
root.weapon_integral_sale_divisor_table = rel.w.size();
|
||||
for (size_t z = 0; z < pmt.num_weapon_classes(); z++) {
|
||||
w.put_u8(pmt.get_sale_divisor(0, z));
|
||||
rel.w.put_u8(pmt.get_sale_divisor(0, z));
|
||||
}
|
||||
} else {
|
||||
align(4);
|
||||
root.weapon_sale_divisor_table = w.size();
|
||||
rel.align(4);
|
||||
root.weapon_sale_divisor_table = rel.w.size();
|
||||
for (size_t z = 0; z < pmt.num_weapon_sale_divisors(); z++) {
|
||||
w.put<F32T<BE>>(pmt.get_sale_divisor(0, z));
|
||||
rel.template put<F32T<BE>>(pmt.get_sale_divisor(0, z));
|
||||
}
|
||||
}
|
||||
|
||||
if constexpr (requires { root.non_weapon_integral_sale_divisor_table; }) {
|
||||
root.non_weapon_integral_sale_divisor_table = w.size();
|
||||
NonWeaponSaleDivisorsDCProtos sds;
|
||||
sds.armor_divisor = pmt.get_sale_divisor(1, 1);
|
||||
sds.shield_divisor = pmt.get_sale_divisor(1, 2);
|
||||
sds.unit_divisor = pmt.get_sale_divisor(1, 3);
|
||||
w.put<NonWeaponSaleDivisorsDCProtos>(sds);
|
||||
root.non_weapon_integral_sale_divisor_table = rel.template put<NonWeaponSaleDivisorsDCProtos>(sds);
|
||||
} else {
|
||||
align(4);
|
||||
root.non_weapon_sale_divisor_table = w.size();
|
||||
rel.align(4);
|
||||
NonWeaponSaleDivisorsT<BE> sds;
|
||||
sds.armor_divisor = pmt.get_sale_divisor(1, 1);
|
||||
sds.shield_divisor = pmt.get_sale_divisor(1, 2);
|
||||
sds.unit_divisor = pmt.get_sale_divisor(1, 3);
|
||||
sds.mag_divisor = pmt.get_sale_divisor(2, 0);
|
||||
w.put<NonWeaponSaleDivisorsT<BE>>(sds);
|
||||
root.non_weapon_sale_divisor_table = rel.template put<NonWeaponSaleDivisorsT<BE>>(sds);
|
||||
}
|
||||
|
||||
MagFeedResultsListOffsetsT<BE> mag_feed_result_offsets;
|
||||
for (size_t table_index = 0; table_index < 8; table_index++) {
|
||||
mag_feed_result_offsets[table_index] = w.size();
|
||||
mag_feed_result_offsets[table_index] = rel.w.size();
|
||||
for (size_t item_id = 0; item_id < 11; item_id++) {
|
||||
w.put<MagFeedResult>(pmt.get_mag_feed_result(table_index, item_id));
|
||||
rel.template put<MagFeedResult>(pmt.get_mag_feed_result(table_index, item_id));
|
||||
}
|
||||
}
|
||||
|
||||
root.star_value_table = w.size();
|
||||
w.write(pmt.get_star_value_table());
|
||||
root.star_value_table = rel.write(pmt.get_star_value_table());
|
||||
|
||||
if constexpr (requires { root.unknown_a1; }) {
|
||||
align(2);
|
||||
root.unknown_a1 = w.size();
|
||||
w.write(pmt.get_unknown_a1());
|
||||
rel.align(2);
|
||||
root.unknown_a1 = rel.write(pmt.get_unknown_a1());
|
||||
}
|
||||
|
||||
align(2);
|
||||
root.special_table = w.size();
|
||||
rel.align(2);
|
||||
root.special_table = rel.w.size();
|
||||
for (size_t z = 0; z < pmt.num_specials(); z++) {
|
||||
w.put<SpecialT<BE>>(pmt.get_special(z));
|
||||
rel.template put<SpecialT<BE>>(pmt.get_special(z));
|
||||
}
|
||||
|
||||
align(4);
|
||||
root.weapon_effect_table = w.size();
|
||||
rel.align(4);
|
||||
root.weapon_effect_table = rel.w.size();
|
||||
for (size_t z = 0; z < pmt.num_weapon_effects(); z++) {
|
||||
w.put<WeaponEffectT<BE>>(pmt.get_weapon_effect(z));
|
||||
rel.template put<WeaponEffectT<BE>>(pmt.get_weapon_effect(z));
|
||||
}
|
||||
|
||||
align(4);
|
||||
rel.align(4);
|
||||
if constexpr (requires { root.shield_effect_table; }) {
|
||||
root.shield_effect_table = w.size();
|
||||
root.shield_effect_table = rel.w.size();
|
||||
for (size_t z = 0; z < pmt.num_shield_effects(); z++) {
|
||||
w.put<ShieldEffectT<BE>>(pmt.get_shield_effect(z));
|
||||
rel.template put<ShieldEffectT<BE>>(pmt.get_shield_effect(z));
|
||||
}
|
||||
}
|
||||
|
||||
align(4);
|
||||
rel.align(4);
|
||||
if constexpr (requires { root.sound_remap_table; }) {
|
||||
std::vector<SoundRemapTableOffsetsT<BE>> remap_refs;
|
||||
const auto& remaps = pmt.get_all_sound_remaps();
|
||||
for (const auto& remap : remaps) {
|
||||
auto& remap_ref = remap_refs.emplace_back();
|
||||
remap_ref.sound_id = remap.sound_id;
|
||||
remap_ref.remaps_for_rt_index_table = w.size();
|
||||
remap_ref.remaps_for_rt_index_table = rel.w.size();
|
||||
for (uint32_t remap_sound_id : remap.by_rt_index) {
|
||||
w.put<U32T<BE>>(remap_sound_id);
|
||||
rel.template put<U32T<BE>>(remap_sound_id);
|
||||
}
|
||||
remap_ref.remaps_for_char_class_table = w.size();
|
||||
remap_ref.remaps_for_char_class_table = rel.w.size();
|
||||
for (uint32_t remap_sound_id : remap.by_char_class) {
|
||||
w.put<U32T<BE>>(remap_sound_id);
|
||||
rel.template put<U32T<BE>>(remap_sound_id);
|
||||
}
|
||||
}
|
||||
ArrayRefT<BE> remap_vec{remaps.size(), w.size()};
|
||||
ArrayRefT<BE> remap_vec{remaps.size(), rel.w.size()};
|
||||
for (const auto& remap_ref : remap_refs) {
|
||||
w.put<SoundRemapTableOffsetsT<BE>>(remap_ref);
|
||||
relocations.emplace(w.size() - 8);
|
||||
relocations.emplace(w.size() - 4);
|
||||
uint32_t offset = rel.template put<SoundRemapTableOffsetsT<BE>>(remap_ref);
|
||||
rel.relocations.emplace(offset + 4);
|
||||
rel.relocations.emplace(offset + 8);
|
||||
}
|
||||
root.sound_remap_table = w.size();
|
||||
write_ref(remap_vec);
|
||||
root.sound_remap_table = rel.write_ref(remap_vec);
|
||||
}
|
||||
|
||||
align(4);
|
||||
root.stat_boost_table = w.size();
|
||||
rel.align(4);
|
||||
root.stat_boost_table = rel.w.size();
|
||||
for (size_t z = 0; z < pmt.num_stat_boosts(); z++) {
|
||||
w.put<StatBoostT<BE>>(pmt.get_stat_boost(z));
|
||||
rel.template put<StatBoostT<BE>>(pmt.get_stat_boost(z));
|
||||
}
|
||||
|
||||
if constexpr (requires { root.max_tech_level_table; }) {
|
||||
root.max_tech_level_table = w.size();
|
||||
MaxTechniqueLevels max_tech_levels;
|
||||
for (size_t tech_num = 0; tech_num < 0x13; tech_num++) {
|
||||
for (size_t char_class = 0; char_class < 0x0C; char_class++) {
|
||||
max_tech_levels[tech_num][char_class] = pmt.get_max_tech_level(char_class, tech_num);
|
||||
}
|
||||
}
|
||||
w.put<MaxTechniqueLevels>(max_tech_levels);
|
||||
root.max_tech_level_table = rel.template put<MaxTechniqueLevels>(max_tech_levels);
|
||||
}
|
||||
|
||||
ArrayRefT<BE> combination_table_ref;
|
||||
if constexpr (requires { root.combination_table; }) {
|
||||
combination_table_ref.offset = w.size();
|
||||
combination_table_ref.offset = rel.w.size();
|
||||
combination_table_ref.count = pmt.num_item_combinations();
|
||||
for (size_t z = 0; z < combination_table_ref.count; z++) {
|
||||
w.put<ItemCombination>(pmt.get_item_combination(z));
|
||||
rel.template put<ItemCombination>(pmt.get_item_combination(z));
|
||||
}
|
||||
}
|
||||
|
||||
if constexpr (requires { root.tech_boost_table; }) {
|
||||
align(4);
|
||||
root.tech_boost_table = w.size();
|
||||
rel.align(4);
|
||||
root.tech_boost_table = rel.w.size();
|
||||
for (size_t z = 0; z < pmt.num_tech_boosts(); z++) {
|
||||
w.put<TechBoostT<BE>>(pmt.get_tech_boost(z));
|
||||
rel.template put<TechBoostT<BE>>(pmt.get_tech_boost(z));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3165,8 +3118,8 @@ public:
|
||||
if constexpr (requires { root.unwrap_table; }) {
|
||||
for (size_t event = 0; event < pmt.num_events(); event++) {
|
||||
auto [event_items, num_items] = pmt.get_event_items(event);
|
||||
unwrap_table_refs.emplace_back(ArrayRefT<BE>{num_items, w.size()});
|
||||
w.write(event_items, sizeof(EventItem) * num_items);
|
||||
unwrap_table_refs.emplace_back(ArrayRefT<BE>{num_items, rel.w.size()});
|
||||
rel.write(event_items, sizeof(EventItem) * num_items);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3174,95 +3127,65 @@ public:
|
||||
if constexpr (requires { root.unsealable_table; }) {
|
||||
const auto& items = pmt.all_unsealable_items();
|
||||
unsealable_table_ref.count = items.size();
|
||||
unsealable_table_ref.offset = w.size();
|
||||
unsealable_table_ref.offset = rel.w.size();
|
||||
for (const auto& item : items) {
|
||||
UnsealableItem encoded;
|
||||
u32_to_item_code(encoded.item, item);
|
||||
w.put<UnsealableItem>(encoded);
|
||||
rel.template put<UnsealableItem>(encoded);
|
||||
}
|
||||
}
|
||||
|
||||
ArrayRefT<BE> ranged_specials_ref;
|
||||
if constexpr (requires { root.ranged_special_table; }) {
|
||||
ranged_specials_ref.count = pmt.num_ranged_specials();
|
||||
ranged_specials_ref.offset = w.size();
|
||||
ranged_specials_ref.offset = rel.w.size();
|
||||
for (size_t z = 0; z < ranged_specials_ref.count; z++) {
|
||||
w.put<RangedSpecial>(pmt.get_ranged_special(z));
|
||||
rel.template put<RangedSpecial>(pmt.get_ranged_special(z));
|
||||
}
|
||||
}
|
||||
|
||||
align(4);
|
||||
root.armor_table = w.size();
|
||||
write_ref(armors_ref);
|
||||
write_ref(shields_ref);
|
||||
root.unit_table = w.size();
|
||||
write_ref(units_ref);
|
||||
root.mag_table = w.size();
|
||||
write_ref(mags_ref);
|
||||
root.tool_table = w.size();
|
||||
rel.align(4);
|
||||
root.armor_table = rel.write_ref(armors_ref);
|
||||
rel.write_ref(shields_ref);
|
||||
root.unit_table = rel.write_ref(units_ref);
|
||||
root.mag_table = rel.write_ref(mags_ref);
|
||||
root.tool_table = rel.w.size();
|
||||
for (const auto& ref : tool_refs) {
|
||||
write_ref(ref);
|
||||
rel.write_ref(ref);
|
||||
}
|
||||
root.weapon_table = w.size();
|
||||
root.weapon_table = rel.w.size();
|
||||
for (const auto& ref : weapon_refs) {
|
||||
write_ref(ref);
|
||||
rel.write_ref(ref);
|
||||
}
|
||||
if constexpr (requires { root.combination_table; }) {
|
||||
root.combination_table = w.size();
|
||||
write_ref(combination_table_ref);
|
||||
root.combination_table = rel.write_ref(combination_table_ref);
|
||||
}
|
||||
if constexpr (requires { root.unwrap_table; }) {
|
||||
ArrayRefT<BE> event_ref{unwrap_table_refs.size(), w.size()};
|
||||
ArrayRefT<BE> event_ref{unwrap_table_refs.size(), rel.w.size()};
|
||||
for (const auto& ref : unwrap_table_refs) {
|
||||
write_ref(ref);
|
||||
rel.write_ref(ref);
|
||||
}
|
||||
root.unwrap_table = w.size();
|
||||
write_ref(event_ref);
|
||||
root.unwrap_table = rel.write_ref(event_ref);
|
||||
}
|
||||
if constexpr (requires { root.unsealable_table; }) {
|
||||
root.unsealable_table = w.size();
|
||||
write_ref(unsealable_table_ref);
|
||||
root.unsealable_table = rel.write_ref(unsealable_table_ref);
|
||||
}
|
||||
if constexpr (requires { root.ranged_special_table; }) {
|
||||
root.ranged_special_table = w.size();
|
||||
write_ref(ranged_specials_ref);
|
||||
root.ranged_special_table = rel.write_ref(ranged_specials_ref);
|
||||
}
|
||||
|
||||
root.mag_feed_table = w.size();
|
||||
w.put<MagFeedResultsListOffsetsT<BE>>(mag_feed_result_offsets);
|
||||
root.mag_feed_table = rel.template put<MagFeedResultsListOffsetsT<BE>>(mag_feed_result_offsets);
|
||||
for (size_t z = 1; z <= 8; z++) {
|
||||
relocations.emplace(w.size() - (z * 4));
|
||||
rel.relocations.emplace(rel.w.size() - (z * 4));
|
||||
}
|
||||
|
||||
RELFileFooterT<BE> footer;
|
||||
footer.root_offset = w.size();
|
||||
w.put<RootT>(root);
|
||||
uint32_t root_offset = rel.template put<RootT>(root);
|
||||
constexpr size_t root_field_count = (sizeof(RootT) / 4) - ((requires { root.entry_count; }) ? 1 : 0);
|
||||
for (size_t z = 1; z <= root_field_count; z++) {
|
||||
relocations.emplace(w.size() - (z * 4));
|
||||
rel.relocations.emplace(rel.w.size() - (z * 4));
|
||||
}
|
||||
|
||||
align(0x20);
|
||||
footer.relocations_offset = w.size();
|
||||
footer.num_relocations = relocations.size();
|
||||
footer.unused1[0] = 1;
|
||||
uint32_t last_offset = 0;
|
||||
for (uint32_t reloc_offset : relocations) {
|
||||
if (reloc_offset & 3) {
|
||||
throw logic_error("Relocation is not 4-byte aligned");
|
||||
}
|
||||
size_t reloc_value = (reloc_offset - last_offset) >> 2;
|
||||
if (reloc_value > 0xFFFF) {
|
||||
throw runtime_error("Relocation offset is too far away from previous");
|
||||
}
|
||||
w.put<U16T<BE>>(reloc_value);
|
||||
last_offset = reloc_offset;
|
||||
}
|
||||
|
||||
align(0x20);
|
||||
w.put<RELFileFooterT<BE>>(footer);
|
||||
|
||||
return std::move(w.str());
|
||||
return rel.finalize(root_offset);
|
||||
}
|
||||
|
||||
protected:
|
||||
@@ -3270,7 +3193,7 @@ protected:
|
||||
phosg::StringReader r;
|
||||
const RootT* root;
|
||||
|
||||
mutable std::optional<std::set<uint32_t>> start_offsets;
|
||||
mutable std::set<uint32_t> start_offsets;
|
||||
|
||||
mutable std::unordered_map<uint16_t, Weapon> weapons;
|
||||
mutable std::vector<ArmorOrShield> armors;
|
||||
@@ -3292,7 +3215,7 @@ protected:
|
||||
// the matching order matters.
|
||||
mutable std::optional<std::map<uint32_t, std::vector<ItemCombination>>> item_combination_index;
|
||||
|
||||
mutable std::optional<std::unordered_set<uint32_t>> unsealable_table;
|
||||
mutable std::optional<std::set<uint32_t>> unsealable_table;
|
||||
};
|
||||
|
||||
using ItemParameterTableDCNTE = BinaryItemParameterTableT<
|
||||
|
||||
@@ -472,7 +472,7 @@ public:
|
||||
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;
|
||||
virtual const std::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;
|
||||
|
||||
|
||||
+180
-90
@@ -5,14 +5,13 @@
|
||||
#include <phosg/Filesystem.hh>
|
||||
|
||||
#include "CommonFileFormats.hh"
|
||||
#include "Compression.hh"
|
||||
#include "PSOEncryption.hh"
|
||||
#include "StaticGameData.hh"
|
||||
|
||||
using namespace std;
|
||||
|
||||
void LevelTable::reset_to_base(PlayerStats& stats, uint8_t char_class) const {
|
||||
stats.level = 0;
|
||||
stats.experience = 0;
|
||||
stats.exp = 0;
|
||||
stats.char_stats = this->base_stats_for_class(char_class);
|
||||
}
|
||||
|
||||
@@ -28,43 +27,111 @@ void LevelTable::advance_to_level(PlayerStats& stats, uint32_t level, uint8_t ch
|
||||
stats.char_stats.dfp += level_stats.dfp;
|
||||
stats.char_stats.ata += level_stats.ata;
|
||||
// Note: It is not a bug that lck is ignored here; the original code ignores it too.
|
||||
stats.experience = level_stats.experience;
|
||||
stats.exp = level_stats.exp;
|
||||
}
|
||||
}
|
||||
|
||||
LevelTableV2::LevelTableV2(const string& data, bool compressed) {
|
||||
struct Offsets {
|
||||
// TODO: The overall format of this file on V2 has much more data than we actually use. What's known of the
|
||||
// structure so far:
|
||||
le_uint32_t level_deltas; // (5468) -> u32[9] -> LevelStatsDelta[200]
|
||||
le_uint32_t unknown_a1; // (548C) -> float[6]
|
||||
le_uint32_t max_stats; // (54A4) -> PlayerStats[9]
|
||||
le_uint32_t level_100_stats; // (55E8) -> PlayerStats[9]
|
||||
le_uint32_t base_stats; // (57AC) -> u32[9] -> CharacterStats
|
||||
le_uint32_t unknown_a2; // (57D0) -> (0x120 zero bytes)
|
||||
le_uint32_t attack_data; // (58F0) -> AttackData[9]
|
||||
le_uint32_t unknown_a4; // (5AA0) -> parray<parray<float, 5>, 9>
|
||||
le_uint32_t unknown_a5; // (5B54) -> float[9]
|
||||
le_uint32_t unknown_a6; // (5B78) -> (0x30 bytes)
|
||||
le_uint32_t unknown_a7; // (5BA8) -> (0x2D bytes)
|
||||
le_uint32_t unknown_a8; // (5E00) -> u32[3] -> float[0x2D]
|
||||
le_uint32_t unknown_a9; // (5DF4) -> (0x90 bytes)
|
||||
le_uint32_t unknown_a10; // (60D0) -> u32[3] -> (0x10-byte struct)[0x0C]
|
||||
le_uint32_t unknown_a11; // (616C) -> u32[3] -> (0x30-bytes)
|
||||
le_uint32_t unknown_a12; // (64FC) -> u32[3] -> (0x14-byte struct)[0x0F]
|
||||
} __packed_ws__(Offsets, 0x40);
|
||||
|
||||
phosg::StringReader r;
|
||||
string decompressed_data;
|
||||
if (compressed) {
|
||||
decompressed_data = prs_decompress(data);
|
||||
r = phosg::StringReader(decompressed_data);
|
||||
} else {
|
||||
r = phosg::StringReader(data);
|
||||
phosg::JSON LevelTable::json() const {
|
||||
auto base_stats_json = phosg::JSON::list();
|
||||
auto max_stats_json = phosg::JSON::list();
|
||||
auto level_deltas_json = phosg::JSON::list();
|
||||
for (size_t char_class = 0; char_class < this->num_char_classes(); char_class++) {
|
||||
base_stats_json.emplace_back(this->base_stats_for_class(char_class).json());
|
||||
max_stats_json.emplace_back(this->max_stats_for_class(char_class).json());
|
||||
auto this_class_level_deltas_json = phosg::JSON::list();
|
||||
for (size_t level = 0; level < 200; level++) {
|
||||
this_class_level_deltas_json.emplace_back(this->stats_delta_for_level(char_class, level).json());
|
||||
}
|
||||
level_deltas_json.emplace_back(std::move(this_class_level_deltas_json));
|
||||
}
|
||||
return phosg::JSON::dict({
|
||||
{"BaseStats", std::move(base_stats_json)},
|
||||
{"MaxStats", std::move(max_stats_json)},
|
||||
{"LevelDeltas", std::move(level_deltas_json)},
|
||||
});
|
||||
}
|
||||
|
||||
JSONLevelTable::JSONLevelTable(const phosg::JSON& json) {
|
||||
const auto& base_stats_json = json.at("BaseStats").as_list();
|
||||
const auto& max_stats_json = json.at("MaxStats").as_list();
|
||||
const auto& level_deltas_json = json.at("LevelDeltas").as_list();
|
||||
for (size_t char_class = 0; char_class < base_stats_json.size(); char_class++) {
|
||||
this->base_stats.emplace_back(CharacterStats::from_json(*base_stats_json.at(char_class)));
|
||||
this->max_stats.emplace_back(PlayerStats::from_json(*max_stats_json.at(char_class)));
|
||||
const auto& this_class_level_deltas_json = level_deltas_json.at(char_class)->as_list();
|
||||
auto& parsed_deltas = this->level_deltas.emplace_back();
|
||||
for (size_t level = 0; level < 200; level++) {
|
||||
parsed_deltas[level] = LevelStatsDelta::from_json(*this_class_level_deltas_json.at(level));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
size_t JSONLevelTable::num_char_classes() const {
|
||||
return this->base_stats.size();
|
||||
}
|
||||
|
||||
const CharacterStats& JSONLevelTable::base_stats_for_class(uint8_t char_class) const {
|
||||
return this->base_stats.at(char_class);
|
||||
}
|
||||
|
||||
const PlayerStats& JSONLevelTable::max_stats_for_class(uint8_t char_class) const {
|
||||
return this->max_stats.at(char_class);
|
||||
}
|
||||
|
||||
const LevelStatsDelta& JSONLevelTable::stats_delta_for_level(uint8_t char_class, uint8_t level) const {
|
||||
return this->level_deltas.at(char_class).at(level);
|
||||
}
|
||||
|
||||
LevelTableV2::LevelTableV2(const string& data) {
|
||||
struct Root {
|
||||
// The overall format of this file on V2 has much more data than we actually use. This table is sorted by the
|
||||
// offset in the PlayerTable.prs file; note that the offset fields in this structure do not match that order.
|
||||
// ## OFFS WHAT -> TARGET
|
||||
// 0008 level_deltas[0] -> LevelStatsDelta[200] (by level) (the rest follow immediately)
|
||||
// 00 5468 level_deltas -> u32[9] (by char_class)
|
||||
// 04 548C hp_tp_factors -> HPTPFactors[3] (by char_class_class; [0] = hunter, [1] = ranger, [2] = force)
|
||||
// 08 54A4 max_stats -> PlayerStats[9] (by char_class)
|
||||
// 0C 55E8 level_100_stats -> PlayerStats[9] (by char_class)
|
||||
// 572C base_stats[0] -> CharacterStats
|
||||
// 10 57AC base_stats -> u32[9] (by char_class)
|
||||
// 14 57D0 resist_data -> ResistData[9] (by char_class)
|
||||
// 18 58F0 attack_data -> AttackData[9] (by char_class)
|
||||
// 1C 5AA0 unknown_a4 -> float[15][3] (by [???][attack_number])
|
||||
// 20 5B54 unknown_a5 -> float[3][3] (by [strike_number][attack_number])
|
||||
// 24 5B78 unknown_a6 -> float[3][3] (by [strike_number][attack_number]) (may be [4][3] in original code; there are 0xC zero bytes after)
|
||||
// 28 5BA8 unknown_a7 -> uint8_t[15][3] (same indexes as unknown_a4)
|
||||
// 5BD8 unknown_a9[0] -> UnknownA9[15] (index unknown; appears animation-related)
|
||||
// 30 5DF4 unknown_a9 -> u32[3] (by char_class_class)
|
||||
// 2C 5E00 area_sound_configs -> AreaSoundConfig[0x12] (by area)
|
||||
// 5E90 unknown_a10[0] -> (0x10-byte struct)[0x0C] (the rest follow immediately)
|
||||
// 34 60D0 unknown_a10 -> u32[3] (by char_class_class)
|
||||
// 60DC unknown_a11[0] -> WeaponReference[12] (the rest follow immediately)
|
||||
// 38 616C unknown_a11 -> u32[3] (by char_class_class)
|
||||
// 6178 unknown_a12[0] -> UnknownA12[15] (the rest follow immediately)
|
||||
// 3C 64FC unknown_a12 -> u32[3] (by char_class_class)
|
||||
|
||||
/* 00 / 5468 * */ le_uint32_t level_deltas;
|
||||
/* 04 / 548C * */ le_uint32_t hp_tp_factors;
|
||||
/* 08 / 54A4 * */ le_uint32_t max_stats;
|
||||
/* 0C / 55E8 * */ le_uint32_t level_100_stats;
|
||||
/* 10 / 57AC * */ le_uint32_t base_stats;
|
||||
/* 14 / 57D0 * */ le_uint32_t resist_data;
|
||||
/* 18 / 58F0 * */ le_uint32_t attack_data;
|
||||
/* 1C / 5AA0 * */ le_uint32_t unknown_a4;
|
||||
/* 20 / 5B54 * */ le_uint32_t unknown_a5;
|
||||
/* 24 / 5B78 * */ le_uint32_t unknown_a6;
|
||||
/* 28 / 5BA8 * */ le_uint32_t unknown_a7;
|
||||
/* 2C / 5E00 * */ le_uint32_t area_sound_configs;
|
||||
/* 30 / 5DF4 * */ le_uint32_t unknown_a9;
|
||||
/* 34 / 60D0 * */ le_uint32_t unknown_a10;
|
||||
/* 38 / 616C * */ le_uint32_t unknown_a11;
|
||||
/* 3C / 64FC * */ le_uint32_t unknown_a12;
|
||||
} __packed_ws__(Root, 0x40);
|
||||
|
||||
phosg::StringReader r(data);
|
||||
|
||||
const auto& footer = r.pget<RELFileFooter>(r.size() - sizeof(RELFileFooter));
|
||||
const auto& offsets = r.pget<Offsets>(footer.root_offset);
|
||||
const auto& offsets = r.pget<Root>(footer.root_offset);
|
||||
const auto& level_deltas_offsets = r.pget<parray<le_uint32_t, 9>>(offsets.level_deltas);
|
||||
const auto& base_stats_offsets = r.pget<parray<le_uint32_t, 9>>(offsets.base_stats);
|
||||
for (size_t char_class = 0; char_class < 9; char_class++) {
|
||||
@@ -73,17 +140,16 @@ LevelTableV2::LevelTableV2(const string& data, bool compressed) {
|
||||
this->level_deltas[char_class][level] = src_level_deltas[level];
|
||||
}
|
||||
this->max_stats[char_class] = r.pget<PlayerStats>(offsets.max_stats + char_class * sizeof(PlayerStats));
|
||||
this->level_100_stats[char_class] = r.pget<PlayerStats>(offsets.level_100_stats + char_class * sizeof(PlayerStats));
|
||||
this->base_stats[char_class] = r.pget<CharacterStats>(base_stats_offsets[char_class]);
|
||||
}
|
||||
}
|
||||
|
||||
const CharacterStats& LevelTableV2::base_stats_for_class(uint8_t char_class) const {
|
||||
return this->base_stats.at(char_class);
|
||||
size_t LevelTableV2::num_char_classes() const {
|
||||
return 9;
|
||||
}
|
||||
|
||||
const PlayerStats& LevelTableV2::level_100_stats_for_class(uint8_t char_class) const {
|
||||
return this->level_100_stats.at(char_class);
|
||||
const CharacterStats& LevelTableV2::base_stats_for_class(uint8_t char_class) const {
|
||||
return this->base_stats.at(char_class);
|
||||
}
|
||||
|
||||
const PlayerStats& LevelTableV2::max_stats_for_class(uint8_t char_class) const {
|
||||
@@ -94,46 +160,11 @@ const LevelStatsDelta& LevelTableV2::stats_delta_for_level(uint8_t char_class, u
|
||||
return this->level_deltas.at(char_class).at(level);
|
||||
}
|
||||
|
||||
LevelTableV3BE::LevelTableV3BE(const string& data, bool encrypted) {
|
||||
phosg::StringReader r;
|
||||
string decompressed_data;
|
||||
if (encrypted) {
|
||||
auto decrypted = decrypt_pr2_data<true>(data);
|
||||
decompressed_data = prs_decompress(decrypted.compressed_data);
|
||||
if (decompressed_data.size() != decrypted.decompressed_size) {
|
||||
throw runtime_error("decompressed data size does not match expected size");
|
||||
}
|
||||
r = phosg::StringReader(decompressed_data);
|
||||
} else {
|
||||
r = phosg::StringReader(data);
|
||||
}
|
||||
|
||||
// The GC format is very simple (but everything is big-endian):
|
||||
// root:
|
||||
// u32 offset:
|
||||
// u32[12] offsets:
|
||||
// LevelStatsDeltaBE[200] level_deltas
|
||||
const auto& footer = r.pget<RELFileFooterBE>(r.size() - sizeof(RELFileFooterBE));
|
||||
const auto& offsets = r.pget<parray<be_uint32_t, 12>>(r.pget_u32b(footer.root_offset));
|
||||
for (size_t char_class = 0; char_class < 12; char_class++) {
|
||||
const auto& src_deltas = r.pget<parray<LevelStatsDeltaBE, 200>>(offsets[char_class]);
|
||||
for (size_t level = 0; level < 200; level++) {
|
||||
const auto& src_delta = src_deltas[level];
|
||||
auto& dest_delta = this->level_deltas[char_class][level];
|
||||
dest_delta.atp = src_delta.atp;
|
||||
dest_delta.mst = src_delta.mst;
|
||||
dest_delta.evp = src_delta.evp;
|
||||
dest_delta.hp = src_delta.hp;
|
||||
dest_delta.dfp = src_delta.dfp;
|
||||
dest_delta.ata = src_delta.ata;
|
||||
dest_delta.lck = src_delta.lck;
|
||||
dest_delta.tp = src_delta.tp;
|
||||
dest_delta.experience = src_delta.experience;
|
||||
}
|
||||
}
|
||||
size_t LevelTableV3::num_char_classes() const {
|
||||
return 12;
|
||||
}
|
||||
|
||||
const CharacterStats& LevelTableV3BE::base_stats_for_class(uint8_t char_class) const {
|
||||
const CharacterStats& LevelTableV3::base_stats_for_class(uint8_t char_class) const {
|
||||
static const array<CharacterStats, 12> data = {
|
||||
// ATP MST EVP HP DFP ATA LCK
|
||||
CharacterStats{0x0023, 0x001D, 0x002D, 0x0014, 0x0011, 0x001E, 0x000A},
|
||||
@@ -168,31 +199,86 @@ static const array<PlayerStats, 12> max_stats_v3_v4 = {
|
||||
PlayerStats{{0x0474, 0x0407, 0x0384, 0x02CF, 0x0241, 0x06C2, 0x0064}, 0x0064, 0.0f, 0.0f, 0, 0, 0},
|
||||
};
|
||||
|
||||
const PlayerStats& LevelTableV3BE::max_stats_for_class(uint8_t char_class) const {
|
||||
const PlayerStats& LevelTableV3::max_stats_for_class(uint8_t char_class) const {
|
||||
return max_stats_v3_v4.at(char_class);
|
||||
}
|
||||
|
||||
const LevelStatsDelta& LevelTableV3BE::stats_delta_for_level(uint8_t char_class, uint8_t level) const {
|
||||
const LevelStatsDelta& LevelTableV3::stats_delta_for_level(uint8_t char_class, uint8_t level) const {
|
||||
return this->level_deltas.at(char_class).at(level);
|
||||
}
|
||||
|
||||
LevelTableV4::LevelTableV4(const string& data, bool compressed) {
|
||||
struct Offsets {
|
||||
template <bool BE>
|
||||
void parse_level_deltas_t(std::array<std::array<LevelStatsDelta, 200>, 12>& deltas, const string& data) {
|
||||
// The V3 format is very simple:
|
||||
// root:
|
||||
// u32 offset:
|
||||
// u32[12] offsets:
|
||||
// LevelStatsDeltaBE[200] level_deltas
|
||||
phosg::StringReader r(data);
|
||||
const auto& footer = r.pget<RELFileFooterT<BE>>(r.size() - sizeof(RELFileFooterT<BE>));
|
||||
const auto& offsets = r.pget<parray<U32T<BE>, 12>>(r.pget<U32T<BE>>(footer.root_offset));
|
||||
for (size_t char_class = 0; char_class < 12; char_class++) {
|
||||
const auto& src_deltas = r.pget<parray<LevelStatsDeltaT<BE>, 200>>(offsets[char_class]);
|
||||
for (size_t level = 0; level < 200; level++) {
|
||||
deltas[char_class][level] = src_deltas[level];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LevelTableGC::LevelTableGC(const string& data) {
|
||||
parse_level_deltas_t<true>(this->level_deltas, data);
|
||||
}
|
||||
|
||||
LevelTableXB::LevelTableXB(const string& data) {
|
||||
parse_level_deltas_t<false>(this->level_deltas, data);
|
||||
}
|
||||
|
||||
struct RootV4 {
|
||||
le_uint32_t base_stats; // -> u32[12] -> CharacterStats
|
||||
le_uint32_t level_deltas; // -> u32[12] -> LevelStatsDelta[200]
|
||||
} __packed_ws__(Offsets, 8);
|
||||
} __packed_ws__(RootV4, 8);
|
||||
|
||||
phosg::StringReader r;
|
||||
string decompressed_data;
|
||||
if (compressed) {
|
||||
decompressed_data = prs_decompress(data);
|
||||
r = phosg::StringReader(decompressed_data);
|
||||
} else {
|
||||
r = phosg::StringReader(data);
|
||||
std::string LevelTable::serialize_binary_v4() const {
|
||||
RELFileWriter<false> rel;
|
||||
RootV4 root;
|
||||
|
||||
{
|
||||
std::vector<uint32_t> offsets;
|
||||
for (size_t char_class = 0; char_class < this->num_char_classes(); char_class++) {
|
||||
offsets.emplace_back(rel.put<CharacterStats>(this->base_stats_for_class(char_class)));
|
||||
}
|
||||
root.base_stats = rel.w.size();
|
||||
for (uint32_t offset : offsets) {
|
||||
rel.write_offset(offset);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
std::vector<uint32_t> offsets;
|
||||
for (size_t char_class = 0; char_class < this->num_char_classes(); char_class++) {
|
||||
offsets.emplace_back(rel.w.size());
|
||||
for (size_t level = 0; level < 200; level++) {
|
||||
rel.put<LevelStatsDelta>(this->stats_delta_for_level(char_class, level));
|
||||
}
|
||||
}
|
||||
root.level_deltas = rel.w.size();
|
||||
for (uint32_t offset : offsets) {
|
||||
rel.write_offset(offset);
|
||||
}
|
||||
}
|
||||
|
||||
size_t root_offset = rel.put<RootV4>(root);
|
||||
rel.relocations.emplace(root_offset);
|
||||
rel.relocations.emplace(root_offset + 4);
|
||||
|
||||
return rel.finalize(root_offset);
|
||||
}
|
||||
|
||||
LevelTableV4::LevelTableV4(const string& data) {
|
||||
|
||||
phosg::StringReader r(data);
|
||||
const auto& footer = r.pget<RELFileFooter>(r.size() - sizeof(RELFileFooter));
|
||||
const auto& offsets = r.pget<Offsets>(footer.root_offset);
|
||||
const auto& offsets = r.pget<RootV4>(footer.root_offset);
|
||||
const auto& level_deltas_offsets = r.pget<parray<le_uint32_t, 12>>(offsets.level_deltas);
|
||||
const auto& base_stats_offsets = r.pget<parray<le_uint32_t, 12>>(offsets.base_stats);
|
||||
for (size_t char_class = 0; char_class < 12; char_class++) {
|
||||
@@ -204,6 +290,10 @@ LevelTableV4::LevelTableV4(const string& data, bool compressed) {
|
||||
}
|
||||
}
|
||||
|
||||
size_t LevelTableV4::num_char_classes() const {
|
||||
return 12;
|
||||
}
|
||||
|
||||
const CharacterStats& LevelTableV4::base_stats_for_class(uint8_t char_class) const {
|
||||
return this->base_stats.at(char_class);
|
||||
}
|
||||
|
||||
+168
-41
@@ -21,17 +21,34 @@ struct CharacterStatsT {
|
||||
/* 0A */ U16T<BE> ata = 0;
|
||||
/* 0C */ U16T<BE> lck = 0;
|
||||
/* 0E */
|
||||
|
||||
static CharacterStatsT<BE> from_json(const phosg::JSON& json) {
|
||||
return CharacterStatsT<BE>{
|
||||
json.at("ATP").as_int(),
|
||||
json.at("MST").as_int(),
|
||||
json.at("EVP").as_int(),
|
||||
json.at("HP").as_int(),
|
||||
json.at("DFP").as_int(),
|
||||
json.at("ATA").as_int(),
|
||||
json.at("LCK").as_int()};
|
||||
}
|
||||
phosg::JSON json() const {
|
||||
return phosg::JSON::dict({{"ATP", this->atp.load()},
|
||||
{"MST", this->mst.load()},
|
||||
{"EVP", this->evp.load()},
|
||||
{"HP", this->hp.load()},
|
||||
{"DFP", this->dfp.load()},
|
||||
{"ATA", this->ata.load()},
|
||||
{"LCK", this->lck.load()}});
|
||||
}
|
||||
operator CharacterStatsT<!BE>() const {
|
||||
CharacterStatsT<!BE> ret;
|
||||
ret.atp = this->atp;
|
||||
ret.mst = this->mst;
|
||||
ret.evp = this->evp;
|
||||
ret.hp = this->hp;
|
||||
ret.dfp = this->dfp;
|
||||
ret.ata = this->ata;
|
||||
ret.lck = this->lck;
|
||||
return ret;
|
||||
return CharacterStatsT<!BE>{
|
||||
this->atp.load(),
|
||||
this->mst.load(),
|
||||
this->evp.load(),
|
||||
this->hp.load(),
|
||||
this->dfp.load(),
|
||||
this->ata.load(),
|
||||
this->lck.load()};
|
||||
}
|
||||
} __packed_ws_be__(CharacterStatsT, 0x0E);
|
||||
using CharacterStats = CharacterStatsT<false>;
|
||||
@@ -44,37 +61,82 @@ struct PlayerStatsT {
|
||||
/* 10 */ F32T<BE> attack_range = 0.0;
|
||||
/* 14 */ F32T<BE> knockback_range = 0.0;
|
||||
/* 18 */ U32T<BE> level = 0; // Qedit specifies this as tech level when used for enemies
|
||||
/* 1C */ U32T<BE> experience = 0;
|
||||
/* 1C */ U32T<BE> exp = 0;
|
||||
/* 20 */ U32T<BE> meseta = 0; // Qedit specifies this as TP when used for enemies
|
||||
/* 24 */
|
||||
|
||||
operator PlayerStatsT<!BE>() const {
|
||||
PlayerStatsT<!BE> ret;
|
||||
ret.char_stats = this->char_stats;
|
||||
ret.esp = this->esp;
|
||||
ret.attack_range = this->attack_range;
|
||||
ret.knockback_range = this->knockback_range;
|
||||
ret.level = this->level;
|
||||
ret.experience = this->experience;
|
||||
ret.meseta = this->meseta;
|
||||
static PlayerStatsT<BE> from_json(const phosg::JSON& json) {
|
||||
return PlayerStatsT<BE>{
|
||||
CharacterStatsT<BE>::from_json(json),
|
||||
json.at("ESP").as_int(),
|
||||
json.at("AttackRange").as_float(),
|
||||
json.at("KnockbackRange").as_float(),
|
||||
json.at("Level").as_int(),
|
||||
json.at("EXP").as_int(),
|
||||
json.at("Meseta").as_int()};
|
||||
}
|
||||
phosg::JSON json() const {
|
||||
auto ret = this->char_stats.json();
|
||||
ret.emplace("ESP", this->esp.load());
|
||||
ret.emplace("AttackRange", this->attack_range.load());
|
||||
ret.emplace("KnockbackRange", this->knockback_range.load());
|
||||
ret.emplace("Level", this->level.load());
|
||||
ret.emplace("EXP", this->exp.load());
|
||||
ret.emplace("Meseta", this->meseta.load());
|
||||
return ret;
|
||||
}
|
||||
operator PlayerStatsT<!BE>() const {
|
||||
return PlayerStatsT<!BE>{
|
||||
this->char_stats,
|
||||
this->esp.load(),
|
||||
this->attack_range.load(),
|
||||
this->knockback_range.load(),
|
||||
this->level.load(),
|
||||
this->exp.load(),
|
||||
this->meseta.load()};
|
||||
}
|
||||
} __packed_ws_be__(PlayerStatsT, 0x24);
|
||||
using PlayerStats = PlayerStatsT<false>;
|
||||
using PlayerStatsBE = PlayerStatsT<true>;
|
||||
|
||||
template <bool BE>
|
||||
struct LevelStatsDeltaT {
|
||||
/* 00 */ uint8_t atp;
|
||||
/* 01 */ uint8_t mst;
|
||||
/* 02 */ uint8_t evp;
|
||||
/* 03 */ uint8_t hp;
|
||||
/* 04 */ uint8_t dfp;
|
||||
/* 05 */ uint8_t ata;
|
||||
/* 06 */ uint8_t lck;
|
||||
/* 07 */ uint8_t tp;
|
||||
/* 08 */ U32T<BE> experience;
|
||||
/* 00 */ uint8_t atp = 0;
|
||||
/* 01 */ uint8_t mst = 0;
|
||||
/* 02 */ uint8_t evp = 0;
|
||||
/* 03 */ uint8_t hp = 0;
|
||||
/* 04 */ uint8_t dfp = 0;
|
||||
/* 05 */ uint8_t ata = 0;
|
||||
/* 06 */ uint8_t lck = 0;
|
||||
/* 07 */ uint8_t tp = 0;
|
||||
/* 08 */ U32T<BE> exp = 0;
|
||||
/* 0C */
|
||||
static LevelStatsDeltaT<BE> from_json(const phosg::JSON& json) {
|
||||
return LevelStatsDeltaT<BE>{
|
||||
static_cast<uint8_t>(json.at("ATP").as_int()),
|
||||
static_cast<uint8_t>(json.at("MST").as_int()),
|
||||
static_cast<uint8_t>(json.at("EVP").as_int()),
|
||||
static_cast<uint8_t>(json.at("HP").as_int()),
|
||||
static_cast<uint8_t>(json.at("DFP").as_int()),
|
||||
static_cast<uint8_t>(json.at("ATA").as_int()),
|
||||
static_cast<uint8_t>(json.at("LCK").as_int()),
|
||||
static_cast<uint8_t>(json.at("TP").as_int()),
|
||||
static_cast<uint32_t>(json.at("EXP").as_int())};
|
||||
}
|
||||
phosg::JSON json() const {
|
||||
return phosg::JSON::dict({{"ATP", this->atp},
|
||||
{"MST", this->mst},
|
||||
{"EVP", this->evp},
|
||||
{"HP", this->hp},
|
||||
{"DFP", this->dfp},
|
||||
{"ATA", this->ata},
|
||||
{"LCK", this->lck},
|
||||
{"TP", this->tp},
|
||||
{"EXP", this->exp.load()}});
|
||||
}
|
||||
operator LevelStatsDeltaT<!BE>() const {
|
||||
return LevelStatsDeltaT<!BE>{
|
||||
this->atp, this->mst, this->evp, this->hp, this->dfp, this->ata, this->lck, this->tp, this->exp.load()};
|
||||
}
|
||||
|
||||
void apply(CharacterStats& ps) const {
|
||||
ps.ata += this->ata;
|
||||
@@ -95,6 +157,8 @@ class LevelTable {
|
||||
// Offsets structures inside the subclasses' constructor implementations for more details on the file formats.
|
||||
public:
|
||||
virtual ~LevelTable() = default;
|
||||
|
||||
virtual size_t num_char_classes() const = 0;
|
||||
virtual const CharacterStats& base_stats_for_class(uint8_t char_class) const = 0;
|
||||
virtual const PlayerStats& max_stats_for_class(uint8_t char_class) const = 0;
|
||||
virtual const LevelStatsDelta& stats_delta_for_level(uint8_t char_class, uint8_t level) const = 0;
|
||||
@@ -102,50 +166,113 @@ public:
|
||||
void reset_to_base(PlayerStats& stats, uint8_t char_class) const;
|
||||
void advance_to_level(PlayerStats& stats, uint32_t level, uint8_t char_class) const;
|
||||
|
||||
std::string serialize_binary_v4() const;
|
||||
phosg::JSON json() const;
|
||||
|
||||
protected:
|
||||
LevelTable() = default;
|
||||
};
|
||||
|
||||
class LevelTableV2 : public LevelTable { // from PlayerTable.prs (PC)
|
||||
class JSONLevelTable : public LevelTable {
|
||||
public:
|
||||
LevelTableV2(const std::string& data, bool compressed);
|
||||
virtual ~LevelTableV2() = default;
|
||||
JSONLevelTable(const phosg::JSON& json);
|
||||
virtual ~JSONLevelTable() = default;
|
||||
|
||||
virtual size_t num_char_classes() const;
|
||||
virtual const CharacterStats& base_stats_for_class(uint8_t char_class) const;
|
||||
const PlayerStats& level_100_stats_for_class(uint8_t char_class) const;
|
||||
virtual const PlayerStats& max_stats_for_class(uint8_t char_class) const;
|
||||
virtual const LevelStatsDelta& stats_delta_for_level(uint8_t char_class, uint8_t level) const;
|
||||
|
||||
private:
|
||||
std::vector<CharacterStats> base_stats;
|
||||
std::vector<PlayerStats> max_stats;
|
||||
std::vector<std::array<LevelStatsDelta, 200>> level_deltas;
|
||||
};
|
||||
|
||||
class LevelTableV2 : public LevelTable { // from PlayerTable.prs (PC)
|
||||
public:
|
||||
struct HPTPFactors {
|
||||
le_float hp_factor;
|
||||
le_float tp_factor;
|
||||
} __packed_ws__(HPTPFactors, 8);
|
||||
|
||||
struct UnknownA9 {
|
||||
le_float unknown_a1;
|
||||
le_float unknown_a2;
|
||||
le_float unknown_a3;
|
||||
} __packed_ws__(UnknownA9, 0x0C);
|
||||
|
||||
struct AreaSoundConfig {
|
||||
le_uint16_t step_sound;
|
||||
le_uint16_t grass_step_sound;
|
||||
le_uint16_t water_step_sound;
|
||||
parray<uint8_t, 2> unused;
|
||||
} __packed_ws__(AreaSoundConfig, 8);
|
||||
|
||||
struct WeaponReference {
|
||||
le_uint16_t data1_1;
|
||||
le_uint16_t data1_2;
|
||||
} __packed_ws__(WeaponReference, 4);
|
||||
|
||||
struct UnknownA12 {
|
||||
le_float unknown_a1;
|
||||
le_float unknown_a2;
|
||||
le_float unknown_a3;
|
||||
le_float unknown_a4;
|
||||
le_uint32_t unknown_a5;
|
||||
} __packed_ws__(UnknownA12, 0x14);
|
||||
|
||||
explicit LevelTableV2(const std::string& data);
|
||||
virtual ~LevelTableV2() = default;
|
||||
|
||||
virtual size_t num_char_classes() const;
|
||||
virtual const CharacterStats& base_stats_for_class(uint8_t char_class) const;
|
||||
virtual const PlayerStats& max_stats_for_class(uint8_t char_class) const;
|
||||
virtual const LevelStatsDelta& stats_delta_for_level(uint8_t char_class, uint8_t level) const;
|
||||
|
||||
protected:
|
||||
std::array<CharacterStats, 9> base_stats;
|
||||
std::array<PlayerStats, 9> level_100_stats;
|
||||
std::array<PlayerStats, 9> max_stats;
|
||||
std::array<std::array<LevelStatsDelta, 200>, 9> level_deltas;
|
||||
};
|
||||
|
||||
class LevelTableV3BE : public LevelTable { // from PlyLevelTbl.cpt (GC)
|
||||
class LevelTableV3 : public LevelTable { // from PlyLevelTbl.cpt (GC/XB)
|
||||
public:
|
||||
LevelTableV3BE(const std::string& data, bool encrypted);
|
||||
virtual ~LevelTableV3BE() = default;
|
||||
virtual ~LevelTableV3() = default;
|
||||
|
||||
virtual size_t num_char_classes() const;
|
||||
virtual const CharacterStats& base_stats_for_class(uint8_t char_class) const;
|
||||
virtual const PlayerStats& max_stats_for_class(uint8_t char_class) const;
|
||||
virtual const LevelStatsDelta& stats_delta_for_level(uint8_t char_class, uint8_t level) const;
|
||||
|
||||
private:
|
||||
protected:
|
||||
LevelTableV3() = default;
|
||||
std::array<std::array<LevelStatsDelta, 200>, 12> level_deltas;
|
||||
};
|
||||
|
||||
class LevelTableGC : public LevelTableV3 {
|
||||
public:
|
||||
explicit LevelTableGC(const std::string& data);
|
||||
virtual ~LevelTableGC() = default;
|
||||
};
|
||||
|
||||
class LevelTableXB : public LevelTableV3 {
|
||||
public:
|
||||
explicit LevelTableXB(const std::string& data);
|
||||
virtual ~LevelTableXB() = default;
|
||||
};
|
||||
|
||||
class LevelTableV4 : public LevelTable { // from PlyLevelTbl.prs (BB)
|
||||
public:
|
||||
LevelTableV4(const std::string& data, bool compressed);
|
||||
explicit LevelTableV4(const std::string& data);
|
||||
virtual ~LevelTableV4() = default;
|
||||
|
||||
virtual size_t num_char_classes() const;
|
||||
virtual const CharacterStats& base_stats_for_class(uint8_t char_class) const;
|
||||
virtual const PlayerStats& max_stats_for_class(uint8_t char_class) const;
|
||||
virtual const LevelStatsDelta& stats_delta_for_level(uint8_t char_class, uint8_t level) const;
|
||||
|
||||
private:
|
||||
protected:
|
||||
std::array<CharacterStats, 12> base_stats;
|
||||
std::array<std::array<LevelStatsDelta, 200>, 12> level_deltas;
|
||||
};
|
||||
|
||||
+3
-3
@@ -5,11 +5,11 @@
|
||||
using namespace std;
|
||||
|
||||
phosg::PrefixedLogger channel_exceptions_log("[Channel] ", phosg::LogLevel::L_USE_DEFAULT);
|
||||
phosg::PrefixedLogger client_functions_log("[ClientFunctionIndex] ", phosg::LogLevel::L_USE_DEFAULT);
|
||||
phosg::PrefixedLogger client_log("", phosg::LogLevel::L_USE_DEFAULT);
|
||||
phosg::PrefixedLogger command_data_log("[Commands] ", phosg::LogLevel::L_USE_DEFAULT);
|
||||
phosg::PrefixedLogger config_log("[Config] ", phosg::LogLevel::L_USE_DEFAULT);
|
||||
phosg::PrefixedLogger dns_server_log("[DNSServer] ", phosg::LogLevel::L_USE_DEFAULT);
|
||||
phosg::PrefixedLogger function_compiler_log("[FunctionCompiler] ", phosg::LogLevel::L_USE_DEFAULT);
|
||||
phosg::PrefixedLogger ip_stack_simulator_log("[IPStackSimulator] ", phosg::LogLevel::L_USE_DEFAULT);
|
||||
phosg::PrefixedLogger lobby_log("", phosg::LogLevel::L_USE_DEFAULT);
|
||||
phosg::PrefixedLogger patch_index_log("[PatchFileIndex] ", phosg::LogLevel::L_USE_DEFAULT);
|
||||
@@ -30,11 +30,11 @@ static void set_log_level_from_json(
|
||||
|
||||
void set_all_log_levels(phosg::LogLevel level) {
|
||||
channel_exceptions_log.min_level = level;
|
||||
client_functions_log.min_level = level;
|
||||
client_log.min_level = level;
|
||||
command_data_log.min_level = level;
|
||||
config_log.min_level = level;
|
||||
dns_server_log.min_level = level;
|
||||
function_compiler_log.min_level = level;
|
||||
ip_stack_simulator_log.min_level = level;
|
||||
lobby_log.min_level = level;
|
||||
patch_index_log.min_level = level;
|
||||
@@ -47,11 +47,11 @@ void set_all_log_levels(phosg::LogLevel level) {
|
||||
|
||||
void set_log_levels_from_json(const phosg::JSON& json) {
|
||||
set_log_level_from_json(channel_exceptions_log, json, "ChannelExceptions");
|
||||
set_log_level_from_json(client_functions_log, json, "ClientFunctionIndex");
|
||||
set_log_level_from_json(client_log, json, "Clients");
|
||||
set_log_level_from_json(command_data_log, json, "CommandData");
|
||||
set_log_level_from_json(config_log, json, "Config");
|
||||
set_log_level_from_json(dns_server_log, json, "DNSServer");
|
||||
set_log_level_from_json(function_compiler_log, json, "FunctionCompiler");
|
||||
set_log_level_from_json(ip_stack_simulator_log, json, "IPStackSimulator");
|
||||
set_log_level_from_json(lobby_log, json, "Lobbies");
|
||||
set_log_level_from_json(patch_index_log, json, "PatchFileIndex");
|
||||
|
||||
+1
-1
@@ -4,11 +4,11 @@
|
||||
#include <phosg/Strings.hh>
|
||||
|
||||
extern phosg::PrefixedLogger channel_exceptions_log;
|
||||
extern phosg::PrefixedLogger client_functions_log;
|
||||
extern phosg::PrefixedLogger client_log;
|
||||
extern phosg::PrefixedLogger command_data_log;
|
||||
extern phosg::PrefixedLogger config_log;
|
||||
extern phosg::PrefixedLogger dns_server_log;
|
||||
extern phosg::PrefixedLogger function_compiler_log;
|
||||
extern phosg::PrefixedLogger ip_stack_simulator_log;
|
||||
extern phosg::PrefixedLogger lobby_log;
|
||||
extern phosg::PrefixedLogger patch_index_log;
|
||||
|
||||
+201
-63
@@ -4,80 +4,120 @@
|
||||
|
||||
using namespace std;
|
||||
|
||||
struct MotionReference {
|
||||
struct Side {
|
||||
// This specifies which entry in ItemMagMotion.dat is used. The file is just a list of 0x64-byte structures.
|
||||
// 0xFF = no TItemMagSub is created
|
||||
uint8_t motion_table_entry = 0xFF;
|
||||
parray<uint8_t, 5> unknown_a1 = 0;
|
||||
} __packed_ws__(Side, 0x06);
|
||||
parray<Side, 2> sides; // [0] = right side, [1] = left side
|
||||
} __packed_ws__(MotionReference, 0x0C);
|
||||
|
||||
template <bool BE>
|
||||
struct MotionReferenceTables {
|
||||
// It seems that there are two definition tables, but only the first is used on any version of PSO. On v3 and later,
|
||||
// the two offsets point to the same table, but on v2 they don't and the second table contains different data.
|
||||
// TODO: Figure out what the deal is with the different v2 tables.
|
||||
U32T<BE> ref_table; // -> MotionReference[num_mags]
|
||||
U32T<BE> unused_ref_table; // -> MotionReference[num_mags]
|
||||
U32T<BE> ref_table;
|
||||
U32T<BE> unused_ref_table;
|
||||
} __packed_ws_be__(MotionReferenceTables, 0x08);
|
||||
|
||||
template <bool BE>
|
||||
struct ColorEntry {
|
||||
// Colors are specified as 4 floats, each in the range [0, 1], for each color channel. The default colors are:
|
||||
// alpha red green blue color (see StaticGameData.cc)
|
||||
// 1.0 1.0 0.2 0.1 red
|
||||
// 1.0 0.2 0.2 1.0 blue
|
||||
// 1.0 1.0 0.9 0.1 yellow
|
||||
// 1.0 0.1 1.0 0.1 green
|
||||
// 1.0 0.8 0.1 1.0 purple
|
||||
// 1.0 0.1 0.1 0.2 black
|
||||
// 1.0 0.9 1.0 1.0 white
|
||||
// 1.0 0.1 0.9 1.0 cyan
|
||||
// 1.0 0.5 0.3 0.2 brown
|
||||
// 1.0 1.0 0.4 0.0 orange (v3+)
|
||||
// 1.0 0.502 0.545 0.977 light-blue (v3+)
|
||||
// 1.0 0.502 0.502 0.0 olive (v3+)
|
||||
// 1.0 0.0 0.941 0.714 turquoise (v3+)
|
||||
// 1.0 0.8 0.098 0.392 fuchsia (v3+)
|
||||
// 1.0 0.498 0.498 0.498 grey (v3+)
|
||||
// 1.0 0.996 0.996 0.832 cream (v3+)
|
||||
// 1.0 0.996 0.498 0.784 pink (v3+)
|
||||
// 1.0 0.0 0.498 0.322 dark-green (v3+)
|
||||
// 00 => 1.0 1.0 0.2 0.1 red
|
||||
// 01 => 1.0 0.2 0.2 1.0 blue
|
||||
// 02 => 1.0 1.0 0.9 0.1 yellow
|
||||
// 03 => 1.0 0.1 1.0 0.1 green
|
||||
// 04 => 1.0 0.8 0.1 1.0 purple
|
||||
// 05 => 1.0 0.1 0.1 0.2 black
|
||||
// 06 => 1.0 0.9 1.0 1.0 white
|
||||
// 07 => 1.0 0.1 0.9 1.0 cyan
|
||||
// 08 => 1.0 0.5 0.3 0.2 brown
|
||||
// 09 => 1.0 1.0 0.4 0.0 orange (v3+)
|
||||
// 0A => 1.0 0.502 0.545 0.977 light-blue (v3+)
|
||||
// 0B => 1.0 0.502 0.502 0.0 olive (v3+)
|
||||
// 0C => 1.0 0.0 0.941 0.714 turquoise (v3+)
|
||||
// 0D => 1.0 0.8 0.098 0.392 fuchsia (v3+)
|
||||
// 0E => 1.0 0.498 0.498 0.498 grey (v3+)
|
||||
// 0F => 1.0 0.996 0.996 0.832 cream (v3+)
|
||||
// 10 => 1.0 0.996 0.498 0.784 pink (v3+)
|
||||
// 11 => 1.0 0.0 0.498 0.322 dark-green (v3+)
|
||||
// If a mag's color index is invalid (>= 0x12), it is reassigned at equip time using the following logic:
|
||||
// - Set base_index to player->visual.skin if player is an android, or player->visual.costume otherwise
|
||||
// - If (base_index % 9) < 7 (that is, if their costume or body color is one of the colored slots on the character
|
||||
// creation screen), then set the mag color to either (base_index % 9) or (base_index % 9) + 9, with equal
|
||||
// probability.
|
||||
// - If (base_index % 9) >= 7 (that is, if their costume or body color is one of the last two blank-colored slots
|
||||
// on the character creation screen), then set the mag color to any of the available colors, chosen at random.
|
||||
F32T<BE> alpha;
|
||||
F32T<BE> red;
|
||||
F32T<BE> green;
|
||||
F32T<BE> blue;
|
||||
ColorEntry(const VectorXYZTF& c) : alpha(c.t), red(c.x), green(c.y), blue(c.z) {}
|
||||
operator VectorXYZTF() const {
|
||||
return VectorXYZTF{this->red.load(), this->green.load(), this->blue.load(), this->alpha.load()};
|
||||
}
|
||||
} __packed_ws_be__(ColorEntry, 0x10);
|
||||
|
||||
template <bool BE>
|
||||
struct UnknownA3Entry {
|
||||
struct UnknownA3EntryT {
|
||||
uint8_t flags;
|
||||
uint8_t unknown_a2;
|
||||
U16T<BE> unknown_a3;
|
||||
U16T<BE> unknown_a4;
|
||||
U16T<BE> unknown_a5;
|
||||
} __packed_ws_be__(UnknownA3Entry, 0x08);
|
||||
UnknownA3EntryT(const MagEvolutionTable::UnknownA3Entry& e)
|
||||
: flags(e.flags),
|
||||
unknown_a2(e.unknown_a2),
|
||||
unknown_a3(e.unknown_a3),
|
||||
unknown_a4(e.unknown_a4),
|
||||
unknown_a5(e.unknown_a5) {}
|
||||
operator MagEvolutionTable::UnknownA3Entry() const {
|
||||
return MagEvolutionTable::UnknownA3Entry{
|
||||
this->flags, this->unknown_a2, this->unknown_a3, this->unknown_a4, this->unknown_a5};
|
||||
}
|
||||
} __packed_ws_be__(UnknownA3EntryT, 0x08);
|
||||
|
||||
struct HeaderV1 {
|
||||
parray<uint8_t, 4> unknown_a1 = {0x0F, 0xF0, 0x00, 0x00};
|
||||
le_uint32_t unknown_a2 = 0x00000003;
|
||||
le_uint16_t unknown_a3 = 0x00C8;
|
||||
le_uint16_t unknown_a4 = 0x0078;
|
||||
// unknown_a5 added in V2
|
||||
le_float unknown_a6 = 0.25;
|
||||
le_float unknown_a7 = 0.1;
|
||||
le_uint32_t unknown_a8 = 0x00000C00;
|
||||
} __packed_ws__(HeaderV1, 0x18);
|
||||
|
||||
template <bool BE>
|
||||
struct RootV2V3V4 {
|
||||
/* -- / 112K / V1 / V2 / V3 / BB */
|
||||
/* 00 / 0438 / 0438 / 05BC / 0340 / 0400 */ U32T<BE> motion_tables; // -> MotionReferenceTables
|
||||
/* 04 / 0440 / 0440 / 0594 / 0348 / 0408 */ U32T<BE> unknown_a2; // -> (uint8_t[2])[NumMags] (references into unknown_a3)
|
||||
/* 08 / 0498 / 0498 / 0608 / 03CE / 04AE */ U32T<BE> unknown_a3; // -> UnknownA3Entry[max(unknown_a2) + 1]
|
||||
/* 0C / 0510 / 0520 / 06B0 / 0476 / 0556 */ U32T<BE> unknown_a4; // -> uint8_t[NumMags]
|
||||
/* 10 / 053C / 054C / 06EC / 04BC / 05AC */ U32T<BE> color_table; // -> ColorEntry[NumColors]
|
||||
/* 14 / / / 077C / 05DC / 06CC */ U32T<BE> evolution_number_table; // -> uint8_t[NumMags]
|
||||
} __packed_ws_be__(RootV2V3V4, 0x18);
|
||||
struct HeaderV2V3V4 {
|
||||
parray<uint8_t, 4> unknown_a1 = {0x0F, 0xF0, 0x00, 0x00};
|
||||
U32T<BE> unknown_a2 = 0x00000003;
|
||||
U16T<BE> unknown_a3 = 0x00C8;
|
||||
U16T<BE> unknown_a4 = 0x0078;
|
||||
parray<uint8_t, 4> unknown_a5 = {0xC8, 0x00, 0x00, 0x00};
|
||||
F32T<BE> unknown_a6 = 0.25;
|
||||
F32T<BE> unknown_a7 = 0.1;
|
||||
U32T<BE> unknown_a8 = 0x00000C00;
|
||||
} __packed_ws_be__(HeaderV2V3V4, 0x1C);
|
||||
|
||||
// Fields:
|
||||
// 112K / V1 / V2 / V3 / BB R
|
||||
// 0018 / 0018 / 001C / 001C / 001C motion_tables.ref_table // -> MotionReference[NumMags]
|
||||
// 0228 / 0228 / 02D4 / 001C / 001C motion_tables.unused_ref_table // -> MotionReference[NumMags]
|
||||
// 0438 / 0438 / 05BC / 0340 / 0400 * motion_tables; // -> MotionReferenceTables
|
||||
// 0440 / 0440 / 0594 / 0348 / 0408 * unknown_a2; // -> (uint8_t[2])[NumMags] (references into unknown_a3)
|
||||
// 0498 / 0498 / 0608 / 03CE / 04AE * unknown_a3; // -> UnknownA3Entry[max(unknown_a2) + 1]
|
||||
// 0510 / 0520 / 06B0 / 0476 / 0556 * unknown_a4; // -> uint8_t[NumMags]
|
||||
// 053C / 054C / 06EC / 04BC / 05AC * color_table; // -> ColorEntry[NumColors]
|
||||
// ---- / ---- / 077C / 05DC / 06CC * evolution_number_table; // -> uint8_t[NumMags]
|
||||
|
||||
template <bool BE>
|
||||
struct RootV1 {
|
||||
le_uint32_t motion_tables;
|
||||
le_uint32_t unknown_a2;
|
||||
le_uint32_t unknown_a3;
|
||||
le_uint32_t unknown_a4;
|
||||
le_uint32_t color_table;
|
||||
} __packed_ws__(RootV1, 0x14);
|
||||
U32T<BE> motion_tables;
|
||||
U32T<BE> unknown_a2;
|
||||
U32T<BE> unknown_a3;
|
||||
U32T<BE> unknown_a4;
|
||||
U32T<BE> color_table;
|
||||
} __packed_ws_be__(RootV1, 0x14);
|
||||
|
||||
template <bool BE>
|
||||
struct RootV2V3V4 : RootV1<BE> {
|
||||
U32T<BE> evolution_number_table;
|
||||
} __packed_ws_be__(RootV2V3V4, 0x18);
|
||||
|
||||
static uint8_t get_v1_mag_evolution_number(uint8_t data1_1) {
|
||||
static const std::array<uint8_t, 0x2C> v1_evolution_number_table{
|
||||
@@ -90,22 +130,81 @@ static uint8_t get_v1_mag_evolution_number(uint8_t data1_1) {
|
||||
return v1_evolution_number_table[data1_1];
|
||||
}
|
||||
|
||||
template <typename RootT, size_t NumMags, size_t NumColors, bool BE>
|
||||
class MagEvolutionTableT : public MagEvolutionTable {
|
||||
template <typename HeaderT, typename RootT, size_t NumMags, size_t NumColors, bool BE>
|
||||
class BinaryMagEvolutionTableT : public MagEvolutionTable {
|
||||
public:
|
||||
explicit MagEvolutionTableT(std::shared_ptr<const std::string> data)
|
||||
explicit BinaryMagEvolutionTableT(std::shared_ptr<const std::string> data)
|
||||
: data(data), r(*data), root(&r.pget<RootT>(this->r.pget_u32l(this->data->size() - 0x10))) {}
|
||||
virtual ~MagEvolutionTableT() = default;
|
||||
virtual ~BinaryMagEvolutionTableT() = default;
|
||||
|
||||
virtual VectorXYZTF get_color_rgba(size_t index) const {
|
||||
template <typename RawT, typename ParsedT>
|
||||
const ParsedT& add_to_vector_cache(std::vector<ParsedT>& cache, size_t base_offset, size_t index) const {
|
||||
while (cache.size() <= index) {
|
||||
cache.emplace_back(this->r.pget<RawT>(base_offset + sizeof(RawT) * cache.size()));
|
||||
}
|
||||
return cache[index];
|
||||
}
|
||||
|
||||
virtual size_t num_mags() const {
|
||||
return NumMags;
|
||||
}
|
||||
|
||||
virtual size_t num_motion_entries(bool use_second_table) const {
|
||||
const auto& tables = this->r.pget<MotionReferenceTables<BE>>(this->root->motion_tables);
|
||||
return get_rel_array_count<MotionReference>(
|
||||
this->all_start_offsets(), use_second_table ? tables.unused_ref_table : tables.ref_table);
|
||||
}
|
||||
|
||||
virtual const MotionReference& get_motion_reference(bool use_second_table, size_t index) const {
|
||||
if (index >= this->num_motion_entries(use_second_table)) {
|
||||
throw std::logic_error("Invalid motion reference index");
|
||||
}
|
||||
const auto& tables = this->r.pget<MotionReferenceTables<BE>>(this->root->motion_tables);
|
||||
uint32_t array_offset = use_second_table ? tables.unused_ref_table : tables.ref_table;
|
||||
return this->r.pget<MotionReference>(array_offset + sizeof(MotionReference) * index);
|
||||
}
|
||||
|
||||
virtual std::pair<uint8_t, uint8_t> get_unknown_a2(size_t index) const {
|
||||
if (index >= this->num_mags()) {
|
||||
throw std::logic_error("Invalid unknown_a2 index");
|
||||
}
|
||||
uint32_t base_offset = this->root->unknown_a2 + (index * 2);
|
||||
return std::make_pair(this->r.pget_u8(base_offset), this->r.pget_u8(base_offset + 1));
|
||||
}
|
||||
|
||||
virtual size_t num_unknown_a3_entries() const {
|
||||
return get_rel_array_count<UnknownA3EntryT<BE>>(this->all_start_offsets(), this->root->unknown_a3);
|
||||
}
|
||||
|
||||
virtual const UnknownA3Entry& get_unknown_a3(size_t index) const {
|
||||
if (index >= this->num_unknown_a3_entries()) {
|
||||
throw std::logic_error("Invalid unknown_a2 index");
|
||||
}
|
||||
return this->add_to_vector_cache<UnknownA3EntryT<BE>>(this->unknown_a3_entries, this->root->unknown_a3, index);
|
||||
}
|
||||
|
||||
virtual uint8_t get_unknown_a4(size_t index) const {
|
||||
if (index >= this->num_mags()) {
|
||||
throw std::logic_error("Invalid unknown_a4 index");
|
||||
}
|
||||
return this->r.pget_u8(this->root->unknown_a2 + index);
|
||||
}
|
||||
|
||||
virtual size_t num_colors() const {
|
||||
return NumColors;
|
||||
}
|
||||
|
||||
virtual const VectorXYZTF& get_color_rgba(size_t index) const {
|
||||
if (index >= NumColors) {
|
||||
throw runtime_error("invalid mag color index");
|
||||
}
|
||||
const auto& color = this->r.pget<ColorEntry<BE>>(this->root->color_table + sizeof(ColorEntry<BE>) * index);
|
||||
return {color.red.load(), color.green.load(), color.blue.load(), color.alpha.load()};
|
||||
return this->add_to_vector_cache<ColorEntry<BE>>(this->colors, this->root->color_table, index);
|
||||
}
|
||||
|
||||
virtual uint8_t get_evolution_number(uint8_t data1_1) const {
|
||||
if (data1_1 >= this->num_mags()) {
|
||||
throw std::logic_error("Invalid unknown_a4 index");
|
||||
}
|
||||
if constexpr (requires { this->root->evolution_number_table; }) {
|
||||
return this->r.pget_u8(this->root->evolution_number_table + data1_1);
|
||||
} else {
|
||||
@@ -113,10 +212,20 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
const std::set<uint32_t>& all_start_offsets() const {
|
||||
if (this->start_offsets.empty()) {
|
||||
this->start_offsets = all_relocation_offsets_for_rel_file<BE>(r.pgetv(0, r.size()), r.size());
|
||||
}
|
||||
return this->start_offsets;
|
||||
}
|
||||
|
||||
protected:
|
||||
std::shared_ptr<const std::string> data;
|
||||
phosg::StringReader r;
|
||||
const RootT* root;
|
||||
mutable std::set<uint32_t> start_offsets;
|
||||
mutable std::vector<UnknownA3Entry> unknown_a3_entries;
|
||||
mutable std::vector<VectorXYZTF> colors;
|
||||
};
|
||||
|
||||
class MagEvolutionTableDCNTE : public MagEvolutionTable {
|
||||
@@ -124,8 +233,37 @@ public:
|
||||
MagEvolutionTableDCNTE() = default;
|
||||
virtual ~MagEvolutionTableDCNTE() = default;
|
||||
|
||||
virtual VectorXYZTF get_color_rgba(size_t) const {
|
||||
throw runtime_error("mag colors not available on DC NTE");
|
||||
virtual size_t num_mags() const {
|
||||
return 0x2C;
|
||||
}
|
||||
|
||||
virtual size_t num_motion_entries(bool) const {
|
||||
return 0;
|
||||
}
|
||||
virtual const MotionReference& get_motion_reference(bool, size_t) const {
|
||||
throw runtime_error("Mag tables not available on DC NTE");
|
||||
}
|
||||
|
||||
virtual std::pair<uint8_t, uint8_t> get_unknown_a2(size_t) const {
|
||||
throw runtime_error("Mag tables not available on DC NTE");
|
||||
}
|
||||
|
||||
virtual size_t num_unknown_a3_entries() const {
|
||||
return 0;
|
||||
}
|
||||
virtual const UnknownA3Entry& get_unknown_a3(size_t) const {
|
||||
throw runtime_error("Mag tables not available on DC NTE");
|
||||
}
|
||||
|
||||
virtual uint8_t get_unknown_a4(size_t) const {
|
||||
throw runtime_error("Mag tables not available on DC NTE");
|
||||
}
|
||||
|
||||
virtual size_t num_colors() const {
|
||||
return 0;
|
||||
}
|
||||
virtual const VectorXYZTF& get_color_rgba(size_t) const {
|
||||
throw runtime_error("Mag tables not available on DC NTE");
|
||||
}
|
||||
|
||||
virtual uint8_t get_evolution_number(uint8_t data1_1) const {
|
||||
@@ -133,13 +271,13 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
using MagEvolutionTableDC112000 = MagEvolutionTableT<RootV2V3V4<false>, 0x28, 0x09, false>;
|
||||
using MagEvolutionTableV1 = MagEvolutionTableT<RootV2V3V4<false>, 0x28, 0x09, false>;
|
||||
using MagEvolutionTableV2 = MagEvolutionTableT<RootV2V3V4<false>, 0x3A, 0x09, false>;
|
||||
using MagEvolutionTableGCNTE = MagEvolutionTableT<RootV2V3V4<true>, 0x3A, 0x09, true>;
|
||||
using MagEvolutionTableGC = MagEvolutionTableT<RootV2V3V4<true>, 0x43, 0x12, true>;
|
||||
using MagEvolutionTableXB = MagEvolutionTableT<RootV2V3V4<false>, 0x43, 0x12, false>;
|
||||
using MagEvolutionTableV4 = MagEvolutionTableT<RootV2V3V4<false>, 0x53, 0x12, false>;
|
||||
using MagEvolutionTableDC112000 = BinaryMagEvolutionTableT<HeaderV1, RootV1<false>, 0x28, 0x09, false>;
|
||||
using MagEvolutionTableV1 = BinaryMagEvolutionTableT<HeaderV1, RootV1<false>, 0x28, 0x09, false>;
|
||||
using MagEvolutionTableV2 = BinaryMagEvolutionTableT<HeaderV2V3V4<false>, RootV2V3V4<false>, 0x3A, 0x09, false>;
|
||||
using MagEvolutionTableGCNTE = BinaryMagEvolutionTableT<HeaderV2V3V4<true>, RootV2V3V4<true>, 0x3A, 0x09, true>;
|
||||
using MagEvolutionTableGC = BinaryMagEvolutionTableT<HeaderV2V3V4<true>, RootV2V3V4<true>, 0x43, 0x12, true>;
|
||||
using MagEvolutionTableXB = BinaryMagEvolutionTableT<HeaderV2V3V4<false>, RootV2V3V4<false>, 0x43, 0x12, false>;
|
||||
using MagEvolutionTableV4 = BinaryMagEvolutionTableT<HeaderV2V3V4<false>, RootV2V3V4<false>, 0x53, 0x12, false>;
|
||||
|
||||
std::shared_ptr<MagEvolutionTable> MagEvolutionTable::create(
|
||||
std::shared_ptr<const std::string> data, Version version) {
|
||||
|
||||
@@ -14,11 +14,43 @@
|
||||
|
||||
class MagEvolutionTable {
|
||||
public:
|
||||
struct MotionReference {
|
||||
struct Side {
|
||||
// This specifies which entry in ItemMagMotion.dat is used. The file is just a list of 0x64-byte structures.
|
||||
// 0xFF = no TItemMagSub is created
|
||||
uint8_t motion_table_entry = 0xFF;
|
||||
parray<uint8_t, 5> unknown_a1 = 0;
|
||||
} __packed_ws__(Side, 0x06);
|
||||
parray<Side, 2> sides; // [0] = right side, [1] = left side
|
||||
} __packed_ws__(MotionReference, 0x0C);
|
||||
|
||||
struct UnknownA3Entry {
|
||||
uint8_t flags;
|
||||
uint8_t unknown_a2;
|
||||
uint16_t unknown_a3;
|
||||
uint16_t unknown_a4;
|
||||
uint16_t unknown_a5;
|
||||
};
|
||||
|
||||
virtual ~MagEvolutionTable() = default;
|
||||
|
||||
static std::shared_ptr<MagEvolutionTable> create(std::shared_ptr<const std::string> data, Version version);
|
||||
|
||||
virtual VectorXYZTF get_color_rgba(size_t index) const = 0;
|
||||
virtual size_t num_mags() const = 0;
|
||||
|
||||
virtual size_t num_motion_entries(bool use_second_table) const = 0;
|
||||
virtual const MotionReference& get_motion_reference(bool use_second_table, size_t index) const = 0;
|
||||
|
||||
virtual std::pair<uint8_t, uint8_t> get_unknown_a2(size_t index) const = 0;
|
||||
|
||||
virtual size_t num_unknown_a3_entries() const = 0;
|
||||
virtual const UnknownA3Entry& get_unknown_a3(size_t index) const = 0;
|
||||
|
||||
virtual uint8_t get_unknown_a4(size_t index) const = 0;
|
||||
|
||||
virtual size_t num_colors() const = 0;
|
||||
virtual const VectorXYZTF& get_color_rgba(size_t index) const = 0;
|
||||
|
||||
virtual uint8_t get_evolution_number(uint8_t data1_1) const = 0;
|
||||
|
||||
protected:
|
||||
|
||||
+136
-42
@@ -23,9 +23,11 @@
|
||||
|
||||
#include "AddressTranslator.hh"
|
||||
#include "BMLArchive.hh"
|
||||
#include "CommonFileFormats.hh"
|
||||
#include "Compression.hh"
|
||||
#include "DCSerialNumbers.hh"
|
||||
#include "DNSServer.hh"
|
||||
#include "DOLFileIndex.hh"
|
||||
#include "DownloadSession.hh"
|
||||
#include "GSLArchive.hh"
|
||||
#include "GameServer.hh"
|
||||
@@ -1750,18 +1752,22 @@ Action a_assemble_quest_script(
|
||||
write_output_data(args, result_data.data(), result_data.size(), compress ? "bin" : "bind");
|
||||
});
|
||||
|
||||
Action a_assemble_all_patches(
|
||||
"assemble-all-patches", "\
|
||||
assemble-all-patches [--skip-encrypted]\n\
|
||||
Action a_assemble_all_client_functions(
|
||||
"assemble-all-client-functions", "\
|
||||
assemble-all-client-functions [--skip-encrypted] OUTPUT-DIRECTORY\n\
|
||||
Assemble all patches in the system/client-functions directory, and produce\n\
|
||||
two compiled .bin files for each patch (one unencrypted, for most PSO\n\
|
||||
two compiled .bin files for each patch: one unencrypted, for most PSO\n\
|
||||
versions, and one encrypted, for PSO GC JP v1.4, JP Ep3, and Ep3 Trial\n\
|
||||
Edition). The output files are saved in system/client-functions.\n",
|
||||
Edition. If --skip-encrypted is given, only the unencrypted .bin files are\n\
|
||||
created.\n",
|
||||
+[](phosg::Arguments& args) {
|
||||
auto fci = make_shared<FunctionCodeIndex>("system/client-functions", false);
|
||||
auto fci = make_shared<ClientFunctionIndex>("system/client-functions", false);
|
||||
|
||||
const std::string& output_dir = args.get<string>(1);
|
||||
std::filesystem::create_directories(output_dir);
|
||||
|
||||
bool skip_encrypted = args.get<bool>("skip-encrypted");
|
||||
auto process_code = [&](shared_ptr<const CompiledFunctionCode> code,
|
||||
auto process_code = [&](shared_ptr<const ClientFunctionIndex::Function> code,
|
||||
uint32_t checksum_addr,
|
||||
uint32_t checksum_size,
|
||||
uint32_t override_start_addr) -> void {
|
||||
@@ -1774,34 +1780,18 @@ Action a_assemble_all_patches(
|
||||
code, {}, nullptr, 0, checksum_addr, checksum_size, override_start_addr, encrypted);
|
||||
w.put(PSOCommandHeaderDCV3{.command = 0xB2, .flag = 0x00, .size = data.size() + 4});
|
||||
w.write(data);
|
||||
string out_path = std::format("{}.{}.{}.bin",
|
||||
code->source_path, str_for_specific_version(code->specific_version), (encrypted ? "enc" : "std"));
|
||||
string out_path = std::format("{}/{}.{}.{}.bin",
|
||||
output_dir, code->short_name, str_for_specific_version(code->specific_version), (encrypted ? "enc" : "std"));
|
||||
phosg::save_file(out_path, w.str());
|
||||
phosg::fwrite_fmt(stderr, "... {}\n", out_path);
|
||||
}
|
||||
};
|
||||
|
||||
for (const auto& it : fci->name_and_specific_version_to_patch_function) {
|
||||
process_code(it.second, 0, 0, 0);
|
||||
for (const auto& [_, fn] : fci->all_functions) {
|
||||
process_code(fn, 0, 0, 0);
|
||||
}
|
||||
try {
|
||||
process_code(fci->name_to_function.at("VersionDetectDC"), 0, 0, 0);
|
||||
} catch (const out_of_range&) {
|
||||
}
|
||||
try {
|
||||
process_code(fci->name_to_function.at("VersionDetectGC"), 0, 0, 0);
|
||||
} catch (const out_of_range&) {
|
||||
}
|
||||
try {
|
||||
process_code(fci->name_to_function.at("VersionDetectXB"), 0, 0, 0);
|
||||
} catch (const out_of_range&) {
|
||||
}
|
||||
try {
|
||||
process_code(fci->name_to_function.at("CacheClearFix-Phase1"), 0x80000000, 8, 0x7F2734EC);
|
||||
} catch (const out_of_range&) {
|
||||
}
|
||||
try {
|
||||
process_code(fci->name_to_function.at("CacheClearFix-Phase2"), 0, 0, 0);
|
||||
process_code(fci->get("CacheClearFix-Phase1", SPECIFIC_VERSION_PPC_INDETERMINATE), 0x80000000, 8, 0x7F2734EC);
|
||||
} catch (const out_of_range&) {
|
||||
}
|
||||
});
|
||||
@@ -2428,6 +2418,93 @@ Action a_encode_item_parameter_table(
|
||||
write_output_data(args, data.data(), data.size(), nullptr);
|
||||
});
|
||||
|
||||
Action a_decode_level_table(
|
||||
"decode-level-table", nullptr,
|
||||
+[](phosg::Arguments& args) {
|
||||
auto input_data = read_input_data(args);
|
||||
std::shared_ptr<LevelTable> table;
|
||||
bool decompressed = args.get<bool>("decompressed");
|
||||
switch (get_cli_version(args)) {
|
||||
case Version::PC_V2:
|
||||
table = std::make_shared<LevelTableV2>(decompressed ? input_data : prs_decompress(input_data));
|
||||
break;
|
||||
case Version::GC_V3:
|
||||
table = std::make_shared<LevelTableGC>(
|
||||
decompressed ? input_data : decrypt_and_decompress_pr2_data<true>(input_data));
|
||||
break;
|
||||
case Version::XB_V3:
|
||||
table = std::make_shared<LevelTableXB>(
|
||||
decompressed ? input_data : decrypt_and_decompress_pr2_data<false>(input_data));
|
||||
break;
|
||||
case Version::BB_V4:
|
||||
table = std::make_shared<LevelTableV4>(decompressed ? input_data : prs_decompress(input_data));
|
||||
break;
|
||||
default:
|
||||
throw std::runtime_error("This version does not have a level table");
|
||||
}
|
||||
auto json = table->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_level_table(
|
||||
"encode-level-table-v4", nullptr,
|
||||
+[](phosg::Arguments& args) {
|
||||
JSONLevelTable table(phosg::JSON::parse(read_input_data(args)));
|
||||
string data = table.serialize_binary_v4();
|
||||
if (!args.get<bool>("decompressed")) {
|
||||
data = prs_compress_optimal(data);
|
||||
}
|
||||
write_output_data(args, data.data(), data.size(), nullptr);
|
||||
});
|
||||
|
||||
Action a_decode_battle_params(
|
||||
"decode-battle-params", nullptr,
|
||||
+[](phosg::Arguments& args) {
|
||||
auto data_on_ep1 = std::make_shared<std::string>(phosg::load_file(args.get<std::string>(1)));
|
||||
auto data_on_ep2 = std::make_shared<std::string>(phosg::load_file(args.get<std::string>(2)));
|
||||
auto data_on_ep4 = std::make_shared<std::string>(phosg::load_file(args.get<std::string>(3)));
|
||||
auto data_off_ep1 = std::make_shared<std::string>(phosg::load_file(args.get<std::string>(4)));
|
||||
auto data_off_ep2 = std::make_shared<std::string>(phosg::load_file(args.get<std::string>(5)));
|
||||
auto data_off_ep4 = std::make_shared<std::string>(phosg::load_file(args.get<std::string>(6)));
|
||||
BinaryBattleParamsIndex index(data_on_ep1, data_on_ep2, data_on_ep4, data_off_ep1, data_off_ep2, data_off_ep4);
|
||||
auto json = index.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;
|
||||
}
|
||||
phosg::save_file(args.get<std::string>(7), json.serialize(serialize_options));
|
||||
});
|
||||
|
||||
Action a_encode_battle_params(
|
||||
"encode-battle-params", nullptr,
|
||||
+[](phosg::Arguments& args) {
|
||||
JSONBattleParamsIndex index(phosg::JSON::parse(read_input_data(args)));
|
||||
std::string pfx = args.get<string>(2);
|
||||
phosg::save_file(pfx + "_on.dat", &index.get_table(false, Episode::EP1), sizeof(BattleParamsIndex::Table));
|
||||
phosg::save_file(pfx + "_lab_on.dat", &index.get_table(false, Episode::EP2), sizeof(BattleParamsIndex::Table));
|
||||
phosg::save_file(pfx + "_ep4_on.dat", &index.get_table(false, Episode::EP4), sizeof(BattleParamsIndex::Table));
|
||||
phosg::save_file(pfx + ".dat", &index.get_table(true, Episode::EP1), sizeof(BattleParamsIndex::Table));
|
||||
phosg::save_file(pfx + "_lab.dat", &index.get_table(true, Episode::EP2), sizeof(BattleParamsIndex::Table));
|
||||
phosg::save_file(pfx + "_ep4.dat", &index.get_table(true, Episode::EP4), sizeof(BattleParamsIndex::Table));
|
||||
});
|
||||
|
||||
Action a_find_rel_section(
|
||||
"find-rel-sections", nullptr,
|
||||
+[](phosg::Arguments& args) {
|
||||
auto data = read_input_data(args);
|
||||
auto offsets = args.get<bool>("big-endian")
|
||||
? all_relocation_offsets_for_rel_file<true>(data.data(), data.size())
|
||||
: all_relocation_offsets_for_rel_file<false>(data.data(), data.size());
|
||||
for (uint32_t offset : offsets) {
|
||||
phosg::fwrite_fmt(stdout, "{:08X}\n", offset);
|
||||
}
|
||||
});
|
||||
|
||||
Action a_describe_item(
|
||||
"describe-item", "\
|
||||
describe-item DATA-OR-DESCRIPTION\n\
|
||||
@@ -2603,7 +2680,6 @@ Action a_print_level_stats(
|
||||
|
||||
vector<PlayerStats> level_1_v1_v2;
|
||||
vector<PlayerStats> level_100_v1_v2;
|
||||
vector<PlayerStats> level_100_limit_v1_v2;
|
||||
vector<PlayerStats> level_200_v1_v2;
|
||||
vector<PlayerStats> level_200_limit_v1_v2;
|
||||
vector<PlayerStats> level_1_v3;
|
||||
@@ -2615,7 +2691,6 @@ Action a_print_level_stats(
|
||||
for (size_t z = 0; z < 12; z++) {
|
||||
if (z < 9) {
|
||||
level_1_v1_v2.emplace_back().char_stats = s->level_table_v1_v2->base_stats_for_class(z);
|
||||
level_100_limit_v1_v2.emplace_back(s->level_table_v1_v2->level_100_stats_for_class(z));
|
||||
level_200_limit_v1_v2.emplace_back(s->level_table_v1_v2->max_stats_for_class(z));
|
||||
s->level_table_v1_v2->advance_to_level(level_100_v1_v2.emplace_back(level_1_v1_v2.back()), 99, z);
|
||||
s->level_table_v1_v2->advance_to_level(level_200_v1_v2.emplace_back(level_1_v1_v2.back()), 199, z);
|
||||
@@ -2669,7 +2744,6 @@ Action a_print_level_stats(
|
||||
|
||||
print_stats_set(level_1_v1_v2, "v1/v2 Lv.1 ");
|
||||
print_stats_set(level_100_v1_v2, "v1/v2 Lv.100");
|
||||
print_stats_set(level_100_limit_v1_v2, "v1 limit ");
|
||||
print_stats_set(level_200_v1_v2, "v2 Lv.200 ");
|
||||
print_stats_set(level_200_limit_v1_v2, "v2 limit ");
|
||||
print_stats_set(level_1_v3, "v3 Lv.1 ");
|
||||
@@ -3600,7 +3674,7 @@ Action a_check_client_functions(
|
||||
"check-client-functions", nullptr,
|
||||
+[](phosg::Arguments&) {
|
||||
set_all_log_levels(phosg::LogLevel::L_DEBUG);
|
||||
FunctionCodeIndex fci("system/client-functions", true);
|
||||
ClientFunctionIndex index("system/client-functions", true);
|
||||
phosg::fwrite_fmt(stdout, "All client functions compiled\n");
|
||||
});
|
||||
|
||||
@@ -3967,7 +4041,7 @@ Action a_run_server_replay_log(
|
||||
std::filesystem::create_directories("system/players");
|
||||
}
|
||||
|
||||
const string& replay_log_filename = args.get<string>("replay-log");
|
||||
const auto& replay_log_filenames = args.get_multi<string>("replay-log");
|
||||
|
||||
#ifndef PHOSG_WINDOWS
|
||||
signal(SIGPIPE, SIG_IGN);
|
||||
@@ -3976,7 +4050,7 @@ Action a_run_server_replay_log(
|
||||
use_terminal_colors = true;
|
||||
}
|
||||
|
||||
auto state = make_shared<ServerState>(get_config_filename(args), !replay_log_filename.empty());
|
||||
auto state = make_shared<ServerState>(get_config_filename(args), !replay_log_filenames.empty());
|
||||
if (args.get<bool>("debug")) {
|
||||
state->is_debug = true;
|
||||
}
|
||||
@@ -3995,19 +4069,39 @@ Action a_run_server_replay_log(
|
||||
}
|
||||
|
||||
shared_ptr<ServerShell> shell;
|
||||
shared_ptr<ReplaySession> replay_session;
|
||||
shared_ptr<SignalWatcher> signal_watcher;
|
||||
if (!replay_log_filename.empty()) {
|
||||
shared_ptr<ReplaySession> last_running_replay;
|
||||
if (!replay_log_filenames.empty()) {
|
||||
config_log.info_f("Starting game server");
|
||||
state->game_server = make_shared<GameServer>(state);
|
||||
|
||||
// TODO: Do this properly via a config option, you lazy bum
|
||||
state->dol_file_index = make_shared<DOLFileIndex>();
|
||||
|
||||
auto log_f = phosg::fopen_shared(replay_log_filename, "rt");
|
||||
|
||||
replay_session = make_shared<ReplaySession>(state, log_f.get(), false);
|
||||
asio::co_spawn(*state->io_context, replay_session->run(), asio::detached);
|
||||
auto run_replays = [&]() -> asio::awaitable<void> {
|
||||
try {
|
||||
for (const auto& log_filename : replay_log_filenames) {
|
||||
phosg::log_info_f("[Replay] {} ...", log_filename);
|
||||
auto log_f = phosg::fopen_shared(log_filename, "rt");
|
||||
last_running_replay = make_shared<ReplaySession>(state, log_f.get());
|
||||
co_await last_running_replay->run();
|
||||
if (last_running_replay->failed()) {
|
||||
phosg::log_error_f("[Replay] {} failed", log_filename);
|
||||
break;
|
||||
}
|
||||
phosg::log_info_f("[Replay] {} OK", log_filename);
|
||||
state->reset_between_replays();
|
||||
}
|
||||
phosg::log_info_f("[Replay] All replays complete");
|
||||
} catch (const std::exception& e) {
|
||||
phosg::log_info_f("[Replay] Replays failed: {}", e.what());
|
||||
}
|
||||
if (!last_running_replay->failed()) {
|
||||
last_running_replay.reset();
|
||||
}
|
||||
state->io_context->stop();
|
||||
};
|
||||
asio::co_spawn(*state->io_context, run_replays, asio::detached);
|
||||
|
||||
} else {
|
||||
config_log.info_f("Opening sockets");
|
||||
@@ -4100,7 +4194,7 @@ Action a_run_server_replay_log(
|
||||
should_run_shell = false;
|
||||
}
|
||||
if (should_run_shell) {
|
||||
should_run_shell = !replay_session.get();
|
||||
should_run_shell = replay_log_filenames.empty();
|
||||
}
|
||||
|
||||
config_log.info_f("Ready");
|
||||
@@ -4111,7 +4205,7 @@ Action a_run_server_replay_log(
|
||||
state->io_context->run();
|
||||
config_log.info_f("Normal shutdown");
|
||||
|
||||
if (replay_session && replay_session->failed()) {
|
||||
if (last_running_replay) {
|
||||
throw runtime_error("Replay failed");
|
||||
}
|
||||
});
|
||||
|
||||
@@ -334,7 +334,7 @@ using PlayerDispDataDCPCV3 = PlayerDispDataDCPCV3T<false>;
|
||||
using PlayerDispDataDCPCV3BE = PlayerDispDataDCPCV3T<true>;
|
||||
|
||||
struct PlayerDispDataBBPreview {
|
||||
/* 00 */ le_uint32_t experience = 0;
|
||||
/* 00 */ le_uint32_t exp = 0;
|
||||
/* 04 */ le_uint32_t level = 0;
|
||||
// The name field in this structure is used for the player's Guild Card number, apparently (possibly because it's a
|
||||
// char array and this is BB)
|
||||
|
||||
@@ -75,19 +75,16 @@ static asio::awaitable<HandlerResult> C_1D(shared_ptr<Client> c, Channel::Messag
|
||||
double ping_ms = static_cast<double>(ping_usecs) / 1000.0;
|
||||
send_text_message_fmt(c->channel, "To proxy: {:g}ms", ping_ms);
|
||||
co_return HandlerResult::SUPPRESS;
|
||||
}
|
||||
|
||||
} else {
|
||||
if (c->proxy_session->is_in_game) {
|
||||
c->log.info_f("Forwarding in-game command 1D through proxy");
|
||||
}
|
||||
co_return HandlerResult::FORWARD;
|
||||
}
|
||||
|
||||
co_return HandlerResult::SUPPRESS;
|
||||
}
|
||||
|
||||
static asio::awaitable<HandlerResult> S_1D(shared_ptr<Client> c, Channel::Message&) {
|
||||
c->proxy_session->server_channel->send(0x1D);
|
||||
co_return HandlerResult::SUPPRESS;
|
||||
static asio::awaitable<HandlerResult> S_1D(shared_ptr<Client>, Channel::Message&) {
|
||||
co_return HandlerResult::FORWARD;
|
||||
}
|
||||
|
||||
static asio::awaitable<HandlerResult> S_97(shared_ptr<Client> c, Channel::Message&) {
|
||||
|
||||
+617
-623
File diff suppressed because it is too large
Load Diff
+16
-16
@@ -342,7 +342,7 @@ static void send_main_menu(shared_ptr<Client> c) {
|
||||
|
||||
main_menu->items.emplace_back(MainMenuItemID::DOWNLOAD_QUESTS, "Download quests",
|
||||
"Download quests", MenuItem::Flag::INVISIBLE_ON_DC_PROTOS | MenuItem::Flag::INVISIBLE_ON_PC_NTE | MenuItem::Flag::INVISIBLE_ON_BB);
|
||||
if (!s->function_code_index->patch_menu_empty(c->specific_version)) {
|
||||
if (!s->client_functions->patch_menu_empty(c->specific_version)) {
|
||||
main_menu->items.emplace_back(MainMenuItemID::PATCH_SWITCHES, "Patches",
|
||||
"Change game\nbehaviors", MenuItem::Flag::REQUIRES_SEND_FUNCTION_CALL_RUNS_CODE);
|
||||
}
|
||||
@@ -450,14 +450,14 @@ static asio::awaitable<void> send_auto_patches_if_needed(shared_ptr<Client> c) {
|
||||
c->set_flag(Client::Flag::HAS_AUTO_PATCHES);
|
||||
co_await prepare_client_for_patches(c);
|
||||
|
||||
unordered_set<shared_ptr<const CompiledFunctionCode>> functions_to_send;
|
||||
unordered_set<shared_ptr<const ClientFunctionIndex::Function>> functions_to_send;
|
||||
if (c->version() == Version::BB_V4) {
|
||||
for (const auto& patch_name : s->bb_required_patches) {
|
||||
try {
|
||||
functions_to_send.emplace(s->function_code_index->get_patch(patch_name, c->specific_version));
|
||||
functions_to_send.emplace(s->client_functions->get(patch_name, c->specific_version));
|
||||
} catch (const out_of_range&) {
|
||||
string message = std::format(
|
||||
"Your client is not compatible with a\nrequired patch on this server.\n\nClient version: {:08X}\nPatch name: {}", c->specific_version, patch_name);
|
||||
"Your client is not compatible with a\nrequired patch on this server.\n\nClient version: {}\nPatch name: {}", str_for_specific_version(c->specific_version), patch_name);
|
||||
send_message_box(c, message);
|
||||
c->channel->disconnect();
|
||||
co_return;
|
||||
@@ -466,18 +466,18 @@ static asio::awaitable<void> send_auto_patches_if_needed(shared_ptr<Client> c) {
|
||||
}
|
||||
for (const auto& patch_name : s->auto_patches) {
|
||||
try {
|
||||
functions_to_send.emplace(s->function_code_index->get_patch(patch_name, c->specific_version));
|
||||
functions_to_send.emplace(s->client_functions->get(patch_name, c->specific_version));
|
||||
} catch (const out_of_range&) {
|
||||
c->log.warning_f("Server has auto patch {} enabled, but it is not available for specific_version {:08X}",
|
||||
patch_name, c->specific_version);
|
||||
c->log.warning_f("Server has auto patch {} enabled, but it is not available for specific_version {}",
|
||||
patch_name, str_for_specific_version(c->specific_version));
|
||||
}
|
||||
}
|
||||
for (const auto& patch_name : c->login->account->auto_patches_enabled) {
|
||||
try {
|
||||
functions_to_send.emplace(s->function_code_index->get_patch(patch_name, c->specific_version));
|
||||
functions_to_send.emplace(s->client_functions->get(patch_name, c->specific_version));
|
||||
} catch (const out_of_range&) {
|
||||
c->log.warning_f("Client has auto patch {} enabled, but it is not available for specific_version {:08X}",
|
||||
patch_name, c->specific_version);
|
||||
c->log.warning_f("Client has auto patch {} enabled, but it is not available for specific_version {}",
|
||||
patch_name, str_for_specific_version(c->specific_version));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1535,7 +1535,7 @@ static asio::awaitable<void> on_9D_9E(shared_ptr<Client> c, Channel::Message& ms
|
||||
// not; we'll call on_login_complete once we receive the B3 response
|
||||
if (c->version() == Version::PC_V2) {
|
||||
try {
|
||||
auto code = s->function_code_index->name_to_function.at("ReturnTokenX86");
|
||||
auto code = s->client_functions->get("ReturnToken", SPECIFIC_VERSION_X86_INDETERMINATE);
|
||||
unordered_map<string, uint32_t> label_writes{{"token", c->login->account->account_id}};
|
||||
auto resp = co_await send_function_call(c, code, label_writes, nullptr, 0, 0x00400000, 0x0000E000, 0, true);
|
||||
|
||||
@@ -1558,7 +1558,8 @@ static asio::awaitable<void> on_9D_9E(shared_ptr<Client> c, Channel::Message& ms
|
||||
c->specific_version = SPECIFIC_VERSION_PC_V2_INDETERMINATE;
|
||||
c->log.info_f("Version cannot be determined from PE header checksum {:08X}", resp.checksum);
|
||||
}
|
||||
} catch (const out_of_range&) {
|
||||
} catch (const out_of_range& e) {
|
||||
c->log.info_f("Cannot determine PSO PC specific version: {}", e.what());
|
||||
}
|
||||
}
|
||||
co_await on_login_complete(c);
|
||||
@@ -2960,7 +2961,7 @@ static asio::awaitable<void> on_10_main_menu(shared_ptr<Client> c, uint32_t item
|
||||
// We have to prepare the client for patches here, even though we don't send them from this mennu, because we
|
||||
// need to know the client's specific_version before sending the menu.
|
||||
co_await prepare_client_for_patches(c);
|
||||
send_menu(c, c->require_server_state()->function_code_index->patch_switches_menu(c->specific_version, s->auto_patches, c->login->account->auto_patches_enabled));
|
||||
send_menu(c, c->require_server_state()->client_functions->patch_switches_menu(c->specific_version, s->auto_patches, c->login->account->auto_patches_enabled));
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -3337,13 +3338,12 @@ static void on_10_patch_switches(shared_ptr<Client> c, uint32_t item_id) {
|
||||
}
|
||||
|
||||
auto s = c->require_server_state();
|
||||
uint64_t key = (static_cast<uint64_t>(item_id) << 32) | c->specific_version;
|
||||
auto fn = s->function_code_index->menu_item_id_and_specific_version_to_patch_function.at(key);
|
||||
auto fn = s->client_functions->get_by_menu_item_id(item_id);
|
||||
if (!c->login->account->auto_patches_enabled.emplace(fn->short_name).second) {
|
||||
c->login->account->auto_patches_enabled.erase(fn->short_name);
|
||||
}
|
||||
c->login->account->save();
|
||||
send_menu(c, s->function_code_index->patch_switches_menu(c->specific_version, s->auto_patches, c->login->account->auto_patches_enabled));
|
||||
send_menu(c, s->client_functions->patch_switches_menu(c->specific_version, s->auto_patches, c->login->account->auto_patches_enabled));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4234,7 +4234,7 @@ static void add_player_exp(shared_ptr<Client> c, uint32_t exp, uint16_t from_ene
|
||||
auto s = c->require_server_state();
|
||||
auto p = c->character_file();
|
||||
|
||||
p->disp.stats.experience += exp;
|
||||
p->disp.stats.exp += exp;
|
||||
if (c->version() == Version::BB_V4) {
|
||||
send_give_experience(c, exp, from_enemy_id);
|
||||
}
|
||||
@@ -4242,7 +4242,7 @@ static void add_player_exp(shared_ptr<Client> c, uint32_t exp, uint16_t from_ene
|
||||
bool leveled_up = false;
|
||||
do {
|
||||
const auto& level = s->level_table(c->version())->stats_delta_for_level(p->disp.visual.char_class, p->disp.stats.level + 1);
|
||||
if (p->disp.stats.experience >= level.experience) {
|
||||
if (p->disp.stats.exp >= level.exp) {
|
||||
leveled_up = true;
|
||||
level.apply(p->disp.stats.char_stats);
|
||||
p->disp.stats.level++;
|
||||
@@ -4304,7 +4304,7 @@ static uint32_t base_exp_for_enemy_type(
|
||||
const auto& bp_table = bp_index->get_table(is_solo, episode);
|
||||
const auto& bp_stats_indexes = type_definition_for_enemy(enemy_type).bp_stats_indexes;
|
||||
if (!bp_stats_indexes.empty()) {
|
||||
return bp_table.stats_for_index(difficulty, bp_stats_indexes.back()).experience;
|
||||
return bp_table.stats_for_index(difficulty, bp_stats_indexes.back()).exp;
|
||||
}
|
||||
} catch (const out_of_range&) {
|
||||
}
|
||||
@@ -4987,8 +4987,8 @@ static asio::awaitable<void> on_battle_level_up_bb(shared_ptr<Client> c, Subcomm
|
||||
auto s = c->require_server_state();
|
||||
auto lp = lc->character_file();
|
||||
uint32_t target_level = min<uint32_t>(lp->disp.stats.level + cmd.num_levels, 199);
|
||||
uint32_t before_exp = lp->disp.stats.experience;
|
||||
int32_t exp_delta = lp->disp.stats.experience - before_exp;
|
||||
uint32_t before_exp = lp->disp.stats.exp;
|
||||
int32_t exp_delta = lp->disp.stats.exp - before_exp;
|
||||
if (exp_delta > 0) {
|
||||
s->level_table(lc->version())->advance_to_level(lp->disp.stats, target_level, lp->disp.visual.char_class);
|
||||
if (lc->version() == Version::BB_V4) {
|
||||
|
||||
@@ -321,9 +321,8 @@ void ReplaySession::apply_default_mask(shared_ptr<Event> ev) {
|
||||
}
|
||||
}
|
||||
|
||||
ReplaySession::ReplaySession(shared_ptr<ServerState> state, FILE* input_log, bool is_interactive)
|
||||
ReplaySession::ReplaySession(shared_ptr<ServerState> state, FILE* input_log)
|
||||
: state(state),
|
||||
is_interactive(is_interactive),
|
||||
prev_psov2_crypt_enabled(this->state->use_psov2_rand_crypt),
|
||||
commands_sent(0),
|
||||
bytes_sent(0),
|
||||
@@ -669,9 +668,6 @@ asio::awaitable<void> ReplaySession::run() {
|
||||
replay_log.info_f("Replay complete: {} commands sent ({} bytes), {} commands received ({} bytes)",
|
||||
this->commands_sent, this->bytes_sent, this->commands_received, this->bytes_received);
|
||||
}
|
||||
if (!this->is_interactive) {
|
||||
this->state->io_context->stop();
|
||||
}
|
||||
}
|
||||
|
||||
void ReplaySession::reschedule_idle_timeout() {
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
|
||||
class ReplaySession {
|
||||
public:
|
||||
ReplaySession(std::shared_ptr<ServerState> state, FILE* input_log, bool is_interactive);
|
||||
ReplaySession(std::shared_ptr<ServerState> state, FILE* input_log);
|
||||
ReplaySession(const ReplaySession&) = delete;
|
||||
ReplaySession(ReplaySession&&) = delete;
|
||||
ReplaySession& operator=(const ReplaySession&) = delete;
|
||||
@@ -62,7 +62,6 @@ private:
|
||||
};
|
||||
|
||||
std::shared_ptr<ServerState> state;
|
||||
bool is_interactive;
|
||||
bool prev_psov2_crypt_enabled;
|
||||
|
||||
std::unordered_map<uint64_t, std::shared_ptr<Client>> clients;
|
||||
|
||||
@@ -370,7 +370,7 @@ PSOBBBaseSystemFile::PSOBBBaseSystemFile() {
|
||||
PlayerDispDataBBPreview PSOBBCharacterFile::to_preview() const {
|
||||
PlayerDispDataBBPreview pre;
|
||||
pre.level = this->disp.stats.level;
|
||||
pre.experience = this->disp.stats.experience;
|
||||
pre.exp = this->disp.stats.exp;
|
||||
pre.visual = this->disp.visual;
|
||||
pre.name = this->disp.name;
|
||||
pre.play_time_seconds = this->play_time_seconds;
|
||||
@@ -1429,11 +1429,11 @@ void PSOBBCharacterFile::import_tethealla_material_usage(std::shared_ptr<const L
|
||||
|
||||
void PSOBBCharacterFile::recompute_stats(std::shared_ptr<const LevelTable> level_table, bool reset_exp) {
|
||||
uint32_t level = this->disp.stats.level;
|
||||
uint32_t exp = this->disp.stats.experience;
|
||||
uint32_t exp = this->disp.stats.exp;
|
||||
level_table->reset_to_base(this->disp.stats, this->disp.visual.char_class);
|
||||
level_table->advance_to_level(this->disp.stats, level, this->disp.visual.char_class);
|
||||
if (!reset_exp) {
|
||||
this->disp.stats.experience = exp;
|
||||
this->disp.stats.exp = exp;
|
||||
}
|
||||
|
||||
this->disp.stats.char_stats.atp += (this->get_material_usage(MaterialType::POWER) * 2);
|
||||
|
||||
+18
-18
@@ -336,7 +336,7 @@ asio::awaitable<void> prepare_client_for_patches(shared_ptr<Client> c) {
|
||||
auto s = c->require_server_state();
|
||||
|
||||
if (!c->check_flag(Client::Flag::SEND_FUNCTION_CALL_NO_CACHE_PATCH)) {
|
||||
auto fn = s->function_code_index->name_to_function.at("CacheClearFix-Phase1");
|
||||
auto fn = s->client_functions->get("CacheClearFix-Phase1", ClientFunctionIndex::Function::Architecture::POWERPC);
|
||||
unordered_map<string, uint32_t> label_writes;
|
||||
auto call1_res = co_await send_function_call(c, fn, label_writes, nullptr, 0, 0x80000000, 8, 0x7F2734EC);
|
||||
try {
|
||||
@@ -345,29 +345,29 @@ asio::awaitable<void> prepare_client_for_patches(shared_ptr<Client> c) {
|
||||
} catch (const out_of_range&) {
|
||||
c->log.info_f("Could not detect specific version from header checksum {:08X}", call1_res.checksum);
|
||||
}
|
||||
co_await send_function_call(c, s->function_code_index->name_to_function.at("CacheClearFix-Phase2"));
|
||||
co_await send_function_call(c, s->client_functions->get("CacheClearFix-Phase2", ClientFunctionIndex::Function::Architecture::POWERPC));
|
||||
c->log.info_f("Client cache behavior patched");
|
||||
c->set_flag(Client::Flag::SEND_FUNCTION_CALL_NO_CACHE_PATCH);
|
||||
}
|
||||
|
||||
const char* version_detect_name = nullptr;
|
||||
ClientFunctionIndex::Function::Architecture arch = ClientFunctionIndex::Function::Architecture::UNKNOWN;
|
||||
if (c->version() == Version::DC_V2) {
|
||||
version_detect_name = "VersionDetectDC";
|
||||
arch = ClientFunctionIndex::Function::Architecture::SH4;
|
||||
} else if (is_gc(c->version())) {
|
||||
version_detect_name = "VersionDetectGC";
|
||||
arch = ClientFunctionIndex::Function::Architecture::POWERPC;
|
||||
} else if (c->version() == Version::XB_V3) {
|
||||
version_detect_name = "VersionDetectXB";
|
||||
arch = ClientFunctionIndex::Function::Architecture::X86;
|
||||
}
|
||||
if (version_detect_name && specific_version_is_indeterminate(c->specific_version)) {
|
||||
auto vers_detect_res = co_await send_function_call(
|
||||
c, s->function_code_index->name_to_function.at(version_detect_name));
|
||||
if ((arch != ClientFunctionIndex::Function::Architecture::UNKNOWN) &&
|
||||
specific_version_is_indeterminate(c->specific_version)) {
|
||||
auto vers_detect_res = co_await send_function_call(c, s->client_functions->get("VersionDetect", arch));
|
||||
c->specific_version = vers_detect_res.return_value;
|
||||
c->log.info_f("Version detected as {:08X}", c->specific_version);
|
||||
}
|
||||
}
|
||||
|
||||
string prepare_send_function_call_data(
|
||||
shared_ptr<const CompiledFunctionCode> code,
|
||||
shared_ptr<const ClientFunctionIndex::Function> code,
|
||||
const unordered_map<string, uint32_t>& label_writes,
|
||||
const void* suffix_data,
|
||||
size_t suffix_size,
|
||||
@@ -417,7 +417,7 @@ string prepare_send_function_call_data(
|
||||
|
||||
asio::awaitable<C_ExecuteCodeResult_B3> send_function_call(
|
||||
shared_ptr<Client> c,
|
||||
shared_ptr<const CompiledFunctionCode> code,
|
||||
shared_ptr<const ClientFunctionIndex::Function> code,
|
||||
const unordered_map<string, uint32_t>& label_writes,
|
||||
const void* suffix_data,
|
||||
size_t suffix_size,
|
||||
@@ -446,7 +446,7 @@ asio::awaitable<C_ExecuteCodeResult_B3> send_function_call(
|
||||
}
|
||||
|
||||
asio::awaitable<void> send_function_call_multi(
|
||||
shared_ptr<Client> c, unordered_set<shared_ptr<const CompiledFunctionCode>> codes) {
|
||||
shared_ptr<Client> c, unordered_set<shared_ptr<const ClientFunctionIndex::Function>> codes) {
|
||||
if (codes.empty()) {
|
||||
co_return;
|
||||
}
|
||||
@@ -469,7 +469,7 @@ asio::awaitable<void> send_function_call_multi(
|
||||
void send_function_call(
|
||||
shared_ptr<Channel> ch,
|
||||
uint64_t client_enabled_flags,
|
||||
shared_ptr<const CompiledFunctionCode> code,
|
||||
shared_ptr<const ClientFunctionIndex::Function> code,
|
||||
const unordered_map<string, uint32_t>& label_writes,
|
||||
const void* suffix_data,
|
||||
size_t suffix_size,
|
||||
@@ -526,7 +526,7 @@ asio::awaitable<bool> send_protected_command(std::shared_ptr<Client> c, const vo
|
||||
co_await prepare_client_for_patches(c);
|
||||
|
||||
try {
|
||||
auto fn = s->function_code_index->get_patch("CallProtectedHandler", c->specific_version);
|
||||
auto fn = s->client_functions->get("CallProtectedHandler", c->specific_version);
|
||||
unordered_map<string, uint32_t> label_writes{{"size", size}};
|
||||
co_await send_function_call(c, fn, label_writes, data, size);
|
||||
auto l = echo_to_lobby ? c->lobby.lock() : nullptr;
|
||||
@@ -550,7 +550,7 @@ asio::awaitable<void> send_dol_file(shared_ptr<Client> c, shared_ptr<DOLFileInde
|
||||
// Determine the necessary start address for the data
|
||||
unordered_map<string, uint32_t> label_writes{{"address", 0x80000034}}; // ArenaHigh from GC globals
|
||||
auto addr_ret = co_await send_function_call(
|
||||
c, s->function_code_index->name_to_function.at("ReadMemoryWordGC"), label_writes);
|
||||
c, s->client_functions->get("ReadMemoryWord", c->specific_version), label_writes);
|
||||
uint32_t dol_base_addr = (addr_ret.return_value - dol->data.size()) & (~3);
|
||||
|
||||
// Write the file in multiple chunks
|
||||
@@ -562,7 +562,7 @@ asio::awaitable<void> send_dol_file(shared_ptr<Client> c, shared_ptr<DOLFileInde
|
||||
string data_to_send = dol->data.substr(offset, bytes_to_send);
|
||||
|
||||
auto s = c->require_server_state();
|
||||
auto fn = s->function_code_index->name_to_function.at("WriteMemoryGC");
|
||||
auto fn = s->client_functions->get("WriteMemory", c->specific_version);
|
||||
label_writes = {{"dest_addr", (dol_base_addr + offset)}, {"size", bytes_to_send}};
|
||||
co_await send_function_call(c, fn, label_writes, data_to_send.data(), data_to_send.size());
|
||||
|
||||
@@ -573,7 +573,7 @@ asio::awaitable<void> send_dol_file(shared_ptr<Client> c, shared_ptr<DOLFileInde
|
||||
}
|
||||
|
||||
// Send the final function, which moves the DOL's sections into place and calls the entrypoint
|
||||
auto fn = s->function_code_index->name_to_function.at("RunDOL");
|
||||
auto fn = s->client_functions->get("RunDOL", c->specific_version);
|
||||
label_writes = {{"dol_base_ptr", dol_base_addr}};
|
||||
co_await send_function_call(c, fn, label_writes);
|
||||
// The client will stop running PSO after this, so disconnect them
|
||||
@@ -2547,7 +2547,7 @@ asio::awaitable<GetPlayerInfoResult> send_get_player_info(shared_ptr<Client> c,
|
||||
}
|
||||
try {
|
||||
auto s = c->require_server_state();
|
||||
auto fn = s->function_code_index->get_patch("GetExtendedPlayerInfo", c->specific_version);
|
||||
auto fn = s->client_functions->get("GetExtendedPlayerInfo", c->specific_version);
|
||||
send_function_call(c->channel, c->enabled_flags, fn);
|
||||
c->function_call_response_queue.emplace_back(make_shared<AsyncPromise<C_ExecuteCodeResult_B3>>());
|
||||
full_req_sent = true;
|
||||
|
||||
+5
-5
@@ -9,8 +9,8 @@
|
||||
#include <unordered_set>
|
||||
|
||||
#include "Client.hh"
|
||||
#include "ClientFunctionIndex.hh"
|
||||
#include "CommandFormats.hh"
|
||||
#include "FunctionCompiler.hh"
|
||||
#include "Lobby.hh"
|
||||
#include "Menu.hh"
|
||||
#include "Quest.hh"
|
||||
@@ -150,7 +150,7 @@ void send_patch_change_to_directory(
|
||||
|
||||
asio::awaitable<void> prepare_client_for_patches(std::shared_ptr<Client> c);
|
||||
std::string prepare_send_function_call_data(
|
||||
std::shared_ptr<const CompiledFunctionCode> code,
|
||||
std::shared_ptr<const ClientFunctionIndex::Function> code,
|
||||
const std::unordered_map<std::string, uint32_t>& label_writes,
|
||||
const void* suffix_data,
|
||||
size_t suffix_size,
|
||||
@@ -163,7 +163,7 @@ std::string prepare_send_function_call_data(
|
||||
void send_function_call(
|
||||
std::shared_ptr<Channel> ch,
|
||||
uint64_t client_enabled_flags,
|
||||
std::shared_ptr<const CompiledFunctionCode> code,
|
||||
std::shared_ptr<const ClientFunctionIndex::Function> code,
|
||||
const std::unordered_map<std::string, uint32_t>& label_writes = {},
|
||||
const void* suffix_data = nullptr,
|
||||
size_t suffix_size = 0,
|
||||
@@ -173,7 +173,7 @@ void send_function_call(
|
||||
bool ignore_actually_runs_code_flag = false);
|
||||
asio::awaitable<C_ExecuteCodeResult_B3> send_function_call(
|
||||
std::shared_ptr<Client> c,
|
||||
std::shared_ptr<const CompiledFunctionCode> code,
|
||||
std::shared_ptr<const ClientFunctionIndex::Function> code,
|
||||
const std::unordered_map<std::string, uint32_t>& label_writes = {},
|
||||
const void* suffix_data = nullptr,
|
||||
size_t suffix_size = 0,
|
||||
@@ -182,7 +182,7 @@ asio::awaitable<C_ExecuteCodeResult_B3> send_function_call(
|
||||
uint32_t override_relocations_offset = 0,
|
||||
bool ignore_actually_runs_code_flag = false);
|
||||
asio::awaitable<void> send_function_call_multi(
|
||||
std::shared_ptr<Client> c, std::unordered_set<std::shared_ptr<const CompiledFunctionCode>> codes);
|
||||
std::shared_ptr<Client> c, std::unordered_set<std::shared_ptr<const ClientFunctionIndex::Function>> codes);
|
||||
asio::awaitable<bool> send_protected_command(std::shared_ptr<Client> c, const void* data, size_t size, bool echo_to_lobby);
|
||||
asio::awaitable<void> send_dol_file(std::shared_ptr<Client> c, std::shared_ptr<DOLFileIndex::File> dol);
|
||||
|
||||
|
||||
+84
-40
@@ -1892,21 +1892,30 @@ void ServerState::load_set_data_tables() {
|
||||
}
|
||||
|
||||
void ServerState::load_battle_params() {
|
||||
config_log.info_f("Loading battle parameters");
|
||||
this->battle_params = make_shared<BattleParamsIndex>(
|
||||
config_log.info_f("Loading JSON battle parameters");
|
||||
try {
|
||||
this->battle_params = make_shared<JSONBattleParamsIndex>(phosg::JSON::parse(phosg::load_file(
|
||||
"system/tables/battle-params.json")));
|
||||
} catch (const std::exception& e) {
|
||||
config_log.info_f("Cannot load JSON battle parameters ({}); loading binary battle parameters", e.what());
|
||||
this->battle_params = make_shared<BinaryBattleParamsIndex>(
|
||||
this->load_bb_file("BattleParamEntry_on.dat"),
|
||||
this->load_bb_file("BattleParamEntry_lab_on.dat"),
|
||||
this->load_bb_file("BattleParamEntry_ep4_on.dat"),
|
||||
this->load_bb_file("BattleParamEntry.dat"),
|
||||
this->load_bb_file("BattleParamEntry_lab.dat"),
|
||||
this->load_bb_file("BattleParamEntry_ep4.dat"));
|
||||
}
|
||||
}
|
||||
|
||||
void ServerState::load_level_tables() {
|
||||
config_log.info_f("Loading level tables");
|
||||
this->level_table_v1_v2 = make_shared<LevelTableV2>(phosg::load_file("system/level-tables/PlayerTable-pc-v2.prs"), true);
|
||||
this->level_table_v3 = make_shared<LevelTableV3BE>(phosg::load_file("system/level-tables/PlyLevelTbl-gc-v3.cpt"), true);
|
||||
this->level_table_v4 = make_shared<LevelTableV4>(*this->load_bb_file("PlyLevelTbl.prs"), true);
|
||||
this->level_table_v1_v2 = make_shared<JSONLevelTable>(phosg::JSON::parse(phosg::load_file(
|
||||
"system/tables/level-table-v1-v2.json")));
|
||||
this->level_table_v3 = make_shared<JSONLevelTable>(phosg::JSON::parse(phosg::load_file(
|
||||
"system/tables/level-table-v3.json")));
|
||||
this->level_table_v4 = make_shared<JSONLevelTable>(phosg::JSON::parse(phosg::load_file(
|
||||
"system/tables/level-table-v4.json")));
|
||||
}
|
||||
|
||||
void ServerState::load_text_index() {
|
||||
@@ -2032,11 +2041,11 @@ void ServerState::load_drop_tables() {
|
||||
|
||||
unordered_map<string, shared_ptr<const RareItemSet>> new_rare_item_sets;
|
||||
unordered_map<string, shared_ptr<const CommonItemSet>> new_common_item_sets;
|
||||
for (const auto& item : std::filesystem::directory_iterator("system/item-tables")) {
|
||||
for (const auto& item : std::filesystem::directory_iterator("system/tables")) {
|
||||
string filename = item.path().filename().string();
|
||||
|
||||
if (filename.starts_with("common-table-") || filename.starts_with("ItemPT-")) {
|
||||
string path = "system/item-tables/" + filename;
|
||||
string path = "system/tables/" + filename;
|
||||
size_t ext_offset = filename.rfind('.');
|
||||
string basename = (ext_offset == string::npos) ? filename : filename.substr(0, ext_offset);
|
||||
|
||||
@@ -2055,7 +2064,7 @@ void ServerState::load_drop_tables() {
|
||||
auto data = make_shared<string>(phosg::load_file(path));
|
||||
shared_ptr<string> ct_data;
|
||||
try {
|
||||
string ct_path = "system/item-tables/" + ct_filename;
|
||||
string ct_path = "system/tables/" + ct_filename;
|
||||
ct_data = make_shared<string>(phosg::load_file(ct_path));
|
||||
config_log.info_f("Loading AFS common item table {} with challenge table {}", filename, ct_filename);
|
||||
} catch (const phosg::cannot_open_file&) {
|
||||
@@ -2075,7 +2084,7 @@ void ServerState::load_drop_tables() {
|
||||
}
|
||||
|
||||
} else if (filename.starts_with("rare-table-") || filename.starts_with("ItemRT-")) {
|
||||
string path = "system/item-tables/" + filename;
|
||||
string path = "system/tables/" + filename;
|
||||
size_t ext_offset = filename.rfind('.');
|
||||
string basename = (ext_offset == string::npos) ? filename : filename.substr(0, ext_offset);
|
||||
|
||||
@@ -2124,20 +2133,20 @@ void ServerState::load_drop_tables() {
|
||||
}
|
||||
|
||||
config_log.info_f("Loading armor table");
|
||||
auto armor_data = make_shared<string>(phosg::load_file("system/item-tables/ArmorRandom-gc-v3.rel"));
|
||||
auto armor_data = make_shared<string>(phosg::load_file("system/tables/ArmorRandom-gc-v3.rel"));
|
||||
auto new_armor_random_set = make_shared<ArmorRandomSet>(armor_data);
|
||||
|
||||
config_log.info_f("Loading tool table");
|
||||
auto tool_data = make_shared<string>(phosg::load_file("system/item-tables/ToolRandom-gc-v3.rel"));
|
||||
auto tool_data = make_shared<string>(phosg::load_file("system/tables/ToolRandom-gc-v3.rel"));
|
||||
auto new_tool_random_set = make_shared<ToolRandomSet>(tool_data);
|
||||
|
||||
config_log.info_f("Loading weapon tables");
|
||||
array<shared_ptr<const WeaponRandomSet>, 4> new_weapon_random_sets;
|
||||
const char* filenames[4] = {
|
||||
"system/item-tables/WeaponRandomNormal-gc-v3.rel",
|
||||
"system/item-tables/WeaponRandomHard-gc-v3.rel",
|
||||
"system/item-tables/WeaponRandomVeryHard-gc-v3.rel",
|
||||
"system/item-tables/WeaponRandomUltimate-gc-v3.rel",
|
||||
"system/tables/WeaponRandomNormal-gc-v3.rel",
|
||||
"system/tables/WeaponRandomHard-gc-v3.rel",
|
||||
"system/tables/WeaponRandomVeryHard-gc-v3.rel",
|
||||
"system/tables/WeaponRandomUltimate-gc-v3.rel",
|
||||
};
|
||||
for (size_t z = 0; z < 4; z++) {
|
||||
auto weapon_data = make_shared<string>(phosg::load_file(filenames[z]));
|
||||
@@ -2145,7 +2154,7 @@ void ServerState::load_drop_tables() {
|
||||
}
|
||||
|
||||
config_log.info_f("Loading tekker adjustment table");
|
||||
auto tekker_data = make_shared<string>(phosg::load_file("system/item-tables/JudgeItem-gc-v3.rel"));
|
||||
auto tekker_data = make_shared<string>(phosg::load_file("system/tables/JudgeItem-gc-v3.rel"));
|
||||
auto new_tekker_adjustment_set = make_shared<TekkerAdjustmentSet>(tekker_data);
|
||||
|
||||
this->rare_item_sets = std::move(new_rare_item_sets);
|
||||
@@ -2161,25 +2170,32 @@ 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/item-parameter-table-{}.json", file_path_token_for_version(v));
|
||||
config_log.debug_f("Loading item definition table {}", path);
|
||||
new_item_parameter_tables[v_s] = ItemParameterTable::from_json(phosg::JSON::parse(phosg::load_file(path)));
|
||||
string json_path = std::format("system/tables/item-parameter-table-{}.json", file_path_token_for_version(v));
|
||||
try {
|
||||
config_log.debug_f("Loading item definition table {}", json_path);
|
||||
new_item_parameter_tables[v_s] = ItemParameterTable::from_json(phosg::JSON::parse(phosg::load_file(json_path)));
|
||||
} catch (const std::exception& e) {
|
||||
string path = std::format("system/tables/ItemPMT-{}.prs", file_path_token_for_version(v));
|
||||
config_log.debug_f("Cannot load {} ({}); loading item definition table {}", json_path, e.what(), path);
|
||||
auto data = std::make_shared<std::string>(prs_decompress(phosg::load_file(path)));
|
||||
new_item_parameter_tables[v_s] = ItemParameterTable::from_binary(data, v);
|
||||
}
|
||||
}
|
||||
|
||||
auto json = phosg::JSON::parse(phosg::load_file("system/item-tables/translation-table.json"));
|
||||
auto json = phosg::JSON::parse(phosg::load_file("system/tables/translation-table.json"));
|
||||
auto new_item_translation_table = make_shared<ItemTranslationTable>(json, new_item_parameter_tables);
|
||||
|
||||
config_log.info_f("Loading v1 mag evolution table");
|
||||
auto mag_data_v1 = make_shared<string>(prs_decompress(phosg::load_file("system/item-tables/ItemMagEdit-dc-v1.prs")));
|
||||
auto mag_data_v1 = make_shared<string>(prs_decompress(phosg::load_file("system/tables/ItemMagEdit-dc-v1.prs")));
|
||||
auto new_table_v1 = MagEvolutionTable::create(mag_data_v1, Version::DC_V1);
|
||||
config_log.info_f("Loading v2 mag evolution table");
|
||||
auto mag_data_v2 = make_shared<string>(prs_decompress(phosg::load_file("system/item-tables/ItemMagEdit-dc-v2.prs")));
|
||||
auto mag_data_v2 = make_shared<string>(prs_decompress(phosg::load_file("system/tables/ItemMagEdit-dc-v2.prs")));
|
||||
auto new_table_v2 = MagEvolutionTable::create(mag_data_v2, Version::DC_V2);
|
||||
config_log.info_f("Loading v3 mag evolution table");
|
||||
auto mag_data_v3 = make_shared<string>(prs_decompress(phosg::load_file("system/item-tables/ItemMagEdit-xb-v3.prs")));
|
||||
auto mag_data_v3 = make_shared<string>(prs_decompress(phosg::load_file("system/tables/ItemMagEdit-xb-v3.prs")));
|
||||
auto new_table_v3 = MagEvolutionTable::create(mag_data_v3, Version::XB_V3);
|
||||
config_log.info_f("Loading v4 mag evolution table");
|
||||
auto mag_data_v4 = make_shared<string>(prs_decompress(phosg::load_file("system/item-tables/ItemMagEdit-bb-v4.prs")));
|
||||
auto mag_data_v4 = make_shared<string>(prs_decompress(phosg::load_file("system/tables/ItemMagEdit-bb-v4.prs")));
|
||||
auto new_table_v4 = MagEvolutionTable::create(mag_data_v4, Version::BB_V4);
|
||||
|
||||
this->item_parameter_tables = std::move(new_item_parameter_tables);
|
||||
@@ -2231,7 +2247,7 @@ void ServerState::load_quest_index(bool raise_on_any_failure) {
|
||||
|
||||
void ServerState::compile_functions(bool raise_on_any_failure) {
|
||||
config_log.info_f("Compiling client functions");
|
||||
this->function_code_index = make_shared<FunctionCodeIndex>("system/client-functions", raise_on_any_failure);
|
||||
this->client_functions = make_shared<ClientFunctionIndex>("system/client-functions", raise_on_any_failure);
|
||||
}
|
||||
|
||||
void ServerState::load_dol_files() {
|
||||
@@ -2243,31 +2259,38 @@ 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 add_file = [&](const std::string& filename, const void* data = nullptr, size_t size = 0) -> void {
|
||||
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;
|
||||
if (!data) {
|
||||
std::string file_data = phosg::load_file("system/blueburst/" + filename);
|
||||
e.size = file_data.size();
|
||||
e.checksum = phosg::crc32(file_data.data(), file_data.size());
|
||||
sf->data += file_data;
|
||||
} else {
|
||||
e.size = size;
|
||||
e.checksum = phosg::crc32(data, size);
|
||||
sf->data.append(reinterpret_cast<const char*>(data), size);
|
||||
}
|
||||
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");
|
||||
auto level_table_data = prs_compress_optimal(this->level_table_v4->serialize_binary_v4());
|
||||
auto pmt_data = prs_compress_optimal(this->item_parameter_table(Version::BB_V4)->serialize_binary(Version::BB_V4));
|
||||
|
||||
const auto& bps = *this->battle_params;
|
||||
add_file("BattleParamEntry.dat", &bps.get_table(true, Episode::EP1), sizeof(BattleParamsIndex::Table));
|
||||
add_file("BattleParamEntry_lab.dat", &bps.get_table(true, Episode::EP2), sizeof(BattleParamsIndex::Table));
|
||||
add_file("BattleParamEntry_ep4.dat", &bps.get_table(true, Episode::EP4), sizeof(BattleParamsIndex::Table));
|
||||
add_file("BattleParamEntry_on.dat", &bps.get_table(false, Episode::EP1), sizeof(BattleParamsIndex::Table));
|
||||
add_file("BattleParamEntry_lab_on.dat", &bps.get_table(false, Episode::EP2), sizeof(BattleParamsIndex::Table));
|
||||
add_file("BattleParamEntry_ep4_on.dat", &bps.get_table(false, Episode::EP4), sizeof(BattleParamsIndex::Table));
|
||||
add_file("PlyLevelTbl.prs", level_table_data.data(), level_table_data.size());
|
||||
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)));
|
||||
add_file("ItemPMT.prs", pmt_data.data(), pmt_data.size());
|
||||
|
||||
this->bb_stream_file = sf;
|
||||
}
|
||||
@@ -2354,6 +2377,27 @@ void ServerState::load_all(bool enable_thread_pool) {
|
||||
this->generate_bb_stream_file();
|
||||
}
|
||||
|
||||
void ServerState::reset_between_replays() {
|
||||
if (this->allow_saving_accounts) {
|
||||
throw std::logic_error("Account saving is enabled during replay");
|
||||
}
|
||||
this->account_index = make_shared<AccountIndex>(true);
|
||||
|
||||
this->next_lobby_id = 0;
|
||||
std::vector<std::shared_ptr<Lobby>> lobbies_to_delete;
|
||||
for (const auto& l : this->all_lobbies()) {
|
||||
if (l->is_game()) {
|
||||
lobbies_to_delete.emplace_back(l);
|
||||
} else {
|
||||
this->next_lobby_id = std::max<uint32_t>(this->next_lobby_id, l->lobby_id + 1);
|
||||
}
|
||||
}
|
||||
for (const auto& l : lobbies_to_delete) {
|
||||
phosg::log_warning_f("Previous replay left a game open ({:08X}); destroying it", l->lobby_id);
|
||||
this->remove_lobby(l);
|
||||
}
|
||||
}
|
||||
|
||||
void ServerState::disconnect_all_banned_clients() {
|
||||
uint64_t now_usecs = phosg::now();
|
||||
|
||||
|
||||
+6
-3
@@ -11,11 +11,12 @@
|
||||
|
||||
#include "Account.hh"
|
||||
#include "Client.hh"
|
||||
#include "ClientFunctionIndex.hh"
|
||||
#include "CommonItemSet.hh"
|
||||
#include "DNSServer.hh"
|
||||
#include "DOLFileIndex.hh"
|
||||
#include "Episode3/DataIndexes.hh"
|
||||
#include "Episode3/Tournament.hh"
|
||||
#include "FunctionCompiler.hh"
|
||||
#include "GSLArchive.hh"
|
||||
#include "IPV4RangeSet.hh"
|
||||
#include "ItemNameIndex.hh"
|
||||
@@ -191,7 +192,7 @@ struct ServerState : public std::enable_shared_from_this<ServerState> {
|
||||
std::vector<std::shared_ptr<const PSOBBEncryption::KeyFile>> bb_private_keys;
|
||||
std::shared_ptr<const parray<uint8_t, 0x16C>> bb_default_keyboard_config;
|
||||
std::shared_ptr<const parray<uint8_t, 0x38>> bb_default_joystick_config;
|
||||
std::shared_ptr<const FunctionCodeIndex> function_code_index;
|
||||
std::shared_ptr<const ClientFunctionIndex> client_functions;
|
||||
std::shared_ptr<const PatchFileIndex> pc_patch_file_index;
|
||||
std::shared_ptr<const PatchFileIndex> bb_patch_file_index;
|
||||
std::unordered_map<uint64_t, std::shared_ptr<const MapFile>> map_file_for_source_hash;
|
||||
@@ -212,7 +213,7 @@ struct ServerState : public std::enable_shared_from_this<ServerState> {
|
||||
std::shared_ptr<const G_SetEXResultValues_Ep3_6xB4x4B> ep3_tournament_final_round_ex_values;
|
||||
std::shared_ptr<const QuestCategoryIndex> quest_category_index;
|
||||
std::shared_ptr<const QuestIndex> quest_index;
|
||||
std::shared_ptr<const LevelTableV2> level_table_v1_v2;
|
||||
std::shared_ptr<const LevelTable> level_table_v1_v2;
|
||||
std::shared_ptr<const LevelTable> level_table_v3;
|
||||
std::shared_ptr<const LevelTable> level_table_v4;
|
||||
std::shared_ptr<const BattleParamsIndex> battle_params;
|
||||
@@ -465,5 +466,7 @@ struct ServerState : public std::enable_shared_from_this<ServerState> {
|
||||
|
||||
void load_all(bool enable_thread_pool);
|
||||
|
||||
void reset_between_replays();
|
||||
|
||||
void disconnect_all_banned_clients();
|
||||
};
|
||||
|
||||
@@ -1095,15 +1095,3 @@ ShellCommand c_create_item(
|
||||
send_text_message(c->channel, "$C7Item created:\n" + name);
|
||||
co_return deque<string>{};
|
||||
});
|
||||
|
||||
ShellCommand c_replay_log(
|
||||
"replay-log", nullptr,
|
||||
+[](ShellCommand::Args& args) -> asio::awaitable<deque<string>> {
|
||||
if (args.s->allow_saving_accounts) {
|
||||
throw runtime_error("Replays cannot be run when account saving is enabled");
|
||||
}
|
||||
auto log_f = phosg::fopen_shared(args.args, "rt");
|
||||
auto replay_session = make_shared<ReplaySession>(args.s, log_f.get(), true);
|
||||
co_await replay_session->run();
|
||||
co_return deque<string>{};
|
||||
});
|
||||
|
||||
+12
-12
@@ -293,18 +293,18 @@ uint8_t npc_for_name(const string& name, Version version) {
|
||||
|
||||
const char* name_for_char_class(uint8_t cls) {
|
||||
static const array<const char*, 12> names = {
|
||||
/* 00 */ "HUmar", // 0
|
||||
/* 01 */ "HUnewearl", // 0
|
||||
/* 02 */ "HUcast", // 1
|
||||
/* 03 */ "RAmar", // 0
|
||||
/* 04 */ "RAcast", // 2
|
||||
/* 05 */ "RAcaseal", // 1
|
||||
/* 06 */ "FOmarl", // 0
|
||||
/* 07 */ "FOnewm", // 0
|
||||
/* 08 */ "FOnewearl", // 0
|
||||
/* 09 */ "HUcaseal", // 1
|
||||
/* 0A */ "FOmar", // 0
|
||||
/* 0B */ "RAmarl"}; // 0
|
||||
/* 00 */ "HUmar",
|
||||
/* 01 */ "HUnewearl",
|
||||
/* 02 */ "HUcast",
|
||||
/* 03 */ "RAmar",
|
||||
/* 04 */ "RAcast",
|
||||
/* 05 */ "RAcaseal",
|
||||
/* 06 */ "FOmarl",
|
||||
/* 07 */ "FOnewm",
|
||||
/* 08 */ "FOnewearl",
|
||||
/* 09 */ "HUcaseal",
|
||||
/* 0A */ "FOmar",
|
||||
/* 0B */ "RAmarl"};
|
||||
try {
|
||||
return names.at(cls);
|
||||
} catch (const out_of_range&) {
|
||||
|
||||
+22
-5
@@ -140,7 +140,7 @@ uint32_t default_sub_version_for_version(Version version) {
|
||||
uint32_t default_specific_version_for_version(Version version, int64_t sub_version) {
|
||||
// For versions that don't support send_function_call by default, we need to set the specific_version based on
|
||||
// sub_version. Fortunately, all versions that share sub_version values also support send_function_call, so for those
|
||||
// versions we get the specific_version later by sending VersionDetectDC, VersionDetectGC, or VersionDetectXB.
|
||||
// versions we get the specific_version later by sending VersionDetect.
|
||||
switch (version) {
|
||||
case Version::DC_NTE:
|
||||
return SPECIFIC_VERSION_DC_NTE; // 1OJ1 (NTE)
|
||||
@@ -159,7 +159,7 @@ uint32_t default_specific_version_for_version(Version version, int64_t sub_versi
|
||||
return SPECIFIC_VERSION_DC_V1_INDETERMINATE;
|
||||
}
|
||||
case Version::DC_V2:
|
||||
return SPECIFIC_VERSION_DC_V2_INDETERMINATE; // 2___; need to send VersionDetectDC
|
||||
return SPECIFIC_VERSION_DC_V2_INDETERMINATE; // 2___; need to send VersionDetect
|
||||
case Version::PC_NTE:
|
||||
return SPECIFIC_VERSION_PC_V2_NTE; // 2OJT
|
||||
case Version::PC_V2:
|
||||
@@ -184,7 +184,7 @@ uint32_t default_specific_version_for_version(Version version, int64_t sub_versi
|
||||
case 0x30: // GC Ep1&2 GameJam demo, GC Ep1&2 Trial Edition, GC Ep1&2 JP v1.2, at least one version of PSO XB
|
||||
case 0x31: // GC Ep1&2 US v1.0, GC US v1.1, XB US
|
||||
default:
|
||||
return SPECIFIC_VERSION_GC_V3_INDETERMINATE; // 3O__; need to send VersionDetectGC
|
||||
return SPECIFIC_VERSION_GC_V3_INDETERMINATE; // 3O__; need to send VersionDetect
|
||||
}
|
||||
throw logic_error("this should be impossible");
|
||||
case Version::GC_EP3_NTE:
|
||||
@@ -199,10 +199,10 @@ uint32_t default_specific_version_for_version(Version version, int64_t sub_versi
|
||||
case -1: // Initial check (before sub_version recognition)
|
||||
case 0x40: // GC Ep3 trial and GC Ep3 JP
|
||||
default:
|
||||
return SPECIFIC_VERSION_GC_EP3_INDETERMINATE; // 3SJ_; need to send VersionDetectGC
|
||||
return SPECIFIC_VERSION_GC_EP3_INDETERMINATE; // 3SJ_; need to send VersionDetect
|
||||
}
|
||||
case Version::XB_V3:
|
||||
return SPECIFIC_VERSION_XB_V3_INDETERMINATE; // 4O__; need to send VersionDetectXB
|
||||
return SPECIFIC_VERSION_XB_V3_INDETERMINATE; // 4O__; need to send VersionDetect
|
||||
case Version::BB_V4:
|
||||
return SPECIFIC_VERSION_BB_V4_INDETERMINATE; // 5___; we should be able to determine version from initial login
|
||||
default:
|
||||
@@ -244,6 +244,23 @@ bool specific_version_is_bb(uint32_t specific_version) {
|
||||
return ((specific_version & 0xFF000000) == 0x35000000);
|
||||
}
|
||||
|
||||
uint32_t specific_version_for_str(const std::string& s) {
|
||||
switch (s.size()) {
|
||||
case 0:
|
||||
return 0;
|
||||
case 1:
|
||||
return (s[0] << 24);
|
||||
case 2:
|
||||
return (s[0] << 24) | (s[1] << 16);
|
||||
case 3:
|
||||
return (s[0] << 24) | (s[1] << 16) | (s[2] << 8);
|
||||
case 4:
|
||||
return (s[0] << 24) | (s[1] << 16) | (s[2] << 8) | s[3];
|
||||
default:
|
||||
throw std::runtime_error("Invalid specific_version string");
|
||||
}
|
||||
}
|
||||
|
||||
string str_for_specific_version(uint32_t specific_version) {
|
||||
string ret;
|
||||
for (size_t z = 0; z < 4; z++) {
|
||||
|
||||
@@ -184,6 +184,10 @@ constexpr bool uses_utf16(Version version) {
|
||||
(version == Version::BB_V4);
|
||||
}
|
||||
|
||||
constexpr uint32_t SPECIFIC_VERSION_SH4_INDETERMINATE = 0x53483400; // SH4_
|
||||
constexpr uint32_t SPECIFIC_VERSION_PPC_INDETERMINATE = 0x50504300; // PPC_
|
||||
constexpr uint32_t SPECIFIC_VERSION_X86_INDETERMINATE = 0x58383600; // X86_
|
||||
|
||||
constexpr uint32_t SPECIFIC_VERSION_DC_NTE = 0x314F4A31; // 1OJ1
|
||||
constexpr uint32_t SPECIFIC_VERSION_DC_11_2000_PROTOTYPE = 0x314F4A32; // 1OJ2
|
||||
constexpr uint32_t SPECIFIC_VERSION_DC_V1_JP = 0x314F4A46; // 1OJF
|
||||
@@ -220,6 +224,7 @@ bool specific_version_is_gc(uint32_t specific_version);
|
||||
bool specific_version_is_xb(uint32_t specific_version);
|
||||
bool specific_version_is_bb(uint32_t specific_version);
|
||||
|
||||
uint32_t specific_version_for_str(const std::string& specific_version);
|
||||
std::string str_for_specific_version(uint32_t specific_version);
|
||||
|
||||
enum class ServerBehavior {
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 2.9 KiB |
@@ -1 +1 @@
|
||||
../item-tables/ItemMagEdit-bb-v4.prs
|
||||
../tables/ItemMagEdit-bb-v4.prs
|
||||
Binary file not shown.
+65
-7
@@ -1,16 +1,76 @@
|
||||
.meta visibility="all"
|
||||
.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
|
||||
start:
|
||||
.include WriteCodeBlocksBB
|
||||
.include WriteCodeBlocks
|
||||
|
||||
|
||||
|
||||
.versions 3OJ2 3OJ3 3OJ4 3OJ5 3OE0 3OE1 3OE2 3OP0
|
||||
|
||||
.label TItemWeapon_SealedJSword_count_kill_loc, <VERS 0x8012D2D4 0x8012D518 0x8012D550 0x8012D4B0 0x8012D578 0x8012D578 0x8012D4C0 0x8012D698>
|
||||
.data TItemWeapon_SealedJSword_count_kill_loc
|
||||
.deltaof TItemWeapon_SealedJSword_count_kill, TItemWeapon_SealedJSword_count_kill_end
|
||||
.address TItemWeapon_SealedJSword_count_kill_loc
|
||||
TItemWeapon_SealedJSword_count_kill: # [std](TItemWeapon_SealedJSword* this @ r3) -> void
|
||||
lwz r4, [r3 + 0xF0] # r4 = this->owner_player
|
||||
lha r5, [r4 + 0x11A] # r5 = this->owner_player->num_kills_since_map_load
|
||||
lha r6, [r3 + 0x1F8] # r6 = this->last_owner_player_kill_count
|
||||
lhz r7, [r3 + 0xE8] # r7 = this->kill_count
|
||||
cmp r6, r5
|
||||
bge TItemWeapon_SealedJSword_count_kill_skip_update
|
||||
lwz r8, [r3 + 0xDC]
|
||||
andi. r8, r8, 0x100
|
||||
beq TItemWeapon_SealedJSword_count_kill_skip_incr # if (!(flags & 0x100)) don't incr kill count
|
||||
sub r8, r5, r6
|
||||
add r7, r7, r8
|
||||
sth [r3 + 0xE8], r7
|
||||
TItemWeapon_SealedJSword_count_kill_skip_incr:
|
||||
sth [r3 + 0x1F8], r5
|
||||
TItemWeapon_SealedJSword_count_kill_skip_update:
|
||||
cmplwi r7, 23000
|
||||
blt TItemWeapon_SealedJSword_count_kill_skip_set_flag
|
||||
lwz r8, [r3 + 0xDC]
|
||||
ori r8, r8, 0x200
|
||||
stw [r3 + 0xDC], r8
|
||||
TItemWeapon_SealedJSword_count_kill_skip_set_flag:
|
||||
blr
|
||||
TItemWeapon_SealedJSword_count_kill_end:
|
||||
|
||||
|
||||
|
||||
.versions 4OJB 4OJD 4OJU 4OED 4OEU 4OPD 4OPU
|
||||
|
||||
.data <VERS 0x00197610 0x001977A0 0x00197920 0x00197880 0x00197810 0x001978A0 0x00197840>
|
||||
.deltaof TItemWeapon_SealedJSword_count_kill, TItemWeapon_SealedJSword_count_kill_end
|
||||
.address <VERS 0x00197610 0x001977A0 0x00197920 0x00197880 0x00197810 0x001978A0 0x00197840>
|
||||
TItemWeapon_SealedJSword_count_kill:
|
||||
mov eax, [ecx + 0xF0]
|
||||
movsx eax, word [eax + 0x11A]
|
||||
movsx edx, word [ecx + 0x1F8]
|
||||
sub edx, eax
|
||||
jge TItemWeapon_SealedJSword_count_kill_skip_update
|
||||
test dword [ecx + 0xDC], 0x100
|
||||
jz TItemWeapon_SealedJSword_count_kill_skip_incr
|
||||
sub [ecx + 0xE8], dx
|
||||
TItemWeapon_SealedJSword_count_kill_skip_incr:
|
||||
mov [ecx + 0x1F8], ax
|
||||
TItemWeapon_SealedJSword_count_kill_skip_update:
|
||||
cmp word [ecx + 0xE8], 23000
|
||||
jb TItemWeapon_SealedJSword_count_kill_skip_set_flag
|
||||
or dword [ecx + 0xDC], 0x200
|
||||
TItemWeapon_SealedJSword_count_kill_skip_set_flag:
|
||||
ret
|
||||
TItemWeapon_SealedJSword_count_kill_end:
|
||||
|
||||
|
||||
|
||||
.versions 59NJ 59NL
|
||||
|
||||
.data <VERS 0x005E32A4 0x005E32C8>
|
||||
.deltaof TItemUnitUnsealable_count_kill, TItemUnitUnsealable_count_kill_end
|
||||
.address <VERS 0x005E32A4 0x005E32C8>
|
||||
@@ -34,8 +94,6 @@ TItemUnitUnsealable_count_kill_skip_update:
|
||||
jmp <VERS 0x005E2C10 0x005E2C34>
|
||||
TItemUnitUnsealable_count_kill_end:
|
||||
|
||||
|
||||
|
||||
.data <VERS 0x005F3E94 0x005F3EFC>
|
||||
.deltaof TItemWeapon_LameDArgent_count_kill, TItemWeapon_LameDArgent_count_kill_end
|
||||
.address <VERS 0x005F3E94 0x005F3EFC>
|
||||
@@ -59,8 +117,6 @@ TItemWeapon_LameDArgent_count_kill_skip_update:
|
||||
ret
|
||||
TItemWeapon_LameDArgent_count_kill_end:
|
||||
|
||||
|
||||
|
||||
.data <VERS 0x005FC95C 0x005FCA74>
|
||||
.deltaof TItemWeapon_SealedJSword_count_kill, TItemWeapon_SealedJSword_count_kill_end
|
||||
.address <VERS 0x005FC95C 0x005FCA74>
|
||||
@@ -86,5 +142,7 @@ TItemWeapon_SealedJSword_count_kill_end:
|
||||
|
||||
|
||||
|
||||
.all_versions
|
||||
|
||||
.data 0x00000000
|
||||
.data 0x00000000
|
||||
@@ -1,42 +0,0 @@
|
||||
.meta name="Kill count fix"
|
||||
.meta description="Fixes client-side\nkill counts when\nmultiple enemies are\nkilled on the same\nframe"
|
||||
|
||||
.versions 3OJ2 3OJ3 3OJ4 3OJ5 3OE0 3OE1 3OE2 3OP0
|
||||
|
||||
entry_ptr:
|
||||
reloc0:
|
||||
.offsetof start
|
||||
start:
|
||||
.include WriteCodeBlocksGC
|
||||
|
||||
.label TItemWeapon_SealedJSword_count_kill_loc, <VERS 0x8012D2D4 0x8012D518 0x8012D550 0x8012D4B0 0x8012D578 0x8012D578 0x8012D4C0 0x8012D698>
|
||||
.data TItemWeapon_SealedJSword_count_kill_loc
|
||||
.deltaof TItemWeapon_SealedJSword_count_kill, TItemWeapon_SealedJSword_count_kill_end
|
||||
.address TItemWeapon_SealedJSword_count_kill_loc
|
||||
TItemWeapon_SealedJSword_count_kill: # [std](TItemWeapon_SealedJSword* this @ r3) -> void
|
||||
lwz r4, [r3 + 0xF0] # r4 = this->owner_player
|
||||
lha r5, [r4 + 0x11A] # r5 = this->owner_player->num_kills_since_map_load
|
||||
lha r6, [r3 + 0x1F8] # r6 = this->last_owner_player_kill_count
|
||||
lhz r7, [r3 + 0xE8] # r7 = this->kill_count
|
||||
cmp r6, r5
|
||||
bge TItemWeapon_SealedJSword_count_kill_skip_update
|
||||
lwz r8, [r3 + 0xDC]
|
||||
andi. r8, r8, 0x100
|
||||
beq TItemWeapon_SealedJSword_count_kill_skip_incr # if (!(flags & 0x100)) don't incr kill count
|
||||
sub r8, r5, r6
|
||||
add r7, r7, r8
|
||||
sth [r3 + 0xE8], r7
|
||||
TItemWeapon_SealedJSword_count_kill_skip_incr:
|
||||
sth [r3 + 0x1F8], r5
|
||||
TItemWeapon_SealedJSword_count_kill_skip_update:
|
||||
cmplwi r7, 23000
|
||||
blt TItemWeapon_SealedJSword_count_kill_skip_set_flag
|
||||
lwz r8, [r3 + 0xDC]
|
||||
ori r8, r8, 0x200
|
||||
stw [r3 + 0xDC], r8
|
||||
TItemWeapon_SealedJSword_count_kill_skip_set_flag:
|
||||
blr
|
||||
TItemWeapon_SealedJSword_count_kill_end:
|
||||
|
||||
.data 0x00000000
|
||||
.data 0x00000000
|
||||
@@ -1,35 +0,0 @@
|
||||
.meta name="Kill count fix"
|
||||
.meta description="Fixes client-side\nkill counts when\nmultiple enemies are\nkilled on the same\nframe"
|
||||
|
||||
.versions 4OJB 4OJD 4OJU 4OED 4OEU 4OPD 4OPU
|
||||
|
||||
entry_ptr:
|
||||
reloc0:
|
||||
.offsetof start
|
||||
start:
|
||||
.include WriteCodeBlocksXB
|
||||
|
||||
.data <VERS 0x00197610 0x001977A0 0x00197920 0x00197880 0x00197810 0x001978A0 0x00197840>
|
||||
.deltaof TItemWeapon_SealedJSword_count_kill, TItemWeapon_SealedJSword_count_kill_end
|
||||
.address <VERS 0x00197610 0x001977A0 0x00197920 0x00197880 0x00197810 0x001978A0 0x00197840>
|
||||
TItemWeapon_SealedJSword_count_kill:
|
||||
mov eax, [ecx + 0xF0]
|
||||
movsx eax, word [eax + 0x11A]
|
||||
movsx edx, word [ecx + 0x1F8]
|
||||
sub edx, eax
|
||||
jge TItemWeapon_SealedJSword_count_kill_skip_update
|
||||
test dword [ecx + 0xDC], 0x100
|
||||
jz TItemWeapon_SealedJSword_count_kill_skip_incr
|
||||
sub [ecx + 0xE8], dx
|
||||
TItemWeapon_SealedJSword_count_kill_skip_incr:
|
||||
mov [ecx + 0x1F8], ax
|
||||
TItemWeapon_SealedJSword_count_kill_skip_update:
|
||||
cmp word [ecx + 0xE8], 23000
|
||||
jb TItemWeapon_SealedJSword_count_kill_skip_set_flag
|
||||
or dword [ecx + 0xDC], 0x200
|
||||
TItemWeapon_SealedJSword_count_kill_skip_set_flag:
|
||||
ret
|
||||
TItemWeapon_SealedJSword_count_kill_end:
|
||||
|
||||
.data 0x00000000
|
||||
.data 0x00000000
|
||||
+8
-12
@@ -1,18 +1,14 @@
|
||||
# This patch changes the amount of items and Meseta that can be stored in the
|
||||
# bank. If the bank item limit is increased beyond 200, this patch requires
|
||||
# server support for extended bank data stored outside of the player's data.
|
||||
# newserv has support for this, but you must set the BBBankItemLimit and
|
||||
# BBBankMesetaLimit values in config.json to match the values used here.
|
||||
# This patch changes the amount of items and Meseta that can be stored in the bank. If the bank item limit is increased
|
||||
# beyond 200, this patch requires server support for extended bank data stored outside of the player's data. newserv
|
||||
# has support for this, but you must set the BBBankItemLimit and BBBankMesetaLimit values in config.json to match the
|
||||
# values used here.
|
||||
|
||||
# As written, this changes the meseta limit to 2000000000 and the item limit to
|
||||
# 1000. The meseta limit can be any value up to 2147483647, and the item limit
|
||||
# can be any value up to 1321. To use different values than the defaults, first
|
||||
# compute the data size as ((slot count * 0x18) + 8), then replace each value
|
||||
# below appropriately.
|
||||
# As written, this changes the meseta limit to 2000000000 and the item limit to 1000. The meseta limit can be any value
|
||||
# up to 2147483647, and the item limit can be any value up to 1321. To use different values than the defaults, first
|
||||
# compute the data size as ((slot count * 0x18) + 8), then replace each value below appropriately.
|
||||
|
||||
.meta name="More bank slots"
|
||||
.meta description=""
|
||||
.meta hide_from_patches_menu
|
||||
|
||||
.versions 59NJ 59NL
|
||||
|
||||
@@ -21,7 +17,7 @@ reloc0:
|
||||
.offsetof start
|
||||
|
||||
start:
|
||||
.include WriteCodeBlocksBB
|
||||
.include WriteCodeBlocks
|
||||
|
||||
.data <VERS 0x006C8C53 0x006C8C0F>
|
||||
.data 4
|
||||
+5
-8
@@ -1,14 +1,11 @@
|
||||
# This patch disables the logic that causes all unlockable areas to be open by
|
||||
# default for all players, instead restoring the logic that checks quest flags
|
||||
# to open areas (as previous PSO versions used).
|
||||
# This patch disables the logic that causes all unlockable areas to be open by default for all players, instead
|
||||
# restoring the logic that checks quest flags to open areas (as previous PSO versions used).
|
||||
|
||||
# This patch is intended to be used in the BBRequiredPatches field in
|
||||
# config.json if you want the classic behavior, hence the presence of the
|
||||
# hide_from_patches_menu directive here.
|
||||
# This patch is intended to be used in the BBRequiredPatches field in config.json if you want the classic behavior,
|
||||
# hence the visibility setting here.
|
||||
|
||||
.meta name="Classic main warp behavior"
|
||||
.meta description=""
|
||||
.meta hide_from_patches_menu
|
||||
|
||||
.versions 59NJ 59NL
|
||||
|
||||
@@ -16,7 +13,7 @@ entry_ptr:
|
||||
reloc0:
|
||||
.offsetof start
|
||||
start:
|
||||
.include WriteCodeBlocksBB
|
||||
.include WriteCodeBlocks
|
||||
.data <VERS 0x0064A642 0x0064A5DE> # Episode 1
|
||||
.data 1
|
||||
.binary 01
|
||||
+4
-7
@@ -1,14 +1,11 @@
|
||||
# It would be a bad idea to remove `.meta hide_from_patches_menu` to make this
|
||||
# patch an option for players to be able to select; either all players on the
|
||||
# server should have this patch, or none should have it.
|
||||
# It would be a bad idea to change this function's visibility; either all players on the server should have this patch,
|
||||
# or none should have it.
|
||||
|
||||
# This patch clears the list of unreleased items on the client, so the client
|
||||
# never creates buggy items when the server generates an item that wasn't
|
||||
# released on the official servers.
|
||||
# This patch clears the list of unreleased items on the client, so the client never creates buggy items when the server
|
||||
# generates an item that wasn't released on the official servers.
|
||||
|
||||
.meta name="Clear unreleased item list"
|
||||
.meta description=""
|
||||
.meta hide_from_patches_menu
|
||||
|
||||
.versions 59NJ 59NL
|
||||
|
||||
+2
-1
@@ -1,3 +1,4 @@
|
||||
.meta visibility="all"
|
||||
.meta name="Item exch. fix"
|
||||
.meta description="Fixes some quest item\nexchange opcodes"
|
||||
|
||||
@@ -7,7 +8,7 @@ entry_ptr:
|
||||
reloc0:
|
||||
.offsetof start
|
||||
start:
|
||||
.include WriteCodeBlocksBB
|
||||
.include WriteCodeBlocks
|
||||
|
||||
|
||||
|
||||
@@ -1,624 +0,0 @@
|
||||
# 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
|
||||
+142
-152
@@ -1,25 +1,24 @@
|
||||
# This patch changes the number of BB character save slots from 4 to any number
|
||||
# up to 127.
|
||||
# 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.
|
||||
# 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:
|
||||
# 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.
|
||||
# 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
|
||||
|
||||
.versions 59NJ 59NL
|
||||
|
||||
entry_ptr:
|
||||
reloc0:
|
||||
@@ -27,7 +26,7 @@ reloc0:
|
||||
|
||||
# Include a few functions first
|
||||
write_call_to_code:
|
||||
.include WriteCallToCode-59NL
|
||||
.include WriteCallToCode
|
||||
memcpy:
|
||||
.include CopyData
|
||||
ret
|
||||
@@ -53,7 +52,7 @@ start:
|
||||
apply_enable_scroll_patch:
|
||||
# This patch enables scrolling behavior within the character list
|
||||
push -5 # Jump size (negative = jmp instead of call)
|
||||
push 0x00413B7F # Jump address
|
||||
push <VERS 0x00413B77 0x00413B7F> # Jump address
|
||||
call get_code_size_for_enable_scroll
|
||||
.deltaof enable_scroll_start, enable_scroll_end
|
||||
get_code_size_for_enable_scroll:
|
||||
@@ -63,7 +62,7 @@ get_code_size_for_enable_scroll:
|
||||
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, [0x00A3B050] # scroll_bar = TAdScrollBarXb_objs[0]
|
||||
mov eax, [<VERS 0x00A38BD0 0x00A3B050>] # 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)
|
||||
@@ -92,10 +91,9 @@ enable_scroll_end:
|
||||
|
||||
|
||||
apply_fix_scroll_patch1:
|
||||
# This patch fixes character selection cursor object so it will take the
|
||||
# scroll offset into account
|
||||
# This patch fixes character selection cursor object so it will take the scroll offset into account
|
||||
push 6 # Call size
|
||||
push 0x00413C38 # Call address
|
||||
push <VERS 0x00413C30 0x00413C38> # 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:
|
||||
@@ -105,7 +103,7 @@ get_code_size_for_fix_scroll_patch1:
|
||||
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, [0x00A3B050] # scroll_bar = TAdScrollBarXb_objs[0]
|
||||
mov eax, [<VERS 0x00A38BD0 0x00A3B050>] # scroll_bar = TAdScrollBarXb_objs[0]
|
||||
add ebp, [eax + 0xAC] # ebp += scroll_bar->selection_state[0].scroll_offset
|
||||
ret
|
||||
fix_scroll_patch1_end:
|
||||
@@ -115,11 +113,10 @@ fix_scroll_patch1_end:
|
||||
|
||||
|
||||
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
|
||||
# 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 0x00413CD8 # Call address
|
||||
push <VERS 0x00413CD0 0x00413CD8> # 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:
|
||||
@@ -127,7 +124,7 @@ get_code_size_for_fix_scroll_patch2:
|
||||
push dword [eax]
|
||||
call fix_scroll_patch2_end
|
||||
fix_scroll_patch2_start:
|
||||
mov eax, [0x00A3B050] # scroll_bar = TAdScrollBarXb_objs[0]
|
||||
mov eax, [<VERS 0x00A38BD0 0x00A3B050>] # 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
|
||||
@@ -139,10 +136,9 @@ fix_scroll_patch2_end:
|
||||
|
||||
|
||||
apply_fix_file_index:
|
||||
# This patch fixes the character file indexing so it will account for the
|
||||
# scroll position
|
||||
# This patch fixes the character file indexing so it will account for the scroll position
|
||||
push 5 # Call size
|
||||
push 0x00413CF0 # Call address
|
||||
push <VERS 0x00413CE8 0x00413CF0> # 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:
|
||||
@@ -150,11 +146,11 @@ get_code_size_for_selection_index_fix2:
|
||||
push dword [eax]
|
||||
call selection_index_fix2_end
|
||||
selection_index_fix2_start:
|
||||
mov eax, [0x00A3B050]
|
||||
mov eax, [<VERS 0x00A38BD0 0x00A3B050>]
|
||||
mov eax, [eax + 0xAC] # eax = TAdScrollBarXb_objs[0]->selection_state[0].scroll_offset
|
||||
add ebp, eax # arg0 += eax
|
||||
mov [esp + 4], ebp
|
||||
mov eax, 0x006C1A80
|
||||
mov eax, <VERS 0x006C1ABC 0x006C1A80>
|
||||
jmp eax # set_current_char_slot
|
||||
selection_index_fix2_end:
|
||||
call write_call_to_code
|
||||
@@ -163,10 +159,9 @@ selection_index_fix2_end:
|
||||
|
||||
|
||||
apply_preview_window_fix:
|
||||
# This patch fixes the preview display so it will show the correct section
|
||||
# ID, level, etc.
|
||||
# This patch fixes the preview display so it will show the correct section ID, level, etc.
|
||||
push 5 # Call size
|
||||
push 0x0040216C # Call address
|
||||
push 0x0040216C
|
||||
call get_code_size_for_preview_window_fix
|
||||
.deltaof preview_window_fix_start, preview_window_fix_end
|
||||
get_code_size_for_preview_window_fix:
|
||||
@@ -174,10 +169,10 @@ get_code_size_for_preview_window_fix:
|
||||
push dword [eax]
|
||||
call preview_window_fix_end
|
||||
preview_window_fix_start:
|
||||
mov eax, [0x00A3B050] # scroll_bar = TAdScrollBarXb_objs[0]
|
||||
mov eax, [<VERS 0x00A38BD0 0x00A3B050>] # scroll_bar = TAdScrollBarXb_objs[0]
|
||||
mov eax, [eax + 0xAC] # eax = scroll_bar->selection_state[0].scroll_offset
|
||||
add [esp + 4], eax
|
||||
mov eax, 0x006C44D0 # get_player_preview_info
|
||||
mov eax, <VERS 0x006C4514 0x006C44D0> # 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
|
||||
@@ -193,267 +188,266 @@ preview_window_fix_end:
|
||||
|
||||
|
||||
apply_static_patches:
|
||||
.include WriteCodeBlocksBB
|
||||
# These patches change various places where the character data size and slot
|
||||
# count are referenced
|
||||
.data 0x004751A4
|
||||
.include WriteCodeBlocks
|
||||
# These patches change various places where the character data size and slot count are referenced
|
||||
.data <VERS 0x00475294 0x004751A4>
|
||||
.data 0x00000001
|
||||
.binary 0C # slot count; TDataProtocol::handle_E5
|
||||
.data 0x0047525B
|
||||
.data <VERS 0x0047534B 0x0047525B>
|
||||
.data 0x00000001
|
||||
.binary 0C # slot count; import_player_preview
|
||||
.data 0x004785E1
|
||||
.data <VERS 0x004786D1 0x004785E1>
|
||||
.data 0x00000001
|
||||
.binary 0C # slot count; TDataProtocol::handle_E4
|
||||
.data 0x0048242D
|
||||
.data <VERS 0x00482559 0x0048242D>
|
||||
.data 0x00000004
|
||||
.data 0x00022FC4 # total file size
|
||||
.data 0x006C17BF
|
||||
.data <VERS 0x006C17FB 0x006C17BF>
|
||||
.data 0x00000001
|
||||
.binary 0C # slot count
|
||||
.data 0x006C1CCB
|
||||
.data <VERS 0x006C1D07 0x006C1CCB>
|
||||
.data 0x00000004
|
||||
.data 0x00022FC4 # total file size
|
||||
.data 0x006C1CFE
|
||||
.data <VERS 0x006C1D3A 0x006C1CFE>
|
||||
.data 0x00000001
|
||||
.binary 0C # slot count
|
||||
.data 0x006C1D1C
|
||||
.data <VERS 0x006C1D58 0x006C1D1C>
|
||||
.data 0x00000004
|
||||
.data 0x00022FC4 # total file size
|
||||
.data 0x006C1DD7
|
||||
.data <VERS 0x006C1E13 0x006C1DD7>
|
||||
.data 0x00000004
|
||||
.data 0x00022FC4 # total file size
|
||||
.data 0x006C222E
|
||||
.data <VERS 0x006C226A 0x006C222E>
|
||||
.data 0x00000004
|
||||
.data 0x00022FC4 # total file size
|
||||
.data 0x006C226D
|
||||
.data <VERS 0x006C22A9 0x006C226D>
|
||||
.data 0x00000001
|
||||
.binary 0C # slot count
|
||||
.data 0x006C228E
|
||||
.data <VERS 0x006C22CA 0x006C228E>
|
||||
.data 0x00000004
|
||||
.data 0x00022FC4 # total file size
|
||||
.data 0x006C229E
|
||||
.data <VERS 0x006C22DA 0x006C229E>
|
||||
.data 0x00000004
|
||||
.data 0x00022FC4 # total file size
|
||||
.data 0x006C24DB
|
||||
.data <VERS 0x006C2517 0x006C24DB>
|
||||
.data 0x00000004
|
||||
.data 0x00022FC4 # total file size
|
||||
.data 0x006C2643
|
||||
.data <VERS 0x006C267F 0x006C2643>
|
||||
.data 0x00000004
|
||||
.data 0x00022FBC # save_count offset
|
||||
.data 0x006C264D
|
||||
.data <VERS 0x006C2689 0x006C264D>
|
||||
.data 0x00000004
|
||||
.data 0x00022FBC # save_count offset
|
||||
.data 0x006C26EF
|
||||
.data <VERS 0x006C272B 0x006C26EF>
|
||||
.data 0x00000004
|
||||
.data 0x00022FBC # save_count offset
|
||||
.data 0x006C2705
|
||||
.data <VERS 0x006C2741 0x006C2705>
|
||||
.data 0x00000004
|
||||
.data 0x00022FC0 # round2_seed offset
|
||||
.data 0x006C2793
|
||||
.data <VERS 0x006C27CF 0x006C2793>
|
||||
.data 0x00000004
|
||||
.data 0x00022FC4 # total file size
|
||||
.data 0x006C286C
|
||||
.data <VERS 0x006C28A8 0x006C286C>
|
||||
.data 0x00000004
|
||||
.data 0x00022FC4 # total file size
|
||||
.data 0x006C3113
|
||||
.data <VERS 0x006C314F 0x006C3113>
|
||||
.data 0x00000004
|
||||
.data 0x00022FC4 # total file size
|
||||
.data 0x006C353F
|
||||
.data <VERS 0x006C357B 0x006C353F>
|
||||
.data 0x00000004
|
||||
.data 0x00022FC4 # total file size
|
||||
.data 0x006C357E
|
||||
.data <VERS 0x006C35BA 0x006C357E>
|
||||
.data 0x00000001
|
||||
.binary 0C # slot count
|
||||
.data 0x006C35AA
|
||||
.data <VERS 0x006C35E6 0x006C35AA>
|
||||
.data 0x00000004
|
||||
.data 0x00022FC4 # total file size
|
||||
.data 0x006C35B7
|
||||
.data <VERS 0x006C35F3 0x006C35B7>
|
||||
.data 0x00000004
|
||||
.data 0x00022FC4 # total file size
|
||||
.data 0x006C35D2
|
||||
.data <VERS 0x006C360E 0x006C35D2>
|
||||
.data 0x00000004
|
||||
.data 0x00022FBC # save_count offset
|
||||
.data 0x006C35DB
|
||||
.data <VERS 0x006C3617 0x006C35DB>
|
||||
.data 0x00000004
|
||||
.data 0x00022FBC # save_count offset
|
||||
.data 0x006C36E0
|
||||
.data <VERS 0x006C371C 0x006C36E0>
|
||||
.data 0x00000004
|
||||
.data 0x00022FC4 # total file size
|
||||
.data 0x006C3B1E
|
||||
.data <VERS 0x006C3B5A 0x006C3B1E>
|
||||
.data 0x00000004
|
||||
.data 0x00022FC4 # total file size
|
||||
.data 0x006C4209
|
||||
.data <VERS 0x006C424D 0x006C4209>
|
||||
.data 0x00000004
|
||||
.data 0x00022FC4 # total file size
|
||||
.data 0x006C47EF
|
||||
.data <VERS 0x006C4833 0x006C47EF>
|
||||
.data 0x00000004
|
||||
.data 0x00022FC4 # total file size
|
||||
.data 0x006C4826
|
||||
.data <VERS 0x006C486A 0x006C4826>
|
||||
.data 0x00000001
|
||||
.binary 0C # slot count
|
||||
.data 0x006C4962
|
||||
.data <VERS 0x006C49A6 0x006C4962>
|
||||
.data 0x00000004
|
||||
.data 0x00022FC4 # total file size
|
||||
.data 0x006C4999
|
||||
.data <VERS 0x006C49DD 0x006C4999>
|
||||
.data 0x00000001
|
||||
.binary 0C # slot count
|
||||
.data 0x006C4A81
|
||||
.data <VERS 0x006C4AC5 0x006C4A81>
|
||||
.data 0x00000004
|
||||
.data 0x00022FC4 # total file size
|
||||
.data 0x006C4ABA
|
||||
.data <VERS 0x006C4AFE 0x006C4ABA>
|
||||
.data 0x00000001
|
||||
.binary 0C # slot count
|
||||
.data 0x006C4C9A
|
||||
.data <VERS 0x006C4CDE 0x006C4C9A>
|
||||
.data 0x00000004
|
||||
.data 0x00022FC4 # total file size
|
||||
.data 0x006C4CD1
|
||||
.data <VERS 0x006C4D15 0x006C4CD1>
|
||||
.data 0x00000001
|
||||
.binary 0C # slot count
|
||||
.data 0x006C4DB9
|
||||
.data <VERS 0x006C4DFD 0x006C4DB9>
|
||||
.data 0x00000004
|
||||
.data 0x00022FC4 # total file size
|
||||
.data 0x006C4DF2
|
||||
.data <VERS 0x006C4E36 0x006C4DF2>
|
||||
.data 0x00000001
|
||||
.binary 0C # slot count
|
||||
.data 0x006C4F58
|
||||
.data <VERS 0x006C4F9C 0x006C4F58>
|
||||
.data 0x00000004
|
||||
.data 0x00022FC4 # total file size
|
||||
.data 0x006C4F94
|
||||
.data <VERS 0x006C4FD7 0x006C4F94>
|
||||
.data 0x00000001
|
||||
.binary 0C # slot count
|
||||
.data 0x006C5181
|
||||
.data <VERS 0x006C51C5 0x006C5181>
|
||||
.data 0x00000004
|
||||
.data 0x00022FC4 # total file size
|
||||
.data 0x006C51BD
|
||||
.data <VERS 0x006C5201 0x006C51BD>
|
||||
.data 0x00000001
|
||||
.binary 0C # slot count
|
||||
.data 0x006C5332
|
||||
.data <VERS 0x006C5376 0x006C5332>
|
||||
.data 0x00000004
|
||||
.data 0x00022FC4 # total file size
|
||||
.data 0x006C536C
|
||||
.data <VERS 0x006C53B0 0x006C536C>
|
||||
.data 0x00000001
|
||||
.binary 0C # slot count
|
||||
.data 0x006C5501
|
||||
.data <VERS 0x006C5545 0x006C5501>
|
||||
.data 0x00000004
|
||||
.data 0x00022FC4 # total file size
|
||||
.data 0x006C553D
|
||||
.data <VERS 0x006C5581 0x006C553D>
|
||||
.data 0x00000001
|
||||
.binary 0C # slot count
|
||||
.data 0x006C56B2
|
||||
.data <VERS 0x006C56F6 0x006C56B2>
|
||||
.data 0x00000004
|
||||
.data 0x00022FC4 # total file size
|
||||
.data 0x006C56EC
|
||||
.data <VERS 0x006C5730 0x006C56EC>
|
||||
.data 0x00000001
|
||||
.binary 0C # slot count
|
||||
.data 0x006C5872
|
||||
.data <VERS 0x006C58B6 0x006C5872>
|
||||
.data 0x00000004
|
||||
.data 0x00022FC4 # total file size
|
||||
.data 0x006C58AC
|
||||
.data <VERS 0x006C58F0 0x006C58AC>
|
||||
.data 0x00000001
|
||||
.binary 0C # slot count
|
||||
.data 0x006C5A41
|
||||
.data <VERS 0x006C5A85 0x006C5A41>
|
||||
.data 0x00000004
|
||||
.data 0x00022FC4 # total file size
|
||||
.data 0x006C5A7D
|
||||
.data <VERS 0x006C5AC1 0x006C5A7D>
|
||||
.data 0x00000001
|
||||
.binary 0C # slot count
|
||||
.data 0x006C5B6E
|
||||
.data <VERS 0x006C5BB2 0x006C5B6E>
|
||||
.data 0x00000004
|
||||
.data 0x00022FC4 # total file size
|
||||
.data 0x006C5BA8
|
||||
.data <VERS 0x006C5BEC 0x006C5BA8>
|
||||
.data 0x00000001
|
||||
.binary 0C # slot count
|
||||
.data 0x006C5D2E
|
||||
.data <VERS 0x006C5D72 0x006C5D2E>
|
||||
.data 0x00000004
|
||||
.data 0x00022FC4 # total file size
|
||||
.data 0x006C5D68
|
||||
.data <VERS 0x006C5DAC 0x006C5D68>
|
||||
.data 0x00000001
|
||||
.binary 0C # slot count
|
||||
.data 0x006C5EEE
|
||||
.data <VERS 0x006C5F32 0x006C5EEE>
|
||||
.data 0x00000004
|
||||
.data 0x00022FC4 # total file size
|
||||
.data 0x006C5F28
|
||||
.data <VERS 0x006C5F6C 0x006C5F28>
|
||||
.data 0x00000001
|
||||
.binary 0C # slot count
|
||||
.data 0x006C60AE
|
||||
.data <VERS 0x006C60F2 0x006C60AE>
|
||||
.data 0x00000004
|
||||
.data 0x00022FC4 # total file size
|
||||
.data 0x006C60E8
|
||||
.data <VERS 0x006C612C 0x006C60E8>
|
||||
.data 0x00000001
|
||||
.binary 0C # slot count
|
||||
.data 0x006C6303
|
||||
.data <VERS 0x006C6346 0x006C6303>
|
||||
.data 0x00000004
|
||||
.data 0x00022FC4 # total file size
|
||||
.data 0x006C633D
|
||||
.data <VERS 0x006C6381 0x006C633D>
|
||||
.data 0x00000001
|
||||
.binary 0C # slot count
|
||||
.data 0x006C64C1
|
||||
.data <VERS 0x006C6505 0x006C64C1>
|
||||
.data 0x00000004
|
||||
.data 0x00022FC4 # total file size
|
||||
.data 0x006C64FD
|
||||
.data <VERS 0x006C6541 0x006C64FD>
|
||||
.data 0x00000001
|
||||
.binary 0C # slot count
|
||||
.data 0x006C65EE
|
||||
.data <VERS 0x006C6632 0x006C65EE>
|
||||
.data 0x00000004
|
||||
.data 0x00022FC4 # total file size
|
||||
.data 0x006C6628
|
||||
.data <VERS 0x006C666C 0x006C6628>
|
||||
.data 0x00000001
|
||||
.binary 0C # slot count
|
||||
.data 0x006C67AE
|
||||
.data <VERS 0x006C67F2 0x006C67AE>
|
||||
.data 0x00000004
|
||||
.data 0x00022FC4 # total file size
|
||||
.data 0x006C67E8
|
||||
.data <VERS 0x006C682C 0x006C67E8>
|
||||
.data 0x00000001
|
||||
.binary 0C # slot count
|
||||
.data 0x006C696E
|
||||
.data <VERS 0x006C69B2 0x006C696E>
|
||||
.data 0x00000004
|
||||
.data 0x00022FC4 # total file size
|
||||
.data 0x006C69A8
|
||||
.data <VERS 0x006C69EC 0x006C69A8>
|
||||
.data 0x00000001
|
||||
.binary 0C # slot count
|
||||
.data 0x006C6B43
|
||||
.data <VERS 0x006C6B87 0x006C6B43>
|
||||
.data 0x00000004
|
||||
.data 0x00022FC4 # total file size
|
||||
.data 0x006C6B74
|
||||
.data <VERS 0x006C6BB8 0x006C6B74>
|
||||
.data 0x00000004
|
||||
.data 0x0000005D # memcard block count
|
||||
.data 0x006C6BF6
|
||||
.data <VERS 0x006C6C3A 0x006C6BF6>
|
||||
.data 0x00000004
|
||||
.data 0x00022FC4 # total file size
|
||||
.data 0x006C6C30
|
||||
.data <VERS 0x006C6C74 0x006C6C30>
|
||||
.data 0x00000001
|
||||
.binary 0C # slot count
|
||||
.data 0x006C6E3E
|
||||
.data <VERS 0x006C6E82 0x006C6E3E>
|
||||
.data 0x00000004
|
||||
.data 0x00022FC4 # total file size
|
||||
.data 0x006C6E78
|
||||
.data <VERS 0x006C6EBC 0x006C6E78>
|
||||
.data 0x00000001
|
||||
.binary 0C # slot count
|
||||
.data 0x006C7075
|
||||
.data <VERS 0x006C70B9 0x006C7075>
|
||||
.data 0x00000004
|
||||
.data 0x00022FC4 # total file size
|
||||
.data 0x006C70AF
|
||||
.data <VERS 0x006C70F3 0x006C70AF>
|
||||
.data 0x00000001
|
||||
.binary 0C # slot count
|
||||
.data 0x006C7A02
|
||||
.data <VERS 0x006C7A46 0x006C7A02>
|
||||
.data 0x00000004
|
||||
.data 0x00022FC4 # total file size
|
||||
.data 0x006C7D22
|
||||
.data <VERS 0x006C7D66 0x006C7D22>
|
||||
.data 0x00000004
|
||||
.data 0x00022FC4 # total file size
|
||||
.data 0x006C7D5E
|
||||
.data <VERS 0x006C7D7C 0x006C7D5E>
|
||||
.data 0x00000001
|
||||
.binary 0C # slot count
|
||||
.data 0x006C7D7C
|
||||
.data <VERS 0x006C7DC0 0x006C7D7C>
|
||||
.data 0x00000004
|
||||
.data 0x00022FC4 # total file size
|
||||
.data 0x0077BE92
|
||||
.data <VERS 0x0077CC72 0x0077BE92>
|
||||
.data 0x00000004
|
||||
.data 0x00022FB4 # bgm_test_songs_unlocked offset
|
||||
|
||||
# Signature check on all save files (rewritten as loop)
|
||||
.data 0x006C1C2D
|
||||
.data <VERS 0x006C1C69 0x006C1C2D>
|
||||
.deltaof sig_check_begin, sig_check_end
|
||||
sig_check_begin:
|
||||
mov edx, 0xC87ED5B1 # Expected signature value
|
||||
@@ -475,10 +469,10 @@ sig_bad:
|
||||
inc eax
|
||||
jmp sig_check_end
|
||||
.binary CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
|
||||
sig_check_end: # 006C1C76
|
||||
sig_check_end: # <VERS 006C1CB2 006C1C76>
|
||||
|
||||
# Send slot count in E3 command
|
||||
.data 0x0046EB20 # TDataProtocol::send_E3_for_index
|
||||
.data <VERS 0x0046EC10 0x0046EB20> # 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*)
|
||||
@@ -493,7 +487,7 @@ send_slot_count_in_E3_begin:
|
||||
mov eax, [ecx]
|
||||
call [eax + 0x20] # this->send_command(&cmd, 0x10) // ret 8
|
||||
add esp, 8
|
||||
mov eax, 0x006C1A80
|
||||
mov eax, <VERS 0x006C1ABC 0x006C1A80>
|
||||
call eax # set_current_char_slot(slot_index) // ret 0
|
||||
add esp, 8
|
||||
ret 4
|
||||
@@ -521,7 +515,7 @@ show_slot_number_strend_again:
|
||||
jmp show_slot_number_strend_again
|
||||
show_slot_number_strend_done:
|
||||
# Format the slot number and append it to the string
|
||||
mov ecx, [0x00A3B050] # scroll_bar = TAdScrollBarXb_objs[0]
|
||||
mov ecx, [<VERS 0x00A38BD0 0x00A3B050>] # 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)
|
||||
@@ -529,7 +523,7 @@ show_slot_number_strend_done:
|
||||
.binary 20002800230025006400290020000000 # L" (#%d) "
|
||||
get_show_slot_number_suffix_fmt:
|
||||
push eax # Destination buffer
|
||||
mov eax, 0x00857E29 # _swprintf
|
||||
mov eax, <VERS 0x00835578 0x00857E29> # _swprintf
|
||||
call eax
|
||||
add esp, 0x0C
|
||||
jmp show_slot_number_end
|
||||
@@ -543,16 +537,15 @@ show_slot_number_end: # 00401E4D
|
||||
|
||||
|
||||
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
|
||||
# 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, 0x008581C5 # operator_new
|
||||
mov eax, <VERS 0x00835915 0x008581C5> # operator_new
|
||||
call eax
|
||||
add esp, 4
|
||||
mov edx, [0x00A95E44] # edx = old char_file_list
|
||||
mov [0x00A95E44], eax
|
||||
mov edx, [<VERS 0x00A939C4 0x00A95E44>] # edx = old char_file_list
|
||||
mov [<VERS 0x00A939C4 0x00A95E44>], 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
|
||||
@@ -565,7 +558,7 @@ update_existing_char_file_list:
|
||||
add edx, 4
|
||||
mov ecx, 0xBA90
|
||||
call memcpy # Copy the existing 4 characters over
|
||||
mov eax, [0x00A95E44]
|
||||
mov eax, [<VERS 0x00A939C4 0x00A95E44>]
|
||||
add eax, 0xBA94
|
||||
mov ecx, 4
|
||||
clear_next_char:
|
||||
@@ -585,40 +578,37 @@ clear_next_char_done:
|
||||
# countof(char_file_list.chars) - 4,
|
||||
# PSOCharacterFile::init,
|
||||
# PSOCharacterFile::destroy)
|
||||
push 0x006C1940 # PSOCharacterFile::destroy
|
||||
push 0x006C17F0 # PSOCharacterFile::init
|
||||
push <VERS 0x006C197C 0x006C1940> # PSOCharacterFile::destroy
|
||||
push <VERS 0x006C182C 0x006C17F0> # PSOCharacterFile::init
|
||||
push 0x08 # slot count - 4
|
||||
push 0x2EA4 # sizeof(PSOCharacterFile)
|
||||
mov eax, [0x00A95E44]
|
||||
mov eax, [<VERS 0x00A939C4 0x00A95E44>]
|
||||
add eax, 0xBA94
|
||||
push eax
|
||||
mov eax, 0x00858736
|
||||
mov eax, <VERS 0x00835E86 0x00858736>
|
||||
call eax
|
||||
|
||||
# Fix the file's checksum
|
||||
mov eax, [0x00A95E44]
|
||||
mov ecx, 0x006C26FC
|
||||
mov eax, [<VERS 0x00A939C4 0x00A95E44>]
|
||||
mov ecx, <VERS 0x006C2738 0x006C26FC>
|
||||
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.
|
||||
# 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, 0x0082E940
|
||||
mov eax, <VERS 0x0084F258 0x0082E940>
|
||||
call eax # malloc10(total file size)
|
||||
add esp, 4
|
||||
mov [0x00A95E2C], eax
|
||||
mov edx, [0x00A95E44]
|
||||
mov [<VERS 0x00A939AC 0x00A95E2C>], eax
|
||||
mov edx, [<VERS 0x00A939C4 0x00A95E44>]
|
||||
mov ecx, 0x00022FC4 # total file size
|
||||
jmp memcpy
|
||||
@@ -1,103 +0,0 @@
|
||||
# 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 0x00A0FC54
|
||||
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, [0x00A9C4F4] # 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, 0x0068D5AC
|
||||
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 0x009783A0 # prefix = L"EXP"
|
||||
push 0x14
|
||||
push 0x14
|
||||
push 0xFFFF00FF # color (ARGB)
|
||||
add eax, 0x300
|
||||
push eax # position
|
||||
mov eax, 0x0078AABC
|
||||
call eax # TFontSmallTask___new__(...)
|
||||
add esp, 0x1C
|
||||
|
||||
skip_text:
|
||||
mov eax, 0x006928C0 # Original handle_6xBF
|
||||
jmp eax # original_handle_6xBF(cmd)
|
||||
|
||||
get_enemy_entity:
|
||||
.include GetEnemyEntity-59NL
|
||||
ret
|
||||
|
||||
handle_6xBF_end:
|
||||
push ecx
|
||||
.include WriteCallToCode-59NL
|
||||
|
||||
|
||||
|
||||
apply_static_patches:
|
||||
.include WriteCodeBlocksBB
|
||||
|
||||
.data 0x0078749D
|
||||
.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 0x007765A5
|
||||
.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
|
||||
+15
-15
@@ -1,11 +1,11 @@
|
||||
# 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
|
||||
# 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
|
||||
|
||||
.versions 59NJ 59NL
|
||||
|
||||
entry_ptr:
|
||||
reloc0:
|
||||
@@ -20,7 +20,7 @@ start:
|
||||
install_hook:
|
||||
pop ecx
|
||||
push 0 # Write address instead of a call/jmp opcode
|
||||
push 0x00A0DC54
|
||||
push <VERS 0x00A0DC54 0x00A0FC54>
|
||||
call get_code_size
|
||||
.deltaof handle_6xBF_start, handle_6xBF_end
|
||||
get_code_size:
|
||||
@@ -30,7 +30,7 @@ get_code_size:
|
||||
handle_6xBF_start: # [std](G_6xBF* cmd @ [esp + 4]) -> void
|
||||
mov edx, [esp + 4]
|
||||
|
||||
mov ecx, [0x00A9A074] # local_client_id
|
||||
mov ecx, [<VERS 0x00A9A074 0x00A9C4F4>] # local_client_id
|
||||
cmp [edx + 2], cx
|
||||
jne skip_text
|
||||
|
||||
@@ -47,41 +47,41 @@ handle_6xBF_start: # [std](G_6xBF* cmd @ [esp + 4]) -> void
|
||||
jnz enemy_entity_ok
|
||||
|
||||
# Use player entity if enemy entity is already gone
|
||||
mov eax, 0x0068D618
|
||||
mov eax, <VERS 0x0068D618 0x0068D5AC>
|
||||
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 <VERS 0x00976380 0x009783A0> # prefix = L"EXP"
|
||||
push 0x14
|
||||
push 0x14
|
||||
push 0xFFFF00FF # color (ARGB)
|
||||
add eax, 0x300
|
||||
push eax # position
|
||||
mov eax, 0x0078B8E8
|
||||
mov eax, <VERS 0x0078B8E8 0x0078AABC>
|
||||
call eax # TFontSmallTask___new__(...)
|
||||
add esp, 0x1C
|
||||
|
||||
skip_text:
|
||||
mov eax, 0x0069292C # Original handle_6xBF
|
||||
mov eax, <VERS 0x0069292C 0x006928C0> # Original handle_6xBF
|
||||
jmp eax # original_handle_6xBF(cmd)
|
||||
|
||||
get_enemy_entity:
|
||||
.include GetEnemyEntity-59NJ
|
||||
.include GetEnemyEntity
|
||||
ret
|
||||
|
||||
handle_6xBF_end:
|
||||
push ecx
|
||||
.include WriteCallToCode-59NJ
|
||||
.include WriteCallToCode
|
||||
|
||||
|
||||
|
||||
apply_static_patches:
|
||||
.include WriteCodeBlocksBB
|
||||
.include WriteCodeBlocks
|
||||
|
||||
.data 0x0078827D
|
||||
.data <VERS 0x0078827D 0x0078749D>
|
||||
.deltaof disable_kill_enemy_callsite_start, disable_kill_enemy_callsite_end
|
||||
disable_kill_enemy_callsite_start:
|
||||
nop
|
||||
@@ -91,7 +91,7 @@ disable_kill_enemy_callsite_start:
|
||||
nop
|
||||
disable_kill_enemy_callsite_end:
|
||||
|
||||
.data 0x00777381
|
||||
.data <VERS 0x00777381 0x007765A5>
|
||||
.deltaof disable_exp_steal_callsite_start, disable_exp_steal_callsite_end
|
||||
disable_exp_steal_callsite_start:
|
||||
add esp, 0x0C # Original function has `ret 0x0C`
|
||||
+7
-12
@@ -1,14 +1,11 @@
|
||||
# It would be a bad idea to remove `.meta hide_from_patches_menu` to make this
|
||||
# patch an option for players to be able to select; either all players on the
|
||||
# server should have this patch, or none should have it.
|
||||
# It would be a bad idea to change this function's visibility; either all players on the server should have this patch,
|
||||
# or none should have it.
|
||||
|
||||
# If you change the stack limits in config.json away from the defaults, you
|
||||
# should change the limits array below to match config.json and add this patch
|
||||
# to the BBRequiredPatches list.
|
||||
# If you change the stack limits in config.json away from the defaults, you should change the limits array below to
|
||||
# match config.json and add this patch to the BBRequiredPatches list.
|
||||
|
||||
.meta name="Item stacks"
|
||||
.meta description=""
|
||||
.meta hide_from_patches_menu
|
||||
|
||||
.versions 59NJ 59NL
|
||||
|
||||
@@ -16,7 +13,7 @@ entry_ptr:
|
||||
reloc0:
|
||||
.offsetof start
|
||||
start:
|
||||
.include WriteCodeBlocksBB
|
||||
.include WriteCodeBlocks
|
||||
|
||||
# Patch 1: rewrite item_is_stackable
|
||||
.data <VERS 0x005C5020 0x005C502C>
|
||||
@@ -63,10 +60,8 @@ max_stack_size_for_tool_start:
|
||||
|
||||
# declare return values array
|
||||
call data_end
|
||||
# This array specifies the stack limits for each tool class. The array index
|
||||
# is the second byte of the item data (see names-v4.json for the values; for
|
||||
# e.g. tech disks this would be 02). For classes beyond 15, the value for 15
|
||||
# is used.
|
||||
# This array specifies the stack limits for each tool class. The array index is the second byte of the item data (see
|
||||
# names-v4.json for the values; for e.g. tech disks this is 02). For classes beyond 15, the value for 15 is used.
|
||||
# Index: 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 10 11 12 13 14 15
|
||||
.binary 0A 0A 01 0A 0A 0A 0A 0A 0A 01 01 01 01 01 01 01 63 01 01 01 01 01
|
||||
data_end:
|
||||
+237
-18
@@ -1,21 +1,27 @@
|
||||
.meta name="Bug fixes"
|
||||
.meta description="Fixes many minor\ngameplay, sound,\nand graphical bugs"
|
||||
# Most original codes by Ralf @ GC-Forever and Aleron Ives, except where noted
|
||||
# https://www.gc-forever.com/forums/viewtopic.php?t=2050
|
||||
# https://www.gc-forever.com/forums/viewtopic.php?t=2049
|
||||
# Xbox ports by fuzziqersoftware
|
||||
# TODO: Port the rest of the GC patches to Xbox
|
||||
|
||||
.meta visibility="all"
|
||||
.meta name="Bug fixes"
|
||||
.meta description="Fixes many minor\ngameplay, sound,\nand graphical bugs"
|
||||
|
||||
.versions 3OJ2 3OJ3 3OJ4 3OJ5 3OE0 3OE1 3OE2 3OP0
|
||||
|
||||
entry_ptr:
|
||||
reloc0:
|
||||
.offsetof start
|
||||
start:
|
||||
.include WriteCodeBlocksGC
|
||||
.include WriteCodeBlocks
|
||||
|
||||
|
||||
|
||||
|
||||
# Olga Flow Barta Bug Fix (makes barta work on ice weakness Olga Flow instead of damaging player)
|
||||
|
||||
.versions 3OJ2 3OJ3 3OJ4 3OJ5 3OE0 3OE1 3OE2 3OP0
|
||||
|
||||
.label g1_hook_call, <VERS 0x802BB4B0 0x802BC3E0 0x802BD528 0x802BD2C0 0x802BBEF4 0x802BBF38 0x802BD474 0x802BCC08>
|
||||
.label g1_hook_loc, 0x8000D980
|
||||
.data g1_hook_loc
|
||||
@@ -34,10 +40,34 @@ g1_hook_end:
|
||||
.address g1_hook_call
|
||||
bl g1_hook_loc
|
||||
|
||||
.versions 4OJB 4OJD 4OJU 4OED 4OEU 4OPD 4OPU
|
||||
|
||||
.label g1_hook_call, <VERS 0x000970E0 0x000973F0 0x00097460 0x00097140 0x000970E0 0x00097160 0x00096FE0>
|
||||
.label g1_hook_loc, <VERS 0x00097124 0x00097434 0x000974A4 0x00097184 0x00097124 0x000971A4 0x00097024>
|
||||
.data g1_hook_call
|
||||
.data 6
|
||||
.address g1_hook_call
|
||||
mov eax, esi
|
||||
cmp al, 19
|
||||
jmp g1_hook_loc
|
||||
g1_hook_call_end:
|
||||
.data g1_hook_loc
|
||||
.deltaof g1_hook_start, g1_hook_end
|
||||
.address g1_hook_loc
|
||||
g1_hook_start:
|
||||
jne g1_hook_skip_replace_value
|
||||
mov al, 2
|
||||
g1_hook_skip_replace_value:
|
||||
cmp eax, [ebx + 0x440] // Original opcode
|
||||
jmp g1_hook_call_end
|
||||
g1_hook_end:
|
||||
|
||||
|
||||
|
||||
# Morfos Frozen Player Bug Fix (stops Morfos Laser multi-hitting when player is frozen)
|
||||
|
||||
.versions 3OJ2 3OJ3 3OJ4 3OJ5 3OE0 3OE1 3OE2 3OP0
|
||||
|
||||
.label g2_hook_call, <VERS 0x80335060 0x803360CC 0x803375E8 0x8033739C 0x80335A50 0x80335A94 0x80337570 0x803369B4>
|
||||
.label g2_hook_loc, 0x8000D9A0
|
||||
.data g2_hook_loc
|
||||
@@ -57,18 +87,62 @@ g2_hook_end:
|
||||
.address g2_hook_call
|
||||
bl g2_hook_loc
|
||||
|
||||
.versions 4OJB 4OJD 4OJU 4OED 4OEU 4OPD 4OPU
|
||||
|
||||
.label g2_hook_call, <VERS 0x0012E257 0x0012E387 0x0012E4E7 0x0012E537 0x0012E567 0x0012E557 0x0012E5A7>
|
||||
.label g2_hook_loc1, <VERS 0x0012E5F4 0x0012E724 0x0012E884 0x0012E8D4 0x0012E904 0x0012E8F4 0x0012E944>
|
||||
.label g2_hook_loc2, <VERS 0x0012E622 0x0012E752 0x0012E8B2 0x0012E902 0x0012E932 0x0012E922 0x0012E972>
|
||||
.data g2_hook_call
|
||||
.data 6
|
||||
.address g2_hook_call
|
||||
call g2_hook_loc1
|
||||
nop
|
||||
.data g2_hook_loc1
|
||||
.deltaof g2_hook_start1, g2_hook_end1
|
||||
.address g2_hook_loc1
|
||||
g2_hook_start1:
|
||||
fld1 st0 // st = [1.0, speed]
|
||||
fld1 st0 // st = [1.0, 1.0, speed]
|
||||
fadd st0, st1 // st = [2.0, 1.0, speed]
|
||||
fdivp st1, st0 // st = [0.5, speed]
|
||||
jmp g2_hook_loc2
|
||||
g2_hook_end1:
|
||||
|
||||
.data g2_hook_loc2
|
||||
.deltaof g2_hook_start2, g2_hook_end2
|
||||
.address g2_hook_loc2
|
||||
g2_hook_start2:
|
||||
test byte [esi + 0x30], 0x20 // If not set, use 1.5; if set, use 0.5
|
||||
jnz g2_hook_entity_is_frozen
|
||||
fld1 st0 // st = [1, 0.5, speed]
|
||||
faddp st1, st0 // st = [1.5, speed]
|
||||
g2_hook_entity_is_frozen:
|
||||
fmulp st1, st0 // st = [((game_flags & 0x20) ? 0.5 : 1.5) * speed]
|
||||
ret
|
||||
g2_hook_end2:
|
||||
|
||||
|
||||
|
||||
# Tiny Grass Assassins Bug Fix
|
||||
|
||||
.versions 3OJ2 3OJ3 3OJ4 3OJ5 3OE0 3OE1 3OE2 3OP0
|
||||
|
||||
.data <VERS 0x800BC750 0x800BCA58 0x800BCBD0 0x800BCB80 0x800BC9E8 0x800BC9E8 0x800BCB90 0x800BCB58>
|
||||
.data 4
|
||||
b +0x10
|
||||
|
||||
.versions 4OJB 4OJD 4OJU 4OED 4OEU 4OPD 4OPU
|
||||
|
||||
.data <VERS 0x0016227A 0x0016238A 0x0016232A 0x0016240A 0x0016229A 0x0016242A 0x0016225A>
|
||||
.data 0x00000002
|
||||
.binary EB0E
|
||||
|
||||
|
||||
|
||||
# Bulclaw HP Bug Fix
|
||||
|
||||
.versions 3OJ2 3OJ3 3OJ4 3OJ5 3OE0 3OE1 3OE2 3OP0
|
||||
|
||||
.data <VERS 0x80091528 0x80091814 0x8009198C 0x8009193C 0x800917B4 0x800917B4 0x8009194C 0x80091914>
|
||||
.data 8
|
||||
bl +0x024C
|
||||
@@ -78,6 +152,8 @@ g2_hook_end:
|
||||
|
||||
# Control Tower: Delbiter Death SFX Bug Fix
|
||||
|
||||
.versions 3OJ2 3OJ3 3OJ4 3OJ5 3OE0 3OE1 3OE2 3OP0
|
||||
|
||||
.label g3_patch_loc, <VERS 0x80301600 0x803025CC 0x80303A1C 0x803037D0 0x80301F58 0x80301F9C 0x8030398C 0x80302D64>
|
||||
.data g3_patch_loc
|
||||
.deltaof g3_code_start, g3_code_end
|
||||
@@ -101,6 +177,8 @@ g3_code_end:
|
||||
|
||||
# Weapon Attributes Patch (allows attributes to work on minibosses and Olga Flow)
|
||||
|
||||
.versions 3OJ2 3OJ3 3OJ4 3OJ5 3OE0 3OE1 3OE2 3OP0
|
||||
|
||||
.label g4_hook_call1, <VERS 0x800142DC 0x8001430C 0x800146A4 0x800142BC 0x800142F4 0x800142F4 0x800142BC 0x80014334>
|
||||
.label g4_hook_call2, <VERS 0x80015D04 0x80015D34 0x80016174 0x80015CE4 0x80015D1C 0x80015D1C 0x80015CE4 0x80015D5C>
|
||||
.label g4_hook_loc, 0x8000C8C0
|
||||
@@ -129,6 +207,8 @@ g4_hook_end:
|
||||
|
||||
# Ruins Laser Fence SFX Bug Fix
|
||||
|
||||
.versions 3OJ2 3OJ3 3OJ4 3OJ5 3OE0 3OE1 3OE2 3OP0
|
||||
|
||||
.data <VERS 0x80166324 0x801666D8 0x80166848 0x8016679C 0x801666E0 0x801666E0 0x80166800 0x80166CC4>
|
||||
.data 8
|
||||
lis r3, 0x4005
|
||||
@@ -142,6 +222,8 @@ g4_hook_end:
|
||||
|
||||
# SFX Cancellation Distance Bug Fix
|
||||
|
||||
.versions 3OJ2 3OJ3 3OJ4 3OJ5 3OE0 3OE1 3OE2 3OP0
|
||||
|
||||
.data <VERS 0x805CB608 0x805D5C08 0x805DD0A8 0x805DCE48 0x805CBF10 0x805D2F30 0x805DC750 0x805D8990>
|
||||
.data 4
|
||||
.float 22500
|
||||
@@ -154,6 +236,8 @@ g4_hook_end:
|
||||
|
||||
# Foie SFX Pitch Bug Fix
|
||||
|
||||
.versions 3OJ2 3OJ3 3OJ4 3OJ5 3OE0 3OE1 3OE2 3OP0
|
||||
|
||||
.data <VERS 0x8022E2A8 0x8022EC44 0x8022FB30 0x8022F8E4 0x8022EB64 0x8022EB64 0x8022FC18 0x8022F4B0>
|
||||
.data 4
|
||||
li r4, 0xFFFFFF00
|
||||
@@ -170,6 +254,8 @@ g4_hook_end:
|
||||
|
||||
# Gifoie SFX Pitch Bug Fix
|
||||
|
||||
.versions 3OJ2 3OJ3 3OJ4 3OJ5 3OE0 3OE1 3OE2 3OP0
|
||||
|
||||
.data <VERS 0x802300B8 0x80230A54 0x80231940 0x802316F4 0x80230974 0x80230974 0x80231A28 0x802312C0>
|
||||
.data 4
|
||||
li r4, 0xFFFFFF00
|
||||
@@ -186,6 +272,8 @@ g4_hook_end:
|
||||
|
||||
# Rafoie SFX Pitch Bug Fix
|
||||
|
||||
.versions 3OJ2 3OJ3 3OJ4 3OJ5 3OE0 3OE1 3OE2 3OP0
|
||||
|
||||
.data <VERS 0x802365AC 0x80236F68 0x80237E54 0x80237C08 0x80236E88 0x80236E88 0x80237F3C 0x802377D4>
|
||||
.data 4
|
||||
li r4, 0xFFFFFF00
|
||||
@@ -214,6 +302,8 @@ g4_hook_end:
|
||||
|
||||
# Barta SFX Pitch Bug Fix
|
||||
|
||||
.versions 3OJ2 3OJ3 3OJ4 3OJ5 3OE0 3OE1 3OE2 3OP0
|
||||
|
||||
.data <VERS 0x80229B54 0x8022A4F0 0x8022B3E0 0x8022B190 0x8022A410 0x8022A410 0x8022B4C4 0x8022AD5C>
|
||||
.data 4
|
||||
li r4, 0xFFFFFF00
|
||||
@@ -230,6 +320,8 @@ g4_hook_end:
|
||||
|
||||
# Gibarta SFX Pitch Bug Fix
|
||||
|
||||
.versions 3OJ2 3OJ3 3OJ4 3OJ5 3OE0 3OE1 3OE2 3OP0
|
||||
|
||||
.data <VERS 0x8022EAB4 0x8022F450 0x80230340 0x802300F0 0x8022F370 0x8022F370 0x80230424 0x8022FCBC>
|
||||
.data 4
|
||||
li r4, 0xFFFFFF00
|
||||
@@ -246,6 +338,8 @@ g4_hook_end:
|
||||
|
||||
# Rabarta SFX Pitch Bug Fix
|
||||
|
||||
.versions 3OJ2 3OJ3 3OJ4 3OJ5 3OE0 3OE1 3OE2 3OP0
|
||||
|
||||
.data <VERS 0x80235DD4 0x80236790 0x8023767C 0x80237430 0x802366B0 0x802366B0 0x80237764 0x80236FFC>
|
||||
.data 4
|
||||
li r4, 0xFFFFFF00
|
||||
@@ -262,6 +356,8 @@ g4_hook_end:
|
||||
|
||||
# Zonde SFX Pitch Bug Fix
|
||||
|
||||
.versions 3OJ2 3OJ3 3OJ4 3OJ5 3OE0 3OE1 3OE2 3OP0
|
||||
|
||||
.data <VERS 0x8023B2C8 0x8023BC84 0x8023CB70 0x8023C924 0x8023BBA4 0x8023BBA4 0x8023CC58 0x8023C4F0>
|
||||
.data 4
|
||||
li r4, 0xFFFFFF00
|
||||
@@ -278,6 +374,8 @@ g4_hook_end:
|
||||
|
||||
# Gizonde SFX Pitch Bug Fix
|
||||
|
||||
.versions 3OJ2 3OJ3 3OJ4 3OJ5 3OE0 3OE1 3OE2 3OP0
|
||||
|
||||
.data <VERS 0x80230E08 0x802317C4 0x802326B0 0x80232464 0x802316E4 0x802316E4 0x80232798 0x80232030>
|
||||
.data 4
|
||||
li r4, 0xFFFFFF00
|
||||
@@ -294,6 +392,8 @@ g4_hook_end:
|
||||
|
||||
# Razonde SFX Pitch Bug Fix
|
||||
|
||||
.versions 3OJ2 3OJ3 3OJ4 3OJ5 3OE0 3OE1 3OE2 3OP0
|
||||
|
||||
.data <VERS 0x80237998 0x80238354 0x80239240 0x80238FF4 0x80238274 0x80238274 0x80239328 0x80238BC0>
|
||||
.data 4
|
||||
li r4, 0xFFFFFF00
|
||||
@@ -310,6 +410,8 @@ g4_hook_end:
|
||||
|
||||
# Grants SFX Pitch Bug Fix
|
||||
|
||||
.versions 3OJ2 3OJ3 3OJ4 3OJ5 3OE0 3OE1 3OE2 3OP0
|
||||
|
||||
.data <VERS 0x802316FC 0x802320B8 0x80232FA4 0x80232D58 0x80231FD8 0x80231FD8 0x8023308C 0x80232924>
|
||||
.data 4
|
||||
li r4, 0xFFFFFF00
|
||||
@@ -326,6 +428,8 @@ g4_hook_end:
|
||||
|
||||
# Megid SFX Pitch Bug Fix
|
||||
|
||||
.versions 3OJ2 3OJ3 3OJ4 3OJ5 3OE0 3OE1 3OE2 3OP0
|
||||
|
||||
.data <VERS 0x802337A8 0x80234164 0x80235050 0x80234E04 0x80234084 0x80234084 0x80235138 0x802349D0>
|
||||
.data 4
|
||||
li r4, 0xFFFFFF00
|
||||
@@ -342,6 +446,8 @@ g4_hook_end:
|
||||
|
||||
# Anti SFX Pitch Bug Fix
|
||||
|
||||
.versions 3OJ2 3OJ3 3OJ4 3OJ5 3OE0 3OE1 3OE2 3OP0
|
||||
|
||||
.data <VERS 0x80229354 0x80229CF0 0x8022ABDC 0x8022A990 0x80229C10 0x80229C10 0x8022ACC4 0x8022A55C>
|
||||
.data 4
|
||||
cmpwi r0, 1
|
||||
@@ -350,26 +456,46 @@ g4_hook_end:
|
||||
|
||||
# Shield DFP/EVP Bug Fix (allows shields to reach true max DFP/EVP values)
|
||||
|
||||
.versions 3OJ2 3OJ3 3OJ4 3OJ5 3OE0 3OE1 3OE2 3OP0
|
||||
|
||||
.data <VERS 0x801185B0 0x801187CC 0x8011885C 0x80118764 0x80118854 0x80118854 0x80118774 0x8011894C>
|
||||
.data 4
|
||||
lbz r0, [r4 + 0x0016]
|
||||
|
||||
.data <VERS 0x801185BC 0x801187D8 0x80118868 0x80118770 0x80118860 0x80118860 0x80118780 0x80118958>
|
||||
.data 4
|
||||
lbz r0, [r4 + 0x0017]
|
||||
|
||||
.versions 4OJB 4OJD 4OJU 4OED 4OEU 4OPD 4OPU
|
||||
|
||||
.data <VERS 0x00185D8E 0x00185F4E 0x0018600E 0x00185F0E 0x00185F6E 0x00185F2E 0x00185F2E>
|
||||
.data 0x00000001
|
||||
.binary 16
|
||||
.data <VERS 0x00185D97 0x00185F57 0x00186017 0x00185F17 0x00185F77 0x00185F37 0x00185F37>
|
||||
.data 0x00000001
|
||||
.binary 17
|
||||
|
||||
|
||||
|
||||
# VR Spaceship Item Drop Bug Fix (allows items to drop from enemies above a certain Y position)
|
||||
|
||||
.versions 3OJ2 3OJ3 3OJ4 3OJ5 3OE0 3OE1 3OE2 3OP0
|
||||
|
||||
.data <VERS 0x805C996C 0x805D3F6C 0x805DB40C 0x805DB1AC 0x805CA274 0x805D1294 0x805DAAB4 0x805D6CF4>
|
||||
.data 4
|
||||
.float 220
|
||||
|
||||
.versions 4OJB 4OJD 4OJU 4OED 4OEU 4OPD 4OPU
|
||||
|
||||
.data <VERS 0x00175D75 0x00175E55 0x00175F35 0x00175EC5 0x00175ED5 0x00175EE5 0x00175E95>
|
||||
.data 0x00000002
|
||||
.data 0x435C0000
|
||||
|
||||
|
||||
|
||||
# Invalid Items Bug Fix
|
||||
|
||||
.versions 3OJ2 3OJ3 3OJ4 3OJ5 3OE0 3OE1 3OE2 3OP0
|
||||
|
||||
.data <VERS 0x8011CA90 0x8011CCD4 0x8011CD0C 0x8011CC6C 0x8011CD34 0x8011CD34 0x8011CC7C 0x8011CE54>
|
||||
.data 0x0C
|
||||
mr r3, r0
|
||||
@@ -392,6 +518,8 @@ g4_hook_end:
|
||||
|
||||
# Item Removal Maxed Stats Bug Fix
|
||||
|
||||
.versions 3OJ2 3OJ3 3OJ4 3OJ5 3OE0 3OE1 3OE2 3OP0
|
||||
|
||||
.label g5_hook1_call, <VERS 0x801B9A88 0x801B9EF4 0x801BCF6C 0x801B9FC0 0x801B9E74 0x801B9E74 0x801BA024 0x801BA4E0>
|
||||
.label g5_hook1_ret, <VERS 0x801B9A8C 0x801B9EF8 0x801BCF70 0x801B9FC4 0x801B9E78 0x801B9E78 0x801BA028 0x801BA4E4>
|
||||
.label g5_hook2_call, <VERS 0x8010B970 0x8010BB70 0x8010BC04 0x8010BAF0 0x8010BC14 0x8010BC14 0x8010BB00 0x8010BCF0>
|
||||
@@ -498,6 +626,8 @@ g5_hook4_end:
|
||||
|
||||
# Unit Present Bug Fix
|
||||
|
||||
.versions 3OJ2 3OJ3 3OJ4 3OJ5 3OE0 3OE1 3OE2 3OP0
|
||||
|
||||
.label g6_hook_loc, 0x8000C640
|
||||
.label g6_hook_call, <VERS 0x80118CE0 0x80118EFC 0x80118FD8 0x80118E94 0x80118F84 0x80118F84 0x80118EA4 0x8011907C>
|
||||
.data g6_hook_loc
|
||||
@@ -521,6 +651,8 @@ g6_hook_end:
|
||||
|
||||
# Bank Item Stacking Bug Fix
|
||||
|
||||
.versions 3OJ2 3OJ3 3OJ4 3OJ5 3OE0 3OE1 3OE2 3OP0
|
||||
|
||||
.label g7_hook1_loc, 0x8000C6D0
|
||||
.label g7_hook1_call, <VERS 0x8021D098 0x8021D9FC 0x8021E8E8 0x8021E69C 0x8021D91C 0x8021D91C 0x8021E9D0 0x8021E268>
|
||||
.label g7_hook2_call, <VERS 0x80220528 0x80220EBC 0x80221DA8 0x80221B5C 0x80220DDC 0x80220DDC 0x80221E90 0x80221728>
|
||||
@@ -553,14 +685,27 @@ g7_hooks_end:
|
||||
|
||||
# Dropped Mag Color Bug Fix
|
||||
|
||||
.versions 3OJ2 3OJ3 3OJ4 3OJ5 3OE0 3OE1 3OE2 3OP0
|
||||
|
||||
.data <VERS 0x80114378 0x8011458C 0x80114634 0x80114524 0x8011461C 0x8011461C 0x80114534 0x8011470C>
|
||||
.data 4
|
||||
li r0, 0x12
|
||||
|
||||
.versions 4OJB
|
||||
|
||||
.data 0x001759E6
|
||||
.data 1
|
||||
.binary 12
|
||||
.data 0x00180898
|
||||
.data 1
|
||||
.binary 12
|
||||
|
||||
|
||||
|
||||
# Meseta Drop System Bug Fix
|
||||
|
||||
.versions 3OJ2 3OJ3 3OJ4 3OJ5 3OE0 3OE1 3OE2 3OP0
|
||||
|
||||
.data <VERS 0x80107478 0x80107654 0x80107708 0x801075D4 0x8010771C 0x8010771C 0x801075E4 0x801077D4>
|
||||
.data 4
|
||||
b +0x0C
|
||||
@@ -573,16 +718,18 @@ g7_hooks_end:
|
||||
|
||||
# Present Color Bug Fix
|
||||
|
||||
.only_versions 3OJ2 3OE0 3OE1
|
||||
.versions 3OJ2 3OE0 3OE1
|
||||
|
||||
.data <VERS 0x80101C14 0x80101EB8 0x80101EB8>
|
||||
.data 4
|
||||
nop
|
||||
.all_versions
|
||||
|
||||
|
||||
|
||||
# Offline Quests Drop Table Bug Fix
|
||||
|
||||
.versions 3OJ2 3OJ3 3OJ4 3OJ5 3OE0 3OE1 3OE2 3OP0
|
||||
|
||||
.data <VERS 0x80104B48 0x80104D24 0x80104DE0 0x80104CA4 0x80104DEC 0x80104DEC 0x80104CB4 0x80104EA4>
|
||||
.data 4
|
||||
beq +0x0C
|
||||
@@ -591,6 +738,8 @@ g7_hooks_end:
|
||||
|
||||
# Mag Revival Priority Bug Fix
|
||||
|
||||
.versions 3OJ2 3OJ3 3OJ4 3OJ5 3OE0 3OE1 3OE2 3OP0
|
||||
|
||||
.label g8_hook_loc, 0x8000C8A0
|
||||
.label g8_hook_call, <VERS 0x80112664 0x80112864 0x80112A3C 0x801127F0 0x80112908 0x80112908 0x80112800 0x801129E4>
|
||||
.data g8_hook_loc
|
||||
@@ -613,6 +762,8 @@ g8_hook_end:
|
||||
|
||||
# Mag Revival Challenge & Quest Mode Bug Fix
|
||||
|
||||
.versions 3OJ2 3OJ3 3OJ4 3OJ5 3OE0 3OE1 3OE2 3OP0
|
||||
|
||||
.data <VERS 0x801CA1F4 0x801CA6E0 0x801CB5EC 0x801CA7AC 0x801CA610 0x801CA610 0x801CA810 0x801CACCC>
|
||||
.data 4
|
||||
b +0x10
|
||||
@@ -621,6 +772,8 @@ g8_hook_end:
|
||||
|
||||
# Chat Bubble Window TAB Bug Fix
|
||||
|
||||
.versions 3OJ2 3OJ3 3OJ4 3OJ5 3OE0 3OE1 3OE2 3OP0
|
||||
|
||||
.data <VERS 0x80250264 0x80250CB0 0x80251CA4 0x802519A4 0x80250AEC 0x80250AEC 0x80251C68 0x802514B0>
|
||||
.data 4
|
||||
nop
|
||||
@@ -629,6 +782,8 @@ g8_hook_end:
|
||||
|
||||
# Chat Log Window LF/Tab Bug Fix
|
||||
|
||||
.versions 3OJ2 3OJ3 3OJ4 3OJ5 3OE0 3OE1 3OE2 3OP0
|
||||
|
||||
.data <VERS 0x80267DDC 0x80268A88 0x80269AE4 0x80269898 0x80268788 0x80268788 0x80269B5C 0x802693A4>
|
||||
.data 4
|
||||
nop
|
||||
@@ -637,6 +792,8 @@ g8_hook_end:
|
||||
|
||||
# Dark/Hell Special GFX Bug Fix (makes Dark/Hell display graphic on success like in PSO BB)
|
||||
|
||||
.versions 3OJ2 3OJ3 3OJ4 3OJ5 3OE0 3OE1 3OE2 3OP0
|
||||
|
||||
.label g9_hook_loc, 0x8000E1E0
|
||||
.label g9_hook_call1, <VERS 0x80355984 0x80356D88 0x803582E4 0x80358098 0x80356838 0x8035687C 0x80358464 0x80357858>
|
||||
.label g9_hook_call2, <VERS 0x80355A04 0x80356E08 0x80358364 0x80358118 0x803568B8 0x803568FC 0x803584E4 0x803578D8>
|
||||
@@ -675,14 +832,24 @@ g9_hook_end:
|
||||
|
||||
# Gol Dragon Camera Bug Fix (makes the camera after Gol Dragon display "normally")
|
||||
|
||||
.versions 3OJ2 3OJ3 3OJ4 3OJ5 3OE0 3OE1 3OE2 3OP0
|
||||
|
||||
.data <VERS 0x802FB99C 0x802FC968 0x802FDE60 0x802FDB6C 0x802FC2F4 0x802FC338 0x802FDD28 0x802FD100>
|
||||
.data 4
|
||||
cmpwi r3, 1
|
||||
|
||||
.versions 4OJB 4OJD 4OJU 4OED 4OEU 4OPD 4OPU
|
||||
|
||||
.data <VERS 0x000A8AE1 0x000A8C51 0x000A8BD1 0x000A89C1 0x000A8961 0x000A89E1 0x000A8921>
|
||||
.data 0x00000002
|
||||
.binary 01
|
||||
|
||||
|
||||
|
||||
# Box/Fence Fadeout Bug Fix (stops boxes and other environmental objects fading in and out as you approach)
|
||||
|
||||
.versions 3OJ2 3OJ3 3OJ4 3OJ5 3OE0 3OE1 3OE2 3OP0
|
||||
|
||||
.data <VERS 0x80189A54 0x80189E2C 0x80189F90 0x80189EF0 0x80189E20 0x80189E20 0x80189F54 0x8018A418>
|
||||
.data 4
|
||||
nop
|
||||
@@ -691,30 +858,62 @@ g9_hook_end:
|
||||
.data 4
|
||||
nop
|
||||
|
||||
.versions 4OJB 4OJD 4OJU 4OED 4OEU 4OPD 4OPU
|
||||
|
||||
.data <VERS 0x001D229B 0x001D244B 0x001D295B 0x001D241B 0x001D26AB 0x001D243B 0x001D26DB>
|
||||
.data 2
|
||||
nop
|
||||
nop
|
||||
|
||||
.data <VERS 0x001DF7C4 0x001DF924 0x001DFD94 0x001DF964 0x001DFB04 0x001DF984 0x001DFA74>
|
||||
.data 6
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
|
||||
|
||||
|
||||
# TP Bar Color Bug Fix
|
||||
|
||||
.versions 3OJ2 3OJ3 3OJ4 3OJ5 3OE0 3OE1 3OE2 3OP0
|
||||
|
||||
.data <VERS 0x8026DA74 0x8026E738 0x8026F794 0x8026F548 0x8026E2D4 0x8026E2D4 0x8026F6FC 0x8026EF44>
|
||||
.data 4
|
||||
subi r4, r4, 0x5506
|
||||
|
||||
.data <VERS 0x8026DB88 0x8026E84C 0x8026F8A8 0x8026F65C 0x8026E3E8 0x8026E3E8 0x8026F810 0x8026F058>
|
||||
.data 4
|
||||
subi r3, r3, 0x5506
|
||||
|
||||
.data <VERS 0x8026DC10 0x8026E8D4 0x8026F930 0x8026F6E4 0x8026E470 0x8026E470 0x8026F898 0x8026F0E0>
|
||||
.data 4
|
||||
subi r4, r3, 0x5506
|
||||
|
||||
.data <VERS 0x804CBB40 0x804CF290 0x804D17E0 0x804D1580 0x804CC310 0x804CC7F0 0x804D0E58 0x804D1248>
|
||||
.data 4
|
||||
.data 0xFF0074EE
|
||||
|
||||
.versions 4OJB 4OJD 4OJU 4OED 4OEU 4OPD 4OPU
|
||||
|
||||
.data <VERS 0x002779CE 0x00277C7E 0x0027808E 0x00277DAE 0x00277ECE 0x00277DCE 0x00277F9E>
|
||||
.data 0x00000004
|
||||
.data 0xFF00AAFA
|
||||
.data <VERS 0x002779DE 0x00277C8E 0x0027809E 0x00277DBE 0x00277EDE 0x00277DDE 0x00277FAE>
|
||||
.data 0x00000004
|
||||
.data 0xFF00AAFA
|
||||
.data <VERS 0x00277A24 0x00277CD4 0x002780E4 0x00277E04 0x00277F24 0x00277E24 0x00277FF4>
|
||||
.data 0x00000004
|
||||
.data 0xFF00AAFA
|
||||
.data <VERS 0x0054543C 0x00545ACC 0x0054D5B4 0x0054AA34 0x0054A2D4 0x0054AA34 0x0054ADD4>
|
||||
.data 0x00000004
|
||||
.data 0xFF0074EE
|
||||
|
||||
|
||||
|
||||
# Devil's and Demon's Special Damage Display Bug Fix
|
||||
|
||||
.versions 3OJ2 3OJ3 3OJ4 3OJ5 3OE0 3OE1 3OE2 3OP0
|
||||
|
||||
.data <VERS 0x8001306C 0x8001309C 0x80013364 0x8001304C 0x80013084 0x80013084 0x8001304C 0x800130C4>
|
||||
.data 4
|
||||
b -0x0340
|
||||
@@ -723,6 +922,8 @@ g9_hook_end:
|
||||
|
||||
# Christmas Trees Bug Fix
|
||||
|
||||
.versions 3OJ2 3OJ3 3OJ4 3OJ5 3OE0 3OE1 3OE2 3OP0
|
||||
|
||||
.label g10_hook_loc, 0x8000B5C8
|
||||
.label g10_hook_call, <VERS 0x80183E94 0x8018425C 0x801843C0 0x80184320 0x80184250 0x80184250 0x80184384 0x80184848>
|
||||
.label g10_hook_ret, <VERS 0x80183E98 0x80184260 0x801843C4 0x80184324 0x80184254 0x80184254 0x80184388 0x8018484C>
|
||||
@@ -750,15 +951,25 @@ g10_hook_end:
|
||||
|
||||
# Rain Drops Color Bug Fix
|
||||
|
||||
.versions 3OJ2 3OJ3 3OJ4 3OJ5 3OE0 3OE1 3OE2 3OP0
|
||||
|
||||
.data <VERS 0x804B3738 0x804B6E58 0x804B92F8 0x804B90B8 0x804B3EF0 0x804B43D0 0x804B8990 0x804B8E10>
|
||||
.data 8
|
||||
.data 0x70808080
|
||||
.data 0x60707070
|
||||
|
||||
.versions 4OJB 4OJD 4OJU 4OED 4OEU 4OPD 4OPU
|
||||
|
||||
.data <VERS 0x0054D670 0x0054DD00 0x005557E8 0x00552C68 0x00552508 0x00552C68 0x00553008>
|
||||
.data 0x00000008
|
||||
.binary 7080808060707070
|
||||
|
||||
|
||||
|
||||
# Reverser Target Lock Bug Fix
|
||||
|
||||
.versions 3OJ2 3OJ3 3OJ4 3OJ5 3OE0 3OE1 3OE2 3OP0
|
||||
|
||||
.data <VERS 0x801C5EA4 0x801C6360 0x801C6604 0x801C642C 0x801C62C0 0x801C62C0 0x801C6490 0x801C694C>
|
||||
.data 4
|
||||
addi r4, r31, 0x02FC
|
||||
@@ -767,11 +978,13 @@ g10_hook_end:
|
||||
|
||||
# Deband/Shifta/Resta Target Bug Fix
|
||||
|
||||
.versions 3OJ2 3OJ3 3OJ4 3OJ5 3OE0 3OE1 3OE2 3OP0
|
||||
|
||||
.data <VERS 0x8022CF84 0x8022D920 0x8022E85C 0x8022E5C0 0x8022D840 0x8022D840 0x8022E8F4 0x8022E18C>
|
||||
.data 4
|
||||
bgt +0x0630
|
||||
|
||||
.only_versions 3OJ2 3OE0 3OE1
|
||||
.versions 3OJ2 3OE0 3OE1
|
||||
.data <VERS 0x8022D278 0x8022DB34 0x8022DB34>
|
||||
.data 4
|
||||
bgt +0x033C
|
||||
@@ -779,12 +992,13 @@ g10_hook_end:
|
||||
.data <VERS 0x8022D36C 0x8022DC28 0x8022DC28>
|
||||
.data 4
|
||||
bgt +0x0248
|
||||
.all_versions
|
||||
|
||||
|
||||
|
||||
# Tech Auto Targeting Bug Fix
|
||||
|
||||
.versions 3OJ2 3OJ3 3OJ4 3OJ5 3OE0 3OE1 3OE2 3OP0
|
||||
|
||||
.data <VERS 0x8022C850 0x8022D1EC 0x8022E128 0x8022DE8C 0x8022D10C 0x8022D10C 0x8022E1C0 0x8022DA58>
|
||||
.data 4
|
||||
nop
|
||||
@@ -817,6 +1031,8 @@ g10_hook_end:
|
||||
|
||||
# Enable Trap Animations
|
||||
|
||||
.versions 3OJ2 3OJ3 3OJ4 3OJ5 3OE0 3OE1 3OE2 3OP0
|
||||
|
||||
.label g11_hook_loc, 0x8000BBD0
|
||||
.label g11_hook_call, <VERS 0x80170C54 0x80171008 0x80171260 0x801710CC 0x80171010 0x80171010 0x80171130 0x801715F4>
|
||||
.data g11_hook_loc
|
||||
@@ -846,7 +1062,8 @@ g11_hook_end:
|
||||
|
||||
# Belra arm bug fix (this part by fuzziqersoftware)
|
||||
|
||||
.only_versions 3OJ2 3OE0 3OE1
|
||||
.versions 3OJ2 3OE0 3OE1
|
||||
|
||||
.label g12_hook1_call, <VERS 0x80095724 0x800959B0 0x800959B0>
|
||||
.label g12_hook2_call, <VERS 0x80095734 0x800959C0 0x800959C0>
|
||||
.label g12_hook_loc, 0x8000B06C
|
||||
@@ -874,12 +1091,12 @@ g12_hook_end:
|
||||
.address g12_hook2_call
|
||||
bl g12_hook2_start
|
||||
|
||||
.all_versions
|
||||
|
||||
|
||||
|
||||
# Tsumikiri J-Sword special attack + rapid weapon switch bug fix (this part by fuzziqersoftware)
|
||||
|
||||
.versions 3OJ2 3OJ3 3OJ4 3OJ5 3OE0 3OE1 3OE2 3OP0
|
||||
|
||||
.label tjs_switch_fix_hook_call, <VERS 0x8034CFA8 0x8034E3AC 0x8034F908 0x8034F6BC 0x8034DE5C 0x8034DEA0 0x8034FA88 0x8034EE7C>
|
||||
.label tjs_switch_fix_hook_loc, 0x8000B050
|
||||
.data tjs_switch_fix_hook_loc
|
||||
@@ -905,7 +1122,8 @@ tjs_switch_fix_hook_end:
|
||||
|
||||
# Battle param reload bug fix (this part by fuzziqersoftware)
|
||||
|
||||
.only_versions 3OJ2 3OE0 3OE1
|
||||
.versions 3OJ2 3OE0 3OE1
|
||||
|
||||
.label end_loading_screen, <VERS 0x8001C6D0 0x8001C8F0 0x8001C8F0>
|
||||
.label load_battle_params, <VERS 0x8001DA48 0x8001DC68 0x8001DC68>
|
||||
.label bp_reload_hook_loc, 0x8000E1BC
|
||||
@@ -928,9 +1146,10 @@ bp_reload_hook_end:
|
||||
.data 4
|
||||
.address bp_reload_hook_call
|
||||
bl bp_reload_hook_start
|
||||
|
||||
|
||||
|
||||
.all_versions
|
||||
|
||||
|
||||
|
||||
.data 0
|
||||
.data 0
|
||||
@@ -1,213 +0,0 @@
|
||||
.meta name="Bug fixes"
|
||||
.meta description="Fixes many minor\ngameplay, sound,\nand graphical bugs"
|
||||
# Original code by Ralf @ GC-Forever and Aleron Ives
|
||||
# https://www.gc-forever.com/forums/viewtopic.php?t=2050
|
||||
# https://www.gc-forever.com/forums/viewtopic.php?t=2049
|
||||
# Xbox port by fuzziqersoftware
|
||||
|
||||
# This patch is a collection of many smaller patches, most of which are not yet ported.
|
||||
|
||||
.versions 4OJB 4OJD 4OJU 4OED 4OEU 4OPD 4OPU
|
||||
|
||||
entry_ptr:
|
||||
reloc0:
|
||||
.offsetof start
|
||||
start:
|
||||
.include WriteCodeBlocksXB
|
||||
|
||||
|
||||
|
||||
# Tiny Grass Assassins Bug Fix
|
||||
|
||||
.data <VERS 0x0016227A 0x0016238A 0x0016232A 0x0016240A 0x0016229A 0x0016242A 0x0016225A>
|
||||
.data 0x00000002
|
||||
.binary EB0E
|
||||
|
||||
|
||||
|
||||
# Shield DFP/EVP Bug Fix (allows shields to reach true max DFP/EVP values)
|
||||
|
||||
.data <VERS 0x00185D8E 0x00185F4E 0x0018600E 0x00185F0E 0x00185F6E 0x00185F2E 0x00185F2E>
|
||||
.data 0x00000001
|
||||
.binary 16
|
||||
.data <VERS 0x00185D97 0x00185F57 0x00186017 0x00185F17 0x00185F77 0x00185F37 0x00185F37>
|
||||
.data 0x00000001
|
||||
.binary 17
|
||||
|
||||
|
||||
|
||||
# VR Spaceship Item Drop Bug Fix (allows items to drop from enemies above a certain Y position)
|
||||
|
||||
.data <VERS 0x00175D75 0x00175E55 0x00175F35 0x00175EC5 0x00175ED5 0x00175EE5 0x00175E95>
|
||||
.data 0x00000002
|
||||
.data 0x435C0000
|
||||
|
||||
|
||||
|
||||
# Gol Dragon Camera Bug Fix (makes the camera after Gol Dragon display "normally")
|
||||
|
||||
.data <VERS 0x000A8AE1 0x000A8C51 0x000A8BD1 0x000A89C1 0x000A8961 0x000A89E1 0x000A8921>
|
||||
.data 0x00000002
|
||||
.binary 01
|
||||
|
||||
|
||||
|
||||
# Rain Drops Color Bug Fix
|
||||
|
||||
.data <VERS 0x0054D670 0x0054DD00 0x005557E8 0x00552C68 0x00552508 0x00552C68 0x00553008>
|
||||
.data 0x00000008
|
||||
.binary 7080808060707070
|
||||
|
||||
|
||||
|
||||
# TP Bar Color Bug Fix
|
||||
|
||||
.data <VERS 0x002779CE 0x00277C7E 0x0027808E 0x00277DAE 0x00277ECE 0x00277DCE 0x00277F9E>
|
||||
.data 0x00000004
|
||||
.data 0xFF00AAFA
|
||||
.data <VERS 0x002779DE 0x00277C8E 0x0027809E 0x00277DBE 0x00277EDE 0x00277DDE 0x00277FAE>
|
||||
.data 0x00000004
|
||||
.data 0xFF00AAFA
|
||||
.data <VERS 0x00277A24 0x00277CD4 0x002780E4 0x00277E04 0x00277F24 0x00277E24 0x00277FF4>
|
||||
.data 0x00000004
|
||||
.data 0xFF00AAFA
|
||||
.data <VERS 0x0054543C 0x00545ACC 0x0054D5B4 0x0054AA34 0x0054A2D4 0x0054AA34 0x0054ADD4>
|
||||
.data 0x00000004
|
||||
.data 0xFF0074EE
|
||||
|
||||
|
||||
|
||||
# Olga Flow Barta Bug Fix
|
||||
|
||||
.label g1_hook_call, <VERS 0x000970E0 0x000973F0 0x00097460 0x00097140 0x000970E0 0x00097160 0x00096FE0>
|
||||
.label g1_hook_loc, <VERS 0x00097124 0x00097434 0x000974A4 0x00097184 0x00097124 0x000971A4 0x00097024>
|
||||
.data g1_hook_call
|
||||
.data 6
|
||||
.address g1_hook_call
|
||||
mov eax, esi
|
||||
cmp al, 19
|
||||
jmp g1_hook_loc
|
||||
g1_hook_call_end:
|
||||
.data g1_hook_loc
|
||||
.deltaof g1_hook_start, g1_hook_end
|
||||
.address g1_hook_loc
|
||||
g1_hook_start:
|
||||
jne g1_hook_skip_replace_value
|
||||
mov al, 2
|
||||
g1_hook_skip_replace_value:
|
||||
cmp eax, [ebx + 0x440] // Original opcode
|
||||
jmp g1_hook_call_end
|
||||
g1_hook_end:
|
||||
|
||||
|
||||
|
||||
# Morfos Frozen Player Bug Fix
|
||||
|
||||
.label g2_hook_call, <VERS 0x0012E257 0x0012E387 0x0012E4E7 0x0012E537 0x0012E567 0x0012E557 0x0012E5A7>
|
||||
.label g2_hook_loc1, <VERS 0x0012E5F4 0x0012E724 0x0012E884 0x0012E8D4 0x0012E904 0x0012E8F4 0x0012E944>
|
||||
.label g2_hook_loc2, <VERS 0x0012E622 0x0012E752 0x0012E8B2 0x0012E902 0x0012E932 0x0012E922 0x0012E972>
|
||||
.data g2_hook_call
|
||||
.data 6
|
||||
.address g2_hook_call
|
||||
call g2_hook_loc1
|
||||
nop
|
||||
.data g2_hook_loc1
|
||||
.deltaof g2_hook_start1, g2_hook_end1
|
||||
.address g2_hook_loc1
|
||||
g2_hook_start1:
|
||||
fld1 st0 // st = [1.0, speed]
|
||||
fld1 st0 // st = [1.0, 1.0, speed]
|
||||
fadd st0, st1 // st = [2.0, 1.0, speed]
|
||||
fdivp st1, st0 // st = [0.5, speed]
|
||||
jmp g2_hook_loc2
|
||||
g2_hook_end1:
|
||||
|
||||
.data g2_hook_loc2
|
||||
.deltaof g2_hook_start2, g2_hook_end2
|
||||
.address g2_hook_loc2
|
||||
g2_hook_start2:
|
||||
test byte [esi + 0x30], 0x20 // If not set, use 1.5; if set, use 0.5
|
||||
jnz g2_hook_entity_is_frozen
|
||||
fld1 st0 // st = [1, 0.5, speed]
|
||||
faddp st1, st0 // st = [1.5, speed]
|
||||
g2_hook_entity_is_frozen:
|
||||
fmulp st1, st0 // st = [((game_flags & 0x20) ? 0.5 : 1.5) * speed]
|
||||
ret
|
||||
g2_hook_end2:
|
||||
|
||||
|
||||
|
||||
# Dropped Mag Color Bug Fix (only needed on beta version)
|
||||
|
||||
.only_versions 4OJB
|
||||
.data 0x001759E6
|
||||
.data 1
|
||||
.binary 12
|
||||
.data 0x00180898
|
||||
.data 1
|
||||
.binary 12
|
||||
.all_versions
|
||||
|
||||
|
||||
|
||||
# Box/Fence Fadeout Bug Fix
|
||||
|
||||
.data <VERS 0x001D229B 0x001D244B 0x001D295B 0x001D241B 0x001D26AB 0x001D243B 0x001D26DB>
|
||||
.data 2
|
||||
nop
|
||||
nop
|
||||
|
||||
.data <VERS 0x001DF7C4 0x001DF924 0x001DFD94 0x001DF964 0x001DFB04 0x001DF984 0x001DFA74>
|
||||
.data 6
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
|
||||
|
||||
|
||||
# TODO: Port the rest of the patches in the GC version of BugFixes:
|
||||
|
||||
# Bulclaw HP Bug Fix
|
||||
# Weapon Attributes Patch
|
||||
# Invalid Items Bug Fix
|
||||
# Item Removal Maxed Stats Bug Fix
|
||||
# Unit Present Bug Fix
|
||||
# Bank Item Stacking Bug Fix
|
||||
# Meseta Drop System Bug Fix
|
||||
# Offline Quests Drop Table Bug Fix
|
||||
# Mag Revival Priority Bug Fix
|
||||
# Mag Revival Challenge & Quest Mode Bug Fix
|
||||
# Reverser Target Lock Bug Fix
|
||||
# Deband/Shifta/Resta Target Bug Fix
|
||||
# Tech Auto Targeting Bug Fix
|
||||
# Enable Trap Animations
|
||||
# Tsumikiri J-Sword special attack + rapid weapon switch bug fix
|
||||
|
||||
# Control Tower: Delbiter Death SFX Bug Fix
|
||||
# Ruins Laser Fence SFX Bug Fix
|
||||
# SFX Cancellation Distance Bug Fix
|
||||
# Foie SFX Pitch Bug Fix
|
||||
# Gifoie SFX Pitch Bug Fix
|
||||
# Rafoie SFX Pitch Bug Fix
|
||||
# Barta SFX Pitch Bug Fix
|
||||
# Gibarta SFX Pitch Bug Fix
|
||||
# Rabarta SFX Pitch Bug Fix
|
||||
# Zonde SFX Pitch Bug Fix
|
||||
# Gizonde SFX Pitch Bug Fix
|
||||
# Razonde SFX Pitch Bug Fix
|
||||
# Grants SFX Pitch Bug Fix
|
||||
# Megid SFX Pitch Bug Fix
|
||||
# Anti SFX Pitch Bug Fix
|
||||
# Present Color Bug Fix
|
||||
# Chat Bubble Window TAB Bug Fix
|
||||
# Chat Log Window LF/Tab Bug Fix
|
||||
# Dark/Hell Special GFX Bug Fix
|
||||
# Devil's and Demon's Special Damage Display Bug Fix
|
||||
# Christmas Trees Bug Fix
|
||||
|
||||
|
||||
.data 0x00000000
|
||||
.data 0x00000000
|
||||
@@ -0,0 +1,115 @@
|
||||
.meta name="CallProtectedHandler"
|
||||
.meta description=""
|
||||
|
||||
entry_ptr:
|
||||
reloc0:
|
||||
.offsetof start
|
||||
|
||||
|
||||
|
||||
.versions 3OJT 3OJ2 3OJ3 3OJ4 3OJ5 3OE0 3OE1 3OE2 3OP0
|
||||
|
||||
start:
|
||||
stwu [r1 - 0x10], r1
|
||||
mflr r0
|
||||
stw [r1 + 0x14], r0
|
||||
stw [r1 + 0x08], r31
|
||||
stw [r1 + 0x0C], r30
|
||||
|
||||
b get_data_addr
|
||||
resume:
|
||||
mflr r31
|
||||
|
||||
lwz r30, [r31]
|
||||
li r0, 1
|
||||
stw [r30], r0
|
||||
|
||||
addi r3, r31, 0x0C
|
||||
lwz r4, [r31 + 8]
|
||||
lwz r0, [r31 + 4]
|
||||
mtctr r0
|
||||
bctrl
|
||||
|
||||
li r0, 0
|
||||
stw [r30], r0
|
||||
|
||||
lwz r30, [r1 + 0x0C]
|
||||
lwz r31, [r1 + 0x08]
|
||||
lwz r0, [r1 + 0x14]
|
||||
mtlr r0
|
||||
addi r1, r1, 0x10
|
||||
blr
|
||||
|
||||
get_data_addr:
|
||||
bl resume
|
||||
# allow_local_client_commands
|
||||
.data <VERS 0x8065F458 0x805C4D58 0x805CF320 0x805D67A0 0x805D6540 0x805C5650 0x805CC630 0x805D5E50 0x805D2090>
|
||||
# RcvPsoData2
|
||||
.data <VERS 0x80236F24 0x801E3B38 0x801E40BC 0x801E4290 0x801E4008 0x801E3F9C 0x801E3F9C 0x801E405C 0x801E4698>
|
||||
|
||||
|
||||
|
||||
.versions 4OJB 4OJD 4OJU 4OED 4OEU 4OPD 4OPU
|
||||
|
||||
start:
|
||||
jmp get_data_addr
|
||||
resume:
|
||||
xchg ebx, [esp]
|
||||
|
||||
mov edx, [ebx]
|
||||
mov dword [edx], 1
|
||||
|
||||
mov edx, [ebx + 4]
|
||||
lea ecx, [ebx + 0x0C]
|
||||
mov eax, [ebx + 8]
|
||||
call edx
|
||||
|
||||
mov edx, [ebx]
|
||||
mov dword [edx], 0
|
||||
|
||||
pop ebx
|
||||
ret
|
||||
|
||||
get_data_addr:
|
||||
call resume
|
||||
.data <VERS 0x0071E8C8 0x0071EF28 0x00726A68 0x00723F68 0x007237E8 0x00723F68 0x007242E8>
|
||||
.data <VERS 0x002DBBA0 0x002DC720 0x002DDFE0 0x002DDB00 0x002DE000 0x002DDB30 0x002DE030>
|
||||
|
||||
|
||||
|
||||
.versions 59NJ 59NL
|
||||
|
||||
start:
|
||||
jmp get_data_addr
|
||||
resume:
|
||||
xchg ebx, [esp]
|
||||
|
||||
mov edx, [ebx]
|
||||
mov dword [edx], 1
|
||||
|
||||
mov edx, [ebx + 4]
|
||||
push dword [ebx + 8]
|
||||
lea ecx, [ebx + 0x0C]
|
||||
push ecx
|
||||
call edx # RcvPsoData2(data, size)
|
||||
add esp, 8
|
||||
|
||||
mov edx, [ebx]
|
||||
mov dword [edx], 0
|
||||
|
||||
pop ebx
|
||||
ret
|
||||
|
||||
get_data_addr:
|
||||
call resume
|
||||
|
||||
.data <VERS 0x00AAC870 0x00AAECF0> # should_allow_protected_commands
|
||||
.data <VERS 0x008015D0 0x00800860> # RcvPsoData2[std](void* data @ [esp + 4], uint32_t size @ [esp + 8])
|
||||
|
||||
|
||||
|
||||
.all_versions
|
||||
|
||||
size:
|
||||
.data 0x00000000
|
||||
data:
|
||||
@@ -1,50 +0,0 @@
|
||||
.meta hide_from_patches_menu
|
||||
.meta name="CallProtectedHandler"
|
||||
.meta description=""
|
||||
|
||||
.versions 3OJT 3OJ2 3OJ3 3OJ4 3OJ5 3OE0 3OE1 3OE2 3OP0
|
||||
|
||||
entry_ptr:
|
||||
reloc0:
|
||||
.offsetof start
|
||||
start:
|
||||
stwu [r1 - 0x10], r1
|
||||
mflr r0
|
||||
stw [r1 + 0x14], r0
|
||||
stw [r1 + 0x08], r31
|
||||
stw [r1 + 0x0C], r30
|
||||
|
||||
b get_data_addr
|
||||
resume:
|
||||
mflr r31
|
||||
|
||||
lwz r30, [r31]
|
||||
li r0, 1
|
||||
stw [r30], r0
|
||||
|
||||
addi r3, r31, 0x0C
|
||||
lwz r4, [r31 + 8]
|
||||
lwz r0, [r31 + 4]
|
||||
mtctr r0
|
||||
bctrl
|
||||
|
||||
li r0, 0
|
||||
stw [r30], r0
|
||||
|
||||
lwz r30, [r1 + 0x0C]
|
||||
lwz r31, [r1 + 0x08]
|
||||
lwz r0, [r1 + 0x14]
|
||||
mtlr r0
|
||||
addi r1, r1, 0x10
|
||||
blr
|
||||
|
||||
get_data_addr:
|
||||
bl resume
|
||||
# allow_local_client_commands
|
||||
.data <VERS 0x8065F458 0x805C4D58 0x805CF320 0x805D67A0 0x805D6540 0x805C5650 0x805CC630 0x805D5E50 0x805D2090>
|
||||
# RcvPsoData2
|
||||
.data <VERS 0x80236F24 0x801E3B38 0x801E40BC 0x801E4290 0x801E4008 0x801E3F9C 0x801E3F9C 0x801E405C 0x801E4698>
|
||||
|
||||
size:
|
||||
.data 0x00000000
|
||||
data:
|
||||
@@ -1,36 +0,0 @@
|
||||
.meta hide_from_patches_menu
|
||||
.meta name="CallProtectedHandler"
|
||||
.meta description=""
|
||||
|
||||
.versions 4OJB 4OJD 4OJU 4OED 4OEU 4OPD 4OPU
|
||||
|
||||
entry_ptr:
|
||||
reloc0:
|
||||
.offsetof start
|
||||
start:
|
||||
jmp get_data_addr
|
||||
resume:
|
||||
xchg ebx, [esp]
|
||||
|
||||
mov edx, [ebx]
|
||||
mov dword [edx], 1
|
||||
|
||||
mov edx, [ebx + 4]
|
||||
lea ecx, [ebx + 0x0C]
|
||||
mov eax, [ebx + 8]
|
||||
call edx
|
||||
|
||||
mov edx, [ebx]
|
||||
mov dword [edx], 0
|
||||
|
||||
pop ebx
|
||||
ret
|
||||
|
||||
get_data_addr:
|
||||
call resume
|
||||
.data <VERS 0x0071E8C8 0x0071EF28 0x00726A68 0x00723F68 0x007237E8 0x00723F68 0x007242E8>
|
||||
.data <VERS 0x002DBBA0 0x002DC720 0x002DDFE0 0x002DDB00 0x002DE000 0x002DDB30 0x002DE030>
|
||||
|
||||
size:
|
||||
.data 0x00000000
|
||||
data:
|
||||
@@ -1,38 +0,0 @@
|
||||
.meta hide_from_patches_menu
|
||||
.meta name="CallProtectedHandler"
|
||||
.meta description=""
|
||||
|
||||
.versions 59NJ 59NL
|
||||
|
||||
entry_ptr:
|
||||
reloc0:
|
||||
.offsetof start
|
||||
start:
|
||||
jmp get_data_addr
|
||||
resume:
|
||||
xchg ebx, [esp]
|
||||
|
||||
mov edx, [ebx]
|
||||
mov dword [edx], 1
|
||||
|
||||
mov edx, [ebx + 4]
|
||||
push dword [ebx + 8]
|
||||
lea ecx, [ebx + 0x0C]
|
||||
push ecx
|
||||
call edx # RcvPsoData2(data, size)
|
||||
add esp, 8
|
||||
|
||||
mov edx, [ebx]
|
||||
mov dword [edx], 0
|
||||
|
||||
pop ebx
|
||||
ret
|
||||
|
||||
get_data_addr:
|
||||
call resume
|
||||
|
||||
.data <VERS 0x00AAC870 0x00AAECF0> # should_allow_protected_commands
|
||||
.data <VERS 0x008015D0 0x00800860> # RcvPsoData2[std](void* data @ [esp + 4], uint32_t size @ [esp + 8])
|
||||
size:
|
||||
.data 0x00000000
|
||||
data:
|
||||
+5
-3
@@ -1,16 +1,18 @@
|
||||
.meta name="Chat"
|
||||
.meta description="Enables extended\nWord Select and\nstops the Log\nWindow from\nscrolling with L+R"
|
||||
# Original codes by Ralf @ GC-Forever and Aleron Ives
|
||||
# https://www.gc-forever.com/forums/viewtopic.php?t=2050
|
||||
# https://www.gc-forever.com/forums/viewtopic.php?t=2049
|
||||
|
||||
.meta visibility="all"
|
||||
.meta name="Chat"
|
||||
.meta description="Enables extended\nWord Select and\nstops the Log\nWindow from\nscrolling with L+R"
|
||||
|
||||
.versions 3OJ2 3OJ3 3OJ4 3OJ5 3OE0 3OE1 3OE2 3OP0 3SJT 3SJ0 3SE0 3SP0
|
||||
|
||||
entry_ptr:
|
||||
reloc0:
|
||||
.offsetof start
|
||||
start:
|
||||
.include WriteCodeBlocksGC
|
||||
.include WriteCodeBlocks
|
||||
|
||||
# Extended Word Select Menu (PSO PCv2 Style)
|
||||
.data <VERS 0x8034445C 0x803457AC 0x80346CCC 0x80346A80 0x8034525C 0x803452A0 0x80346E4C 0x8034627C 0x801D9B30 0x801C7CFC 0x801C7D88 0x801C83FC>
|
||||
+5
-3
@@ -1,16 +1,18 @@
|
||||
.meta name="Common bank"
|
||||
.meta description="Hold L and open\nthe bank to use a\ncommon bank stored\nin temp character\n3's data"
|
||||
# Original code by Ralf @ GC-Forever ("Common Bank (Hold L And Open Bank)")
|
||||
# https://www.gc-forever.com/forums/viewtopic.php?t=2050
|
||||
# https://www.gc-forever.com/forums/viewtopic.php?t=2049
|
||||
|
||||
.meta visibility="all"
|
||||
.meta name="Common bank"
|
||||
.meta description="Hold L and open\nthe bank to use a\ncommon bank stored\nin temp character\n3's data"
|
||||
|
||||
.versions 3OJ2 3OJ3 3OJ4 3OJ5 3OE0 3OE1 3OE2 3OP0
|
||||
|
||||
entry_ptr:
|
||||
reloc0:
|
||||
.offsetof start
|
||||
start:
|
||||
.include WriteCodeBlocksGC
|
||||
.include WriteCodeBlocks
|
||||
|
||||
.data 0x8000BAB4
|
||||
.deltaof hook1, hooks_end
|
||||
+56
-4
@@ -1,16 +1,21 @@
|
||||
.meta name="DC targets"
|
||||
.meta description="Changes the target\nreticle colors to\nthose used on the\nDreamcast"
|
||||
# Original code by Ralf @ GC-Forever and Aleron Ives
|
||||
# https://www.gc-forever.com/forums/viewtopic.php?t=2050
|
||||
# https://www.gc-forever.com/forums/viewtopic.php?t=2049
|
||||
|
||||
.versions 3OJ2 3OJ3 3OJ4 3OJ5 3OE0 3OE1 3OE2 3OP0
|
||||
.meta visibility="all"
|
||||
.meta name="DC targets"
|
||||
.meta description="Changes the target\nreticle colors to\nthose used on the\nDreamcast"
|
||||
|
||||
|
||||
entry_ptr:
|
||||
reloc0:
|
||||
.offsetof start
|
||||
start:
|
||||
.include WriteCodeBlocksGC
|
||||
.include WriteCodeBlocks
|
||||
|
||||
|
||||
|
||||
.versions 3OJ2 3OJ3 3OJ4 3OJ5 3OE0 3OE1 3OE2 3OP0
|
||||
|
||||
.data <VERS 0x802AB3FC 0x802AC2A4 0x802AD3D0 0x802AD184 0x802ABDB8 0x802ABDFC 0x802AD338 0x802ACACC>
|
||||
.data 0x00000004
|
||||
@@ -54,5 +59,52 @@ start:
|
||||
.float 0.1
|
||||
.float 0.1
|
||||
|
||||
|
||||
|
||||
.versions 4OED 4OEU 4OJB 4OJD 4OJU 4OPD 4OPU
|
||||
|
||||
.data <VERS 0x0025BD09 0x0025BE29 0x0025B889 0x0025BC39 0x0025BFB9 0x0025BD29 0x0025BE59>
|
||||
.data 0x00000004
|
||||
.data 0x00FF0000
|
||||
|
||||
.data <VERS 0x0025BD17 0x0025BE37 0x0025B897 0x0025BC47 0x0025BFC7 0x0025BD37 0x0025BE67>
|
||||
.data 0x00000004
|
||||
.data 0x000000FF
|
||||
|
||||
.data <VERS 0x0025BD25 0x0025BE45 0x0025B8A5 0x0025BC55 0x0025BFD5 0x0025BD45 0x0025BE75>
|
||||
.data 0x00000004
|
||||
.data 0x00FFFF00
|
||||
|
||||
.data <VERS 0x005427A0 0x00542040 0x0053D788 0x0053DE00 0x00545320 0x005427A0 0x00542B40>
|
||||
.data 0x00000060
|
||||
.data 0x3F800000
|
||||
.data 0x3F800000
|
||||
.data 0x00000000
|
||||
.data 0x00000000
|
||||
.data 0x3F800000
|
||||
.data 0x3F800000
|
||||
.data 0x00000000
|
||||
.data 0x00000000
|
||||
.data 0x3F800000
|
||||
.data 0x3F800000
|
||||
.data 0x3F800000
|
||||
.data 0x00000000
|
||||
.data 0x3F800000
|
||||
.data 0x00000000
|
||||
.data 0x00000000
|
||||
.data 0x3F800000
|
||||
.data 0x3F800000
|
||||
.data 0x3ECCCCCD
|
||||
.data 0x3DCCCCCD
|
||||
.data 0x3DCCCCCD
|
||||
.data 0x3F800000
|
||||
.data 0x00000000
|
||||
.data 0x00000000
|
||||
.data 0x00000000
|
||||
|
||||
|
||||
|
||||
.all_versions
|
||||
|
||||
.data 0x00000000
|
||||
.data 0x00000000
|
||||
@@ -1,52 +0,0 @@
|
||||
.meta hide_from_patches_menu
|
||||
.meta name="CreateObject"
|
||||
.meta description=""
|
||||
|
||||
entry_ptr:
|
||||
reloc0:
|
||||
.offsetof start
|
||||
start:
|
||||
mflr r0
|
||||
b get_data
|
||||
get_data_ret:
|
||||
mflr r3
|
||||
mtlr r0
|
||||
lwz r0, [r3]
|
||||
mtctr r0
|
||||
addi r3, r3, 4
|
||||
bctr
|
||||
|
||||
get_data:
|
||||
bl get_data_ret
|
||||
.data 0x8020C158 # construct_dat_object_from_args
|
||||
base_type_high:
|
||||
.data 0xFFFF0000 # base_type, set_flags
|
||||
floor_low:
|
||||
.data 0x0000FFFF # index, floor
|
||||
.data 0x00000000 # entity_id, group
|
||||
.data 0x00000000 # room, unknown_a3
|
||||
pos_x:
|
||||
.float 0.0 # pos.x
|
||||
pos_y:
|
||||
.float 0.0 # pos.y
|
||||
pos_z:
|
||||
.float 0.0 # pos.z
|
||||
angle_x:
|
||||
.data 0x00000000 # angle.x
|
||||
angle_y:
|
||||
.data 0x00000000 # angle.y
|
||||
angle_z:
|
||||
.data 0x00000000 # angle.z
|
||||
param1:
|
||||
.float 0.0 # param1
|
||||
param2:
|
||||
.float 0.0 # param2
|
||||
param3:
|
||||
.float 0.0 # param3
|
||||
param4:
|
||||
.data 0 # param4
|
||||
param5:
|
||||
.data 0 # param5
|
||||
param6:
|
||||
.data 0 # param6
|
||||
.data 0 # unused_obj_ptr
|
||||
+3
-2
@@ -1,7 +1,8 @@
|
||||
.meta hide_from_patches_menu
|
||||
.meta name="CreateObject"
|
||||
.meta description=""
|
||||
|
||||
.versions 3OE1 3SE0
|
||||
|
||||
entry_ptr:
|
||||
reloc0:
|
||||
.offsetof start
|
||||
@@ -18,7 +19,7 @@ get_data_ret:
|
||||
|
||||
get_data:
|
||||
bl get_data_ret
|
||||
.data 0x80056D6C # construct_dat_object_from_args
|
||||
.data <VERS 0x8020C158 0x80056D6C> # construct_dat_object_from_args
|
||||
base_type_high:
|
||||
.data 0xFFFF0000 # base_type, set_flags
|
||||
floor_low:
|
||||
+3
-2
@@ -1,12 +1,13 @@
|
||||
.meta hide_from_patches_menu
|
||||
.meta name="Player flags"
|
||||
.meta description=""
|
||||
|
||||
.versions 3OE1
|
||||
|
||||
entry_ptr:
|
||||
reloc0:
|
||||
.offsetof start
|
||||
start:
|
||||
.include WriteCodeBlocksGC
|
||||
.include WriteCodeBlocks
|
||||
|
||||
.label check_controller_button, 0x801A6C68 # [std](ControllerState* st, uint32_t flags) -> bool
|
||||
.label TFogCtrl_change_fog, 0x800FB10C # [std](TFogCtrl* this, uint32_t fog_num, uint32_t instant_transition) -> void
|
||||
+2
-1
@@ -1,8 +1,9 @@
|
||||
.meta hide_from_patches_menu
|
||||
.meta name="MovementDebug"
|
||||
.meta description=""
|
||||
.meta show_return_value
|
||||
|
||||
.versions 3OE1
|
||||
|
||||
# Usage examples:
|
||||
# Read movement data 09 fparam1:
|
||||
# $patch MovementDebug e=0x09 f=1 r=1
|
||||
+3
-2
@@ -1,12 +1,13 @@
|
||||
.meta hide_from_patches_menu
|
||||
.meta name="Player flags"
|
||||
.meta description=""
|
||||
|
||||
.versions 3OE1
|
||||
|
||||
entry_ptr:
|
||||
reloc0:
|
||||
.offsetof start
|
||||
start:
|
||||
.include WriteCodeBlocksGC
|
||||
.include WriteCodeBlocks
|
||||
|
||||
.label TObjPlayer_for_client_id, 0x801BA59C # [std](uint32_t client_id)
|
||||
.label render_debug_printf, 0x803D4E3C # [std](uint32_t coords, const char* fmt, ...);
|
||||
+61
-5
@@ -1,17 +1,69 @@
|
||||
.meta name="Decoction"
|
||||
.meta description="Makes the Decoction\nitem reset your\nmaterial usage"
|
||||
# Original code by Ralf @ GC-Forever and Aleron Ives
|
||||
# https://www.gc-forever.com/forums/viewtopic.php?t=2050
|
||||
# https://www.gc-forever.com/forums/viewtopic.php?t=2049
|
||||
# Xbox port by fuzziqersoftware
|
||||
|
||||
.versions 4OJB 4OJD 4OJU 4OED 4OEU 4OPD 4OPU
|
||||
.meta visibility="all"
|
||||
.meta name="Decoction"
|
||||
.meta description="Makes the Decoction\nitem reset your\nmaterial usage"
|
||||
|
||||
|
||||
entry_ptr:
|
||||
reloc0:
|
||||
.offsetof start
|
||||
start:
|
||||
.include WriteCodeBlocksXB
|
||||
.include WriteCodeBlocks
|
||||
|
||||
|
||||
|
||||
.versions 3OJ2 3OJ3 3OJ4 3OJ5 3OE0 3OE1 3OE2 3OP0
|
||||
|
||||
.data <VERS 0x80350740 0x80351B44 0x803530A0 0x80352E54 0x803515F4 0x80351638 0x80353220 0x80352614>
|
||||
.data 0x00000098
|
||||
.address <VERS 0x80350740 0x80351B44 0x803530A0 0x80352E54 0x803515F4 0x80351638 0x80353220 0x80352614>
|
||||
lbz r0, [r3 + 0xEE]
|
||||
cmplwi r0, 11
|
||||
bne +0x144
|
||||
lwz r31, [r3 + 0xF0]
|
||||
li r0, 0
|
||||
nop
|
||||
li r4, 0x0374
|
||||
li r5, 0x0D38
|
||||
bl +0x58
|
||||
li r5, 0x0D3A
|
||||
bl +0x50
|
||||
li r5, 0x0D3C
|
||||
bl +0x48
|
||||
li r5, 0x0D40
|
||||
bl +0x40
|
||||
li r5, 0x0D44
|
||||
bl +0x38
|
||||
mr r3, r31
|
||||
.data <VERS 0x4BE656A1 0x4BE646F1 0x4BE654CD 0x4BE634AD 0x4BE64BD9 0x4BE64B95 0x4BE63145 0x4BE6420D>
|
||||
lhz r0, [r31 + 0x032C]
|
||||
lhz r3, [r31 + 0x02B8]
|
||||
cmpl r0, r3
|
||||
ble +0x08
|
||||
sth [r31 + 0x032C], r3
|
||||
lhz r0, [r31 + 0x032E]
|
||||
lhz r3, [r31 + 0x02BA]
|
||||
cmpl r0, r3
|
||||
ble +0x08
|
||||
sth [r31 + 0x032E], r3
|
||||
b +0xD8
|
||||
lbzx r6, [r31 + r4]
|
||||
lhzx r7, [r31 + r5]
|
||||
rlwinm r6, r6, 1, 0, 30
|
||||
subf r7, r6, r7
|
||||
sthx [r31 + r5], r7
|
||||
stbx [r31 + r4], r0
|
||||
addi r4, r4, 0x0001
|
||||
blr
|
||||
|
||||
|
||||
|
||||
.versions 4OJB 4OJD 4OJU 4OED 4OEU 4OPD 4OPU
|
||||
|
||||
.data <VERS 0x00184160 0x00184350 0x00184400 0x00184340 0x00184310 0x00184360 0x001842D0>
|
||||
.deltaof code_start, code_end
|
||||
.address <VERS 0x00184160 0x00184350 0x00184400 0x00184340 0x00184310 0x00184360 0x001842D0>
|
||||
@@ -63,7 +115,11 @@ next_stat:
|
||||
pop esi
|
||||
skip_all:
|
||||
ret
|
||||
|
||||
code_end:
|
||||
|
||||
|
||||
|
||||
.all_versions
|
||||
|
||||
.data 0x00000000
|
||||
.data 0x00000000
|
||||
@@ -1,58 +0,0 @@
|
||||
.meta name="Decoction"
|
||||
.meta description="Makes the Decoction\nitem reset your\nmaterial usage"
|
||||
# Original code by Ralf @ GC-Forever and Aleron Ives
|
||||
# https://www.gc-forever.com/forums/viewtopic.php?t=2050
|
||||
# https://www.gc-forever.com/forums/viewtopic.php?t=2049
|
||||
|
||||
.versions 3OJ2 3OJ3 3OJ4 3OJ5 3OE0 3OE1 3OE2 3OP0
|
||||
|
||||
entry_ptr:
|
||||
reloc0:
|
||||
.offsetof start
|
||||
start:
|
||||
.include WriteCodeBlocksGC
|
||||
|
||||
.data <VERS 0x80350740 0x80351B44 0x803530A0 0x80352E54 0x803515F4 0x80351638 0x80353220 0x80352614>
|
||||
.data 0x00000098
|
||||
.address <VERS 0x80350740 0x80351B44 0x803530A0 0x80352E54 0x803515F4 0x80351638 0x80353220 0x80352614>
|
||||
lbz r0, [r3 + 0xEE]
|
||||
cmplwi r0, 11
|
||||
bne +0x144
|
||||
lwz r31, [r3 + 0xF0]
|
||||
li r0, 0
|
||||
nop
|
||||
li r4, 0x0374
|
||||
li r5, 0x0D38
|
||||
bl +0x58
|
||||
li r5, 0x0D3A
|
||||
bl +0x50
|
||||
li r5, 0x0D3C
|
||||
bl +0x48
|
||||
li r5, 0x0D40
|
||||
bl +0x40
|
||||
li r5, 0x0D44
|
||||
bl +0x38
|
||||
mr r3, r31
|
||||
.data <VERS 0x4BE656A1 0x4BE646F1 0x4BE654CD 0x4BE634AD 0x4BE64BD9 0x4BE64B95 0x4BE63145 0x4BE6420D>
|
||||
lhz r0, [r31 + 0x032C]
|
||||
lhz r3, [r31 + 0x02B8]
|
||||
cmpl r0, r3
|
||||
ble +0x08
|
||||
sth [r31 + 0x032C], r3
|
||||
lhz r0, [r31 + 0x032E]
|
||||
lhz r3, [r31 + 0x02BA]
|
||||
cmpl r0, r3
|
||||
ble +0x08
|
||||
sth [r31 + 0x032E], r3
|
||||
b +0xD8
|
||||
lbzx r6, [r31 + r4]
|
||||
lhzx r7, [r31 + r5]
|
||||
rlwinm r6, r6, 1, 0, 30
|
||||
subf r7, r6, r7
|
||||
sthx [r31 + r5], r7
|
||||
stbx [r31 + r4], r0
|
||||
addi r4, r4, 0x0001
|
||||
blr
|
||||
|
||||
.data 0x00000000
|
||||
.data 0x00000000
|
||||
@@ -0,0 +1,47 @@
|
||||
.meta visibility="all"
|
||||
.meta name="Disable idle DC"
|
||||
.meta description="Disables the idle\ndisconnect timeout"
|
||||
|
||||
entry_ptr:
|
||||
reloc0:
|
||||
.offsetof start
|
||||
start:
|
||||
.include WriteCodeBlocks
|
||||
|
||||
|
||||
|
||||
.versions 1OJ3 1OJ4 1OJF 1OEF 1OPF 2OJ4 2OJ5 2OJF 2OEF 2OPF
|
||||
.align 4
|
||||
.data <VERS 0x8C01A454 0x8C01A6D0 0x8C01A414 0x8C01A6C8 0x8C01A6DC 0x8C01B6A4 0x8C01B6A4 0x8C01B684 0x8C01B6A4 0x8C01B6A8>
|
||||
.data 0x00000002
|
||||
mov r0, 0
|
||||
.align 4
|
||||
|
||||
|
||||
|
||||
.versions 3OJ2 3OJ3 3OJ4 3OJ5 3OE0 3OE1 3OE2 3OP0 3SJT 3SJ0 3SE0 3SP0
|
||||
.data <VERS 0x80134D3C 0x80134FA0 0x80135108 0x80135040 0x80134FE0 0x80134FE0 0x80135050 0x801352D0 0x80092C78 0x8009242C 0x80092380 0x80092588>
|
||||
.data 0x00000004
|
||||
li r3, 0
|
||||
|
||||
|
||||
|
||||
.versions 4OJB 4OJD 4OJU 4OED 4OEU 4OPD 4OPU
|
||||
.data <VERS 0x002C0AEE 0x002C167E 0x002C2BEE 0x002C272E 0x002C291E 0x002C275E 0x002C2A7E>
|
||||
.data 0x00000004
|
||||
xor ecx, ecx
|
||||
jmp +3
|
||||
|
||||
|
||||
|
||||
.versions 59NJ 59NL
|
||||
.data <VERS 0x007A1233 0x007A03F7>
|
||||
.data 0x00000005
|
||||
mov eax, 0
|
||||
|
||||
|
||||
|
||||
.all_versions
|
||||
|
||||
.data 0x00000000
|
||||
.data 0x00000000
|
||||
@@ -1,19 +0,0 @@
|
||||
.meta name="Disable idle DC"
|
||||
.meta description="Disables the idle\ndisconnect timeout"
|
||||
|
||||
.versions 1OJ3 1OJ4 1OJF 1OEF 1OPF 2OJ4 2OJ5 2OJF 2OEF 2OPF
|
||||
|
||||
entry_ptr:
|
||||
reloc0:
|
||||
.offsetof start
|
||||
start:
|
||||
.include WriteCodeBlocksDC
|
||||
|
||||
.align 4
|
||||
.data <VERS 0x8C01A454 0x8C01A6D0 0x8C01A414 0x8C01A6C8 0x8C01A6DC 0x8C01B6A4 0x8C01B6A4 0x8C01B684 0x8C01B6A4 0x8C01B6A8>
|
||||
.data 0x00000002
|
||||
mov r0, 0
|
||||
|
||||
.align 4
|
||||
.data 0x00000000
|
||||
.data 0x00000000
|
||||
@@ -1,17 +0,0 @@
|
||||
.meta name="Disable idle DC"
|
||||
.meta description="Disables the idle\ndisconnect timeout"
|
||||
|
||||
.versions 3OJ2 3OJ3 3OJ4 3OJ5 3OE0 3OE1 3OE2 3OP0 3SJT 3SJ0 3SE0 3SP0
|
||||
|
||||
entry_ptr:
|
||||
reloc0:
|
||||
.offsetof start
|
||||
start:
|
||||
.include WriteCodeBlocksGC
|
||||
|
||||
.data <VERS 0x80134D3C 0x80134FA0 0x80135108 0x80135040 0x80134FE0 0x80134FE0 0x80135050 0x801352D0 0x80092C78 0x8009242C 0x80092380 0x80092588>
|
||||
.data 0x00000004
|
||||
li r3, 0
|
||||
|
||||
.data 0x00000000
|
||||
.data 0x00000000
|
||||
@@ -1,18 +0,0 @@
|
||||
.meta name="Disable idle DC"
|
||||
.meta description="Disables the idle\ndisconnect timeout"
|
||||
|
||||
.versions 4OJB 4OJD 4OJU 4OED 4OEU 4OPD 4OPU
|
||||
|
||||
entry_ptr:
|
||||
reloc0:
|
||||
.offsetof start
|
||||
start:
|
||||
.include WriteCodeBlocksXB
|
||||
|
||||
.data <VERS 0x002C0AEE 0x002C167E 0x002C2BEE 0x002C272E 0x002C291E 0x002C275E 0x002C2A7E>
|
||||
.data 0x00000004
|
||||
xor ecx, ecx
|
||||
jmp +3
|
||||
|
||||
.data 0x00000000
|
||||
.data 0x00000000
|
||||
@@ -1,17 +0,0 @@
|
||||
.meta name="Disable idle DC"
|
||||
.meta description="Disables the idle\ndisconnect timeout"
|
||||
|
||||
.versions 59NJ 59NL
|
||||
|
||||
entry_ptr:
|
||||
reloc0:
|
||||
.offsetof start
|
||||
start:
|
||||
.include WriteCodeBlocksBB
|
||||
|
||||
.data <VERS 0x007A1233 0x007A03F7>
|
||||
.data 0x00000005
|
||||
mov eax, 0
|
||||
|
||||
.data 0x00000000
|
||||
.data 0x00000000
|
||||
@@ -0,0 +1,367 @@
|
||||
# Original code by Ralf @ GC-Forever and Aleron Ives
|
||||
# https://www.gc-forever.com/forums/viewtopic.php?t=2050
|
||||
# https://www.gc-forever.com/forums/viewtopic.php?t=2049
|
||||
# Xbox port by fuzziqersoftware
|
||||
|
||||
# BB notes:
|
||||
# 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 visibility="all"
|
||||
.meta name="Draw Distance"
|
||||
.meta description="Extends the draw\ndistance of many\nobjects"
|
||||
|
||||
entry_ptr:
|
||||
reloc0:
|
||||
.offsetof start
|
||||
|
||||
|
||||
|
||||
.versions 3OJ2 3OJ3 3OJ4 3OJ5 3OE0 3OE1 3OE2 3OP0
|
||||
|
||||
start:
|
||||
.include WriteCodeBlocks
|
||||
|
||||
.data 0x8000DFA0
|
||||
.deltaof hook_start, hook_end
|
||||
.address 0x8000DFA0
|
||||
hook_start:
|
||||
hook1:
|
||||
lfs f30, [r2 - <VERS 0x3E08 0x3E08 0x3E08 0x3E08 0x3E00 0x3E00 0x3E00 0x3E00>]
|
||||
fmuls f30, f30, f1
|
||||
blr
|
||||
hook2:
|
||||
lfs f2, [r2 - <VERS 0x3E08 0x3E08 0x3E08 0x3E08 0x3E00 0x3E00 0x3E00 0x3E00>]
|
||||
lfs f0, [r30 + 0x001C]
|
||||
fmuls f0, f0, f2
|
||||
blr
|
||||
hook3:
|
||||
lfs f28, [r2 - <VERS 0x3E08 0x3E08 0x3E08 0x3E08 0x3E00 0x3E00 0x3E00 0x3E00>]
|
||||
fmuls f28, f28, f2
|
||||
blr
|
||||
hook4:
|
||||
lfs f0, [r2 - <VERS 0x3E08 0x3E08 0x3E08 0x3E08 0x3E00 0x3E00 0x3E00 0x3E00>]
|
||||
lfs f1, [r3 + 0x000C]
|
||||
fmuls f0, f0, f1
|
||||
stfs [r3 + 0x000C], f0
|
||||
lis r3, <VERS 0x804C 0x804C 0x804D 0x804D 0x804C 0x804C 0x804D 0x804D>
|
||||
blr
|
||||
hook_end:
|
||||
|
||||
.data <VERS 0x801008E8 0x80100AD0 0x80100B74 0x80100A50 0x80100B8C 0x80100B8C 0x80100A60 0x80100C50>
|
||||
.data 0x00000004
|
||||
.address <VERS 0x801008E8 0x80100AD0 0x80100B74 0x80100A50 0x80100B8C 0x80100B8C 0x80100A60 0x80100C50>
|
||||
bl hook1
|
||||
|
||||
.data <VERS 0x8015671C 0x80156AD0 0x80156C34 0x80156B94 0x80156AD8 0x80156AD8 0x80156BF8 0x801570BC>
|
||||
.data 0x00000004
|
||||
.address <VERS 0x8015671C 0x80156AD0 0x80156C34 0x80156B94 0x80156AD8 0x80156AD8 0x80156BF8 0x801570BC>
|
||||
bl hook2
|
||||
|
||||
.data <VERS 0x801A1C64 0x801A203C 0x801A21A0 0x801A2100 0x801A2040 0x801A2040 0x801A2164 0x801A2628>
|
||||
.data 0x00000004
|
||||
.address <VERS 0x801A1C64 0x801A203C 0x801A21A0 0x801A2100 0x801A2040 0x801A2040 0x801A2164 0x801A2628>
|
||||
bl hook3
|
||||
|
||||
.data <VERS 0x801A1E64 0x801A223C 0x801A23A0 0x801A2300 0x801A2240 0x801A2240 0x801A2364 0x801A2828>
|
||||
.data 0x00000004
|
||||
.address <VERS 0x801A1E64 0x801A223C 0x801A23A0 0x801A2300 0x801A2240 0x801A2240 0x801A2364 0x801A2828>
|
||||
bl hook1
|
||||
|
||||
.data <VERS 0x80205044 0x802058B8 0x80206640 0x802063F4 0x80205840 0x80205840 0x80206728 0x80206124>
|
||||
.data 0x00000004
|
||||
.address <VERS 0x80205044 0x802058B8 0x80206640 0x802063F4 0x80205840 0x80205840 0x80206728 0x80206124>
|
||||
bl hook4
|
||||
|
||||
.data <VERS 0x802057E8 0x8020605C 0x80206DE4 0x80206B98 0x80205FE4 0x80205FE4 0x80206ECC 0x802068C8>
|
||||
.data 0x00000004
|
||||
.address <VERS 0x802057E8 0x8020605C 0x80206DE4 0x80206B98 0x80205FE4 0x80205FE4 0x80206ECC 0x802068C8>
|
||||
bl hook4
|
||||
|
||||
.data <VERS 0x805C83A8 0x805D29A8 0x805D9E48 0x805D9BE8 0x805C8CB0 0x805CFCD0 0x805D94F0 0x805D5730>
|
||||
.data 0x00000004
|
||||
.float 90000
|
||||
|
||||
.data <VERS 0x805C9254 0x805D3854 0x805DACF4 0x805DAA94 0x805C9B5C 0x805D0B7C 0x805DA39C 0x805D65DC>
|
||||
.data 0x00000004
|
||||
.float 62500
|
||||
|
||||
.data <VERS 0x805C987C 0x805D3E7C 0x805DB31C 0x805DB0BC 0x805CA184 0x805D11A4 0x805DA9C4 0x805D6C04>
|
||||
.data 0x00000004
|
||||
.float 640000
|
||||
|
||||
.data <VERS 0x805CA708 0x805D4D08 0x805DC1A8 0x805DBF48 0x805CB010 0x805D2030 0x805DB850 0x805D7A90>
|
||||
.data 0x00000004
|
||||
.float 90000
|
||||
|
||||
.data <VERS 0x805CAC98 0x805D5298 0x805DC738 0x805DC4D8 0x805CB5A0 0x805D25C0 0x805DBDE0 0x805D8020>
|
||||
.data 0x00000004
|
||||
.float 1400
|
||||
|
||||
.data 0x00000000
|
||||
.data 0x00000000
|
||||
|
||||
|
||||
|
||||
.versions 4OED 4OEU 4OJB 4OJD 4OJU 4OPD 4OPU
|
||||
|
||||
start:
|
||||
.include WriteCodeBlocks
|
||||
|
||||
.data <VERS 0x001737C2 0x001737D2 0x00173692 0x00173782 0x00173862 0x001737E2 0x00173792> # From 3OE1:80100B8C
|
||||
.deltaof p1_1s, p1_1e
|
||||
.address <VERS 0x001737C2 0x001737D2 0x00173692 0x00173782 0x00173862 0x001737E2 0x00173792> # From 3OE1:80100B8C
|
||||
p1_1s:
|
||||
call p1_2s
|
||||
nop
|
||||
p1_1e:
|
||||
.data <VERS 0x00173A42 0x00173A52 0x00173912 0x00173A02 0x00173AE2 0x00173A62 0x00173A12>
|
||||
.deltaof p1_2s, p1_2e
|
||||
.address <VERS 0x00173A42 0x00173A52 0x00173912 0x00173A02 0x00173AE2 0x00173A62 0x00173A12>
|
||||
p1_2s:
|
||||
fld st0, dword [esp + 0x1C]
|
||||
fadd st0, st0
|
||||
fchs st0
|
||||
ret
|
||||
p1_2e:
|
||||
|
||||
.data <VERS 0x001A3DEF 0x001A3EEF 0x001A3BBF 0x001A3DBF 0x001A3FDF 0x001A3E0F 0x001A3ECF> # From 3OE1:80156AD8
|
||||
.deltaof p2_1s, p2_1e
|
||||
.address <VERS 0x001A3DEF 0x001A3EEF 0x001A3BBF 0x001A3DBF 0x001A3FDF 0x001A3E0F 0x001A3ECF> # From 3OE1:80156AD8
|
||||
p2_1s:
|
||||
call p2_2s
|
||||
p2_1e:
|
||||
.data <VERS 0x001A3E38 0x001A3F38 0x001A3C08 0x001A3E08 0x001A4028 0x001A3E58 0x001A3F18>
|
||||
.deltaof p2_2s, p2_2e
|
||||
.address <VERS 0x001A3E38 0x001A3F38 0x001A3C08 0x001A3E08 0x001A4028 0x001A3E58 0x001A3F18>
|
||||
p2_2s:
|
||||
fld st0, dword [ecx + 0x1C]
|
||||
fadd st0, st0
|
||||
fld st0, st1
|
||||
ret
|
||||
p2_2e:
|
||||
|
||||
.data <VERS 0x002D2DC8 0x002D3148 0x002D0E68 0x002D1A28 0x002D32F8 0x002D2DF8 0x002D31C8> # From 3OE1:801A2040
|
||||
.deltaof p3_1s, p3_1e
|
||||
.address <VERS 0x002D2DC8 0x002D3148 0x002D0E68 0x002D1A28 0x002D32F8 0x002D2DF8 0x002D31C8> # From 3OE1:801A2040
|
||||
p3_1s:
|
||||
call p3_2s
|
||||
nop
|
||||
p3_1e:
|
||||
.data <VERS 0x002D2EA7 0x002D3227 0x002D0F47 0x002D1B07 0x002D33D7 0x002D2ED7 0x002D32A7>
|
||||
.deltaof p3_2s, p3_2e
|
||||
.address <VERS 0x002D2EA7 0x002D3227 0x002D0F47 0x002D1B07 0x002D33D7 0x002D2ED7 0x002D32A7>
|
||||
p3_2s:
|
||||
fld st0, dword [esp + 0x24]
|
||||
fadd st0, st0
|
||||
fchs st0
|
||||
ret
|
||||
p3_2e:
|
||||
|
||||
.data <VERS 0x00156AC8 0x002D32A8 0x001569E8 0x00156A78 0x00156AB8 0x00156AE8 0x002D3328> # From 3OE1:801A2240
|
||||
.deltaof p4_1s, p4_1e
|
||||
.address <VERS 0x00156AC8 0x002D32A8 0x001569E8 0x00156A78 0x00156AB8 0x00156AE8 0x002D3328> # From 3OE1:801A2240
|
||||
p4_1s:
|
||||
call p4_2s
|
||||
nop
|
||||
p4_1e:
|
||||
.data <VERS 0x00156C44 0x002D33B4 0x00156B64 0x00156BF4 0x00156C34 0x00156C64 0x002D3434>
|
||||
.deltaof p4_2s, p4_2e
|
||||
.address <VERS 0x00156C44 0x002D33B4 0x00156B64 0x00156BF4 0x00156C34 0x00156C64 0x002D3434>
|
||||
p4_2s:
|
||||
fld st0, dword [esp + 0x28]
|
||||
fadd st0, st0
|
||||
fchs st0
|
||||
ret
|
||||
p4_2e:
|
||||
|
||||
.data <VERS 0x002E2B93 0x002E2E8C 0x002E0C33 0x002E17B3 0x002E2E6C 0x002E2BC3 0x002E2EBC> # From 3OE1:80205840
|
||||
.deltaof p5_1s, p5_1e
|
||||
.address <VERS 0x002E2B93 0x002E2E8C 0x002E0C33 0x002E17B3 0x002E2E6C 0x002E2BC3 0x002E2EBC> # From 3OE1:80205840
|
||||
p5_1s:
|
||||
call p5_3s
|
||||
p5_1e:
|
||||
.data <VERS 0x002E1FD1 0x002E2404 0x002E0071 0x002E0BF1 0x002E23E4 0x002E2001 0x002E2434> # From 3OE1:80205FE4
|
||||
.deltaof p5_2s, p5_2e
|
||||
.address <VERS 0x002E1FD1 0x002E2404 0x002E0071 0x002E0BF1 0x002E23E4 0x002E2001 0x002E2434> # From 3OE1:80205FE4
|
||||
p5_2s:
|
||||
call p5_3s
|
||||
p5_2e:
|
||||
.data <VERS 0x002E2C82 0x002E2FD1 0x002E0D22 0x002E18A2 0x002E2FB1 0x002E2CB2 0x002E3001>
|
||||
.deltaof p5_3s, p5_3e
|
||||
.address <VERS 0x002E2C82 0x002E2FD1 0x002E0D22 0x002E18A2 0x002E2FB1 0x002E2CB2 0x002E3001>
|
||||
p5_3s:
|
||||
fld st0, dword [eax + 0x0C]
|
||||
fadd st0, st0
|
||||
fstp dword [eax + 0x0C], st0
|
||||
mov eax, [<VERS 0x0053A9CC 0x0053A26C 0x00535BAC 0x0053622C 0x0053D54C 0x0053A9CC 0x0053AD6C>]
|
||||
ret
|
||||
p5_3e:
|
||||
|
||||
.data <VERS 0x004920A0 0x00491940 0x0048D4F0 0x0048DC88 0x00494C30 0x004920A8 0x00492440> # From 3OE1:805CFCD0
|
||||
.data 0x00000004
|
||||
.data 0x47AFC800
|
||||
|
||||
.data <VERS 0x0042D0A0 0x0042C940 0x00428DC0 0x00429130 0x0042C940 0x0042D0C0 0x0042D450> # From 3OE1:805D0B7C
|
||||
.data 0x00000004
|
||||
.data 0x437A0000
|
||||
|
||||
.data <VERS 0x0049222C 0x00491ACC 0x0048D67C 0x0048DE14 0x00494DBC 0x00492234 0x004925CC> # From 3OE1:805D11A4
|
||||
.data 0x00000004
|
||||
.data 0x491C4000
|
||||
|
||||
.data <VERS 0x0042B838 0x0042B0D8 0x00427558 0x004278C8 0x0042B0D8 0x0042B858 0x0042BBE8> # From 3OE1:805D2030
|
||||
.data 0x00000004
|
||||
.data 0x47AFC800
|
||||
|
||||
.data <VERS 0x001D9736 0x001D9936 0x001D95F6 0x001D9746 0x001D9BC6 0x001D9756 0x001D98A6> # From 3OE1:805D25C0
|
||||
.data 0x00000004
|
||||
.data 0x44AF0000
|
||||
|
||||
.data <VERS 0x001D9748 0x001D9948 0x001D9608 0x001D9758 0x001D9BD8 0x001D9768 0x001D98B8> # From 3OE1:805D25C0
|
||||
.data 0x00000004
|
||||
.data 0x44AF0000
|
||||
|
||||
.data 0x00000000
|
||||
.data 0x00000000
|
||||
|
||||
|
||||
|
||||
.versions 59NJ 59NL
|
||||
|
||||
write_call_func:
|
||||
.include WriteCallToCode
|
||||
|
||||
start:
|
||||
mov eax, 0x41800000 # Environment clip distance mod 16.0f
|
||||
mov [<VERS 0x0097D198 0x0097F1B8>], eax # This affects mostly static map objects
|
||||
mov [<VERS 0x0097D19C 0x0097F1BC>], eax
|
||||
mov [<VERS 0x0097D1A0 0x0097F1C0>], eax
|
||||
|
||||
mov ax, 0x9090
|
||||
mov [<VERS 0x00689BC7 0x00689B5B>], ax # Players draw distance 10000.0f always
|
||||
mov eax, 0x41000000 # Use newly acquired skipped branch room
|
||||
mov [<VERS 0x00689BD1 0x00689B65>], 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 <VERS 0x005C525B 0x005C5267>
|
||||
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 [<VERS 0x00689BD1 0x00689B65>]
|
||||
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 <VERS 0x007BB21E 0x007BA472>
|
||||
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 [<VERS 0x00689BD1 0x00689B65>]
|
||||
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 [<VERS 0x007BB21F 0x007BA473>]
|
||||
add eax, 0x002A1C74
|
||||
mov dword [<VERS 0x00518843 0x005187FF>], eax
|
||||
mov byte [<VERS 0x00518842 0x005187FE>], 0xE8
|
||||
mov dword [<VERS 0x00518847 0x00518803>], 0x90909090
|
||||
ret
|
||||
|
||||
# TOComputerMachine01
|
||||
patch_func_4:
|
||||
pop ecx
|
||||
push 7
|
||||
push <VERS 0x00616FF4 0x00616FFC>
|
||||
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 [<VERS 0x00689BD1 0x00689B65>]
|
||||
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 <VERS 0x006439A8 0x0064394C>
|
||||
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 [<VERS 0x00689BD1 0x00689B65>]
|
||||
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 <VERS 0x0065B959 0x0065B985>
|
||||
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 [<VERS 0x00689BD1 0x00689B65>]
|
||||
fld st0, dword [esp + 0x30]
|
||||
fmulp st1, st0
|
||||
ret
|
||||
patch_code_end6:
|
||||
push ecx
|
||||
jmp write_call_func
|
||||
@@ -1,92 +0,0 @@
|
||||
.meta name="Draw Distance"
|
||||
.meta description="Extends the draw\ndistance of many\nobjects"
|
||||
# Original code by Ralf @ GC-Forever and Aleron Ives
|
||||
# https://www.gc-forever.com/forums/viewtopic.php?t=2050
|
||||
# https://www.gc-forever.com/forums/viewtopic.php?t=2049
|
||||
|
||||
.versions 3OJ2 3OJ3 3OJ4 3OJ5 3OE0 3OE1 3OE2 3OP0
|
||||
|
||||
entry_ptr:
|
||||
reloc0:
|
||||
.offsetof start
|
||||
start:
|
||||
.include WriteCodeBlocksGC
|
||||
|
||||
.data 0x8000DFA0
|
||||
.deltaof hook_start, hook_end
|
||||
.address 0x8000DFA0
|
||||
hook_start:
|
||||
hook1:
|
||||
lfs f30, [r2 - <VERS 0x3E08 0x3E08 0x3E08 0x3E08 0x3E00 0x3E00 0x3E00 0x3E00>]
|
||||
fmuls f30, f30, f1
|
||||
blr
|
||||
hook2:
|
||||
lfs f2, [r2 - <VERS 0x3E08 0x3E08 0x3E08 0x3E08 0x3E00 0x3E00 0x3E00 0x3E00>]
|
||||
lfs f0, [r30 + 0x001C]
|
||||
fmuls f0, f0, f2
|
||||
blr
|
||||
hook3:
|
||||
lfs f28, [r2 - <VERS 0x3E08 0x3E08 0x3E08 0x3E08 0x3E00 0x3E00 0x3E00 0x3E00>]
|
||||
fmuls f28, f28, f2
|
||||
blr
|
||||
hook4:
|
||||
lfs f0, [r2 - <VERS 0x3E08 0x3E08 0x3E08 0x3E08 0x3E00 0x3E00 0x3E00 0x3E00>]
|
||||
lfs f1, [r3 + 0x000C]
|
||||
fmuls f0, f0, f1
|
||||
stfs [r3 + 0x000C], f0
|
||||
lis r3, <VERS 0x804C 0x804C 0x804D 0x804D 0x804C 0x804C 0x804D 0x804D>
|
||||
blr
|
||||
hook_end:
|
||||
|
||||
.data <VERS 0x801008E8 0x80100AD0 0x80100B74 0x80100A50 0x80100B8C 0x80100B8C 0x80100A60 0x80100C50>
|
||||
.data 0x00000004
|
||||
.address <VERS 0x801008E8 0x80100AD0 0x80100B74 0x80100A50 0x80100B8C 0x80100B8C 0x80100A60 0x80100C50>
|
||||
bl hook1
|
||||
|
||||
.data <VERS 0x8015671C 0x80156AD0 0x80156C34 0x80156B94 0x80156AD8 0x80156AD8 0x80156BF8 0x801570BC>
|
||||
.data 0x00000004
|
||||
.address <VERS 0x8015671C 0x80156AD0 0x80156C34 0x80156B94 0x80156AD8 0x80156AD8 0x80156BF8 0x801570BC>
|
||||
bl hook2
|
||||
|
||||
.data <VERS 0x801A1C64 0x801A203C 0x801A21A0 0x801A2100 0x801A2040 0x801A2040 0x801A2164 0x801A2628>
|
||||
.data 0x00000004
|
||||
.address <VERS 0x801A1C64 0x801A203C 0x801A21A0 0x801A2100 0x801A2040 0x801A2040 0x801A2164 0x801A2628>
|
||||
bl hook3
|
||||
|
||||
.data <VERS 0x801A1E64 0x801A223C 0x801A23A0 0x801A2300 0x801A2240 0x801A2240 0x801A2364 0x801A2828>
|
||||
.data 0x00000004
|
||||
.address <VERS 0x801A1E64 0x801A223C 0x801A23A0 0x801A2300 0x801A2240 0x801A2240 0x801A2364 0x801A2828>
|
||||
bl hook1
|
||||
|
||||
.data <VERS 0x80205044 0x802058B8 0x80206640 0x802063F4 0x80205840 0x80205840 0x80206728 0x80206124>
|
||||
.data 0x00000004
|
||||
.address <VERS 0x80205044 0x802058B8 0x80206640 0x802063F4 0x80205840 0x80205840 0x80206728 0x80206124>
|
||||
bl hook4
|
||||
|
||||
.data <VERS 0x802057E8 0x8020605C 0x80206DE4 0x80206B98 0x80205FE4 0x80205FE4 0x80206ECC 0x802068C8>
|
||||
.data 0x00000004
|
||||
.address <VERS 0x802057E8 0x8020605C 0x80206DE4 0x80206B98 0x80205FE4 0x80205FE4 0x80206ECC 0x802068C8>
|
||||
bl hook4
|
||||
|
||||
.data <VERS 0x805C83A8 0x805D29A8 0x805D9E48 0x805D9BE8 0x805C8CB0 0x805CFCD0 0x805D94F0 0x805D5730>
|
||||
.data 0x00000004
|
||||
.float 90000
|
||||
|
||||
.data <VERS 0x805C9254 0x805D3854 0x805DACF4 0x805DAA94 0x805C9B5C 0x805D0B7C 0x805DA39C 0x805D65DC>
|
||||
.data 0x00000004
|
||||
.float 62500
|
||||
|
||||
.data <VERS 0x805C987C 0x805D3E7C 0x805DB31C 0x805DB0BC 0x805CA184 0x805D11A4 0x805DA9C4 0x805D6C04>
|
||||
.data 0x00000004
|
||||
.float 640000
|
||||
|
||||
.data <VERS 0x805CA708 0x805D4D08 0x805DC1A8 0x805DBF48 0x805CB010 0x805D2030 0x805DB850 0x805D7A90>
|
||||
.data 0x00000004
|
||||
.float 90000
|
||||
|
||||
.data <VERS 0x805CAC98 0x805D5298 0x805DC738 0x805DC4D8 0x805CB5A0 0x805D25C0 0x805DBDE0 0x805D8020>
|
||||
.data 0x00000004
|
||||
.float 1400
|
||||
|
||||
.data 0x00000000
|
||||
.data 0x00000000
|
||||
@@ -1,131 +0,0 @@
|
||||
.meta name="Draw Distance"
|
||||
.meta description="Extends the draw\ndistance of many\nobjects"
|
||||
# Original code by Ralf @ GC-Forever and Aleron Ives
|
||||
# https://www.gc-forever.com/forums/viewtopic.php?t=2050
|
||||
# https://www.gc-forever.com/forums/viewtopic.php?t=2049
|
||||
# Xbox port by fuzziqersoftware
|
||||
|
||||
.versions 4OED 4OEU 4OJB 4OJD 4OJU 4OPD 4OPU
|
||||
|
||||
entry_ptr:
|
||||
reloc0:
|
||||
.offsetof start
|
||||
start:
|
||||
.include WriteCodeBlocksXB
|
||||
|
||||
.data <VERS 0x001737C2 0x001737D2 0x00173692 0x00173782 0x00173862 0x001737E2 0x00173792> # From 3OE1:80100B8C
|
||||
.deltaof p1_1s, p1_1e
|
||||
.address <VERS 0x001737C2 0x001737D2 0x00173692 0x00173782 0x00173862 0x001737E2 0x00173792> # From 3OE1:80100B8C
|
||||
p1_1s:
|
||||
call p1_2s
|
||||
nop
|
||||
p1_1e:
|
||||
.data <VERS 0x00173A42 0x00173A52 0x00173912 0x00173A02 0x00173AE2 0x00173A62 0x00173A12>
|
||||
.deltaof p1_2s, p1_2e
|
||||
.address <VERS 0x00173A42 0x00173A52 0x00173912 0x00173A02 0x00173AE2 0x00173A62 0x00173A12>
|
||||
p1_2s:
|
||||
fld st0, dword [esp + 0x1C]
|
||||
fadd st0, st0
|
||||
fchs st0
|
||||
ret
|
||||
p1_2e:
|
||||
|
||||
.data <VERS 0x001A3DEF 0x001A3EEF 0x001A3BBF 0x001A3DBF 0x001A3FDF 0x001A3E0F 0x001A3ECF> # From 3OE1:80156AD8
|
||||
.deltaof p2_1s, p2_1e
|
||||
.address <VERS 0x001A3DEF 0x001A3EEF 0x001A3BBF 0x001A3DBF 0x001A3FDF 0x001A3E0F 0x001A3ECF> # From 3OE1:80156AD8
|
||||
p2_1s:
|
||||
call p2_2s
|
||||
p2_1e:
|
||||
.data <VERS 0x001A3E38 0x001A3F38 0x001A3C08 0x001A3E08 0x001A4028 0x001A3E58 0x001A3F18>
|
||||
.deltaof p2_2s, p2_2e
|
||||
.address <VERS 0x001A3E38 0x001A3F38 0x001A3C08 0x001A3E08 0x001A4028 0x001A3E58 0x001A3F18>
|
||||
p2_2s:
|
||||
fld st0, dword [ecx + 0x1C]
|
||||
fadd st0, st0
|
||||
fld st0, st1
|
||||
ret
|
||||
p2_2e:
|
||||
|
||||
.data <VERS 0x002D2DC8 0x002D3148 0x002D0E68 0x002D1A28 0x002D32F8 0x002D2DF8 0x002D31C8> # From 3OE1:801A2040
|
||||
.deltaof p3_1s, p3_1e
|
||||
.address <VERS 0x002D2DC8 0x002D3148 0x002D0E68 0x002D1A28 0x002D32F8 0x002D2DF8 0x002D31C8> # From 3OE1:801A2040
|
||||
p3_1s:
|
||||
call p3_2s
|
||||
nop
|
||||
p3_1e:
|
||||
.data <VERS 0x002D2EA7 0x002D3227 0x002D0F47 0x002D1B07 0x002D33D7 0x002D2ED7 0x002D32A7>
|
||||
.deltaof p3_2s, p3_2e
|
||||
.address <VERS 0x002D2EA7 0x002D3227 0x002D0F47 0x002D1B07 0x002D33D7 0x002D2ED7 0x002D32A7>
|
||||
p3_2s:
|
||||
fld st0, dword [esp + 0x24]
|
||||
fadd st0, st0
|
||||
fchs st0
|
||||
ret
|
||||
p3_2e:
|
||||
|
||||
.data <VERS 0x00156AC8 0x002D32A8 0x001569E8 0x00156A78 0x00156AB8 0x00156AE8 0x002D3328> # From 3OE1:801A2240
|
||||
.deltaof p4_1s, p4_1e
|
||||
.address <VERS 0x00156AC8 0x002D32A8 0x001569E8 0x00156A78 0x00156AB8 0x00156AE8 0x002D3328> # From 3OE1:801A2240
|
||||
p4_1s:
|
||||
call p4_2s
|
||||
nop
|
||||
p4_1e:
|
||||
.data <VERS 0x00156C44 0x002D33B4 0x00156B64 0x00156BF4 0x00156C34 0x00156C64 0x002D3434>
|
||||
.deltaof p4_2s, p4_2e
|
||||
.address <VERS 0x00156C44 0x002D33B4 0x00156B64 0x00156BF4 0x00156C34 0x00156C64 0x002D3434>
|
||||
p4_2s:
|
||||
fld st0, dword [esp + 0x28]
|
||||
fadd st0, st0
|
||||
fchs st0
|
||||
ret
|
||||
p4_2e:
|
||||
|
||||
.data <VERS 0x002E2B93 0x002E2E8C 0x002E0C33 0x002E17B3 0x002E2E6C 0x002E2BC3 0x002E2EBC> # From 3OE1:80205840
|
||||
.deltaof p5_1s, p5_1e
|
||||
.address <VERS 0x002E2B93 0x002E2E8C 0x002E0C33 0x002E17B3 0x002E2E6C 0x002E2BC3 0x002E2EBC> # From 3OE1:80205840
|
||||
p5_1s:
|
||||
call p5_3s
|
||||
p5_1e:
|
||||
.data <VERS 0x002E1FD1 0x002E2404 0x002E0071 0x002E0BF1 0x002E23E4 0x002E2001 0x002E2434> # From 3OE1:80205FE4
|
||||
.deltaof p5_2s, p5_2e
|
||||
.address <VERS 0x002E1FD1 0x002E2404 0x002E0071 0x002E0BF1 0x002E23E4 0x002E2001 0x002E2434> # From 3OE1:80205FE4
|
||||
p5_2s:
|
||||
call p5_3s
|
||||
p5_2e:
|
||||
.data <VERS 0x002E2C82 0x002E2FD1 0x002E0D22 0x002E18A2 0x002E2FB1 0x002E2CB2 0x002E3001>
|
||||
.deltaof p5_3s, p5_3e
|
||||
.address <VERS 0x002E2C82 0x002E2FD1 0x002E0D22 0x002E18A2 0x002E2FB1 0x002E2CB2 0x002E3001>
|
||||
p5_3s:
|
||||
fld st0, dword [eax + 0x0C]
|
||||
fadd st0, st0
|
||||
fstp dword [eax + 0x0C], st0
|
||||
mov eax, [<VERS 0x0053A9CC 0x0053A26C 0x00535BAC 0x0053622C 0x0053D54C 0x0053A9CC 0x0053AD6C>]
|
||||
ret
|
||||
p5_3e:
|
||||
|
||||
.data <VERS 0x004920A0 0x00491940 0x0048D4F0 0x0048DC88 0x00494C30 0x004920A8 0x00492440> # From 3OE1:805CFCD0
|
||||
.data 0x00000004
|
||||
.data 0x47AFC800
|
||||
|
||||
.data <VERS 0x0042D0A0 0x0042C940 0x00428DC0 0x00429130 0x0042C940 0x0042D0C0 0x0042D450> # From 3OE1:805D0B7C
|
||||
.data 0x00000004
|
||||
.data 0x437A0000
|
||||
|
||||
.data <VERS 0x0049222C 0x00491ACC 0x0048D67C 0x0048DE14 0x00494DBC 0x00492234 0x004925CC> # From 3OE1:805D11A4
|
||||
.data 0x00000004
|
||||
.data 0x491C4000
|
||||
|
||||
.data <VERS 0x0042B838 0x0042B0D8 0x00427558 0x004278C8 0x0042B0D8 0x0042B858 0x0042BBE8> # From 3OE1:805D2030
|
||||
.data 0x00000004
|
||||
.data 0x47AFC800
|
||||
|
||||
.data <VERS 0x001D9736 0x001D9936 0x001D95F6 0x001D9746 0x001D9BC6 0x001D9756 0x001D98A6> # From 3OE1:805D25C0
|
||||
.data 0x00000004
|
||||
.data 0x44AF0000
|
||||
|
||||
.data <VERS 0x001D9748 0x001D9948 0x001D9608 0x001D9758 0x001D9BD8 0x001D9768 0x001D98B8> # From 3OE1:805D25C0
|
||||
.data 0x00000004
|
||||
.data 0x44AF0000
|
||||
|
||||
.data 0x00000000
|
||||
.data 0x00000000
|
||||
@@ -1,146 +0,0 @@
|
||||
# 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
|
||||
@@ -1,146 +0,0 @@
|
||||
# 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-59NL
|
||||
|
||||
start:
|
||||
mov eax, 0x41800000 # Environment clip distance mod 16.0f
|
||||
mov [0x0097F1B8], eax # This affects mostly static map objects
|
||||
mov [0x0097F1BC], eax
|
||||
mov [0x0097F1C0], eax
|
||||
|
||||
mov ax, 0x9090
|
||||
mov [0x00689B5B], ax # Players draw distance 10000.0f always
|
||||
mov eax, 0x41000000 # Use newly acquired skipped branch room
|
||||
mov [0x00689B65], 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 0x005C5267
|
||||
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 [0x00689B65]
|
||||
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 0x007BA472
|
||||
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 [0x00689B65]
|
||||
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 [0x007BA473]
|
||||
add eax, 0x002A1C74
|
||||
mov dword [0x005187FF], eax
|
||||
mov byte [0x005187FE], 0xE8
|
||||
mov dword [0x00518803], 0x90909090
|
||||
ret
|
||||
|
||||
# TOComputerMachine01
|
||||
patch_func_4:
|
||||
pop ecx
|
||||
push 7
|
||||
push 0x00616FFC
|
||||
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 [0x00689B65]
|
||||
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 0x0064394C
|
||||
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 [0x00689B65]
|
||||
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 0x0065B985
|
||||
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 [0x00689B65]
|
||||
fld st0, dword [esp + 0x30]
|
||||
fmulp st1, st0
|
||||
ret
|
||||
patch_code_end6:
|
||||
push ecx
|
||||
jmp write_call_func
|
||||
@@ -1,258 +0,0 @@
|
||||
.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
|
||||
+42
-37
@@ -1,21 +1,25 @@
|
||||
.meta visibility="all"
|
||||
.meta key="EnemyDamageSync"
|
||||
.meta name="DMC"
|
||||
.meta description="Mitigates effects\nof enemy health\ndesync"
|
||||
.meta client_flag="0x0000001000000000"
|
||||
|
||||
.versions 59NJ 59NL
|
||||
|
||||
entry_ptr:
|
||||
reloc0:
|
||||
.offsetof start
|
||||
|
||||
write_call_to_code_multi:
|
||||
.include WriteCallToCodeMulti-59NL
|
||||
.include WriteCallToCodeMulti
|
||||
write_address_of_code:
|
||||
.include WriteAddressOfCode-59NL
|
||||
.include WriteAddressOfCode
|
||||
|
||||
start:
|
||||
|
||||
# Replace 6x09 with 6xE4 in subcommand handler table
|
||||
mov dword [0x00A0FC30], 0x000600E4 # subcommand=0xE4, flags=6
|
||||
push 0x00A0FC34
|
||||
mov dword [<VERS 0x00A0DC30 0x00A0FC30>], 0x000600E4 # subcommand=0xE4, flags=6
|
||||
push <VERS 0x00A0DC34 0x00A0FC34>
|
||||
call +4
|
||||
.deltaof handle_6xE4_start, handle_6xE4_end
|
||||
pop eax
|
||||
@@ -27,7 +31,7 @@ handle_6xE4_start: # (G_6xE4* cmd @ [esp + 4]) -> void
|
||||
push esi
|
||||
push edi
|
||||
|
||||
test byte [0x00AAB27C], 0x80
|
||||
test byte [<VERS 0x00AA8DFC 0x00AAB27C>], 0x80
|
||||
jz handle_6xE4_return
|
||||
mov ebx, [esp + 0x10] # cmd
|
||||
movzx eax, word [ebx + 2]
|
||||
@@ -37,13 +41,13 @@ handle_6xE4_start: # (G_6xE4* cmd @ [esp + 4]) -> void
|
||||
jge handle_6xE4_return
|
||||
|
||||
movzx eax, word [ebx + 2]
|
||||
.include GetEnemyEntity-59NL # auto* ene = get_enemy_entity(cmd->header.entity_id);
|
||||
.include GetEnemyEntity # 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, [0x00AB02B8] # eax = state_for_enemy(cmd->header.entity_id)
|
||||
add eax, [<VERS 0x00AADE38 0x00AB02B8>] # eax = state_for_enemy(cmd->header.entity_id)
|
||||
|
||||
cmp dword [ebx + 0x0C], 0
|
||||
jl handle_6xE4_not_proportional
|
||||
@@ -90,7 +94,7 @@ handle_6xE4_not_proportional:
|
||||
mov [esp + 4], si # out_cmd.entity_index
|
||||
mov [esp + 6], di # out_cmd.total_damage
|
||||
mov ecx, esp
|
||||
mov edx, 0x008003E0
|
||||
mov edx, <VERS 0x00801150 0x008003E0>
|
||||
call edx # send_and_handle_60(&out_cmd);
|
||||
add esp, 0x10
|
||||
jmp handle_6xE4_return
|
||||
@@ -123,43 +127,44 @@ handle_6xE4_end:
|
||||
|
||||
|
||||
|
||||
# Note: in 59NJ this object is TObjectV00b421c0 (it's the same as 3OE1's TObjectV8047c128)
|
||||
# Write TObjectV00b441c0::incr_hp_with_sync
|
||||
push 5
|
||||
push 0x00774448 # TObjectV00b441c0::v18_accept_hit (presumably Resta) - this is add_hp, not subtract_hp!
|
||||
push <VERS 0x00775224 0x00774448> # TObjectV00b441c0::v18_accept_hit (presumably Resta) - this is add_hp, not subtract_hp!
|
||||
push 5
|
||||
push 0x00777287 # TObjectV00b441c0::subtract_hp_if_not_in_state_2
|
||||
push <VERS 0x00778063 0x00777287> # TObjectV00b441c0::subtract_hp_if_not_in_state_2
|
||||
push 5
|
||||
push 0x00776CD6 # TObjectV00b441c0::v19_handle_hit_special_effects
|
||||
push <VERS 0x00777AB2 0x00776CD6> # TObjectV00b441c0::v19_handle_hit_special_effects
|
||||
push 5
|
||||
push 0x00776D4F # TObjectV00b441c0::v19_handle_hit_special_effects
|
||||
push <VERS 0x00777B2B 0x00776D4F> # TObjectV00b441c0::v19_handle_hit_special_effects
|
||||
push 5
|
||||
push 0x00776E20 # TObjectV00b441c0::v19_handle_hit_special_effects
|
||||
push <VERS 0x00777BFC 0x00776E20> # TObjectV00b441c0::v19_handle_hit_special_effects
|
||||
push 5
|
||||
push 0x00776E99 # TObjectV00b441c0::v19_handle_hit_special_effects
|
||||
push <VERS 0x00777C75 0x00776E99> # TObjectV00b441c0::v19_handle_hit_special_effects
|
||||
push 5
|
||||
push 0x00775F51 # TObjectV00b441c0::v19_handle_hit_special_effects
|
||||
push <VERS 0x00776D2D 0x00775F51> # TObjectV00b441c0::v19_handle_hit_special_effects
|
||||
push 5
|
||||
push 0x00775BE6 # TObjectV00b441c0::v19_handle_hit_special_effects
|
||||
push <VERS 0x007769C2 0x00775BE6> # TObjectV00b441c0::v19_handle_hit_special_effects
|
||||
push 5
|
||||
push 0x00775A60 # TObjectV00b441c0::v19_handle_hit_special_effects
|
||||
push <VERS 0x0077683C 0x00775A60> # TObjectV00b441c0::v19_handle_hit_special_effects
|
||||
push 5
|
||||
push 0x00775726 # TObjectV00b441c0::v19_handle_hit_special_effects (Devil's/Demon's)
|
||||
push <VERS 0x00776502 0x00775726> # TObjectV00b441c0::v19_handle_hit_special_effects (Devil's/Demon's)
|
||||
push 5
|
||||
push 0x00774D7B # TObjectV00b441c0::v18_accept_hit
|
||||
push <VERS 0x00775B57 0x00774D7B> # TObjectV00b441c0::v18_accept_hit
|
||||
push 5
|
||||
push 0x00774C47 # TObjectV00b441c0::v18_accept_hit
|
||||
push <VERS 0x00775A23 0x00774C47> # TObjectV00b441c0::v18_accept_hit
|
||||
push 5
|
||||
push 0x00774A14 # TObjectV00b441c0::v18_accept_hit
|
||||
push <VERS 0x007757F0 0x00774A14> # TObjectV00b441c0::v18_accept_hit
|
||||
push 5
|
||||
push 0x0077482A # TObjectV00b441c0::v18_accept_hit
|
||||
push <VERS 0x00775606 0x0077482A> # TObjectV00b441c0::v18_accept_hit
|
||||
push 5
|
||||
push 0x007746E0 # TObjectV00b441c0::v18_accept_hit
|
||||
push <VERS 0x007754BC 0x007746E0> # TObjectV00b441c0::v18_accept_hit
|
||||
push 5
|
||||
push 0x00774061 # TObjectV00b441c0::v18_accept_hit
|
||||
push <VERS 0x00774E3D 0x00774061> # TObjectV00b441c0::v18_accept_hit
|
||||
push 5
|
||||
push 0x00773EFA # TObjectV00b441c0::v18_accept_hit
|
||||
push <VERS 0x00774CD6 0x00773EFA> # TObjectV00b441c0::v18_accept_hit
|
||||
push 5
|
||||
push 0x00773937 # TObjectV00b441c0::v17
|
||||
push <VERS 0x00774713 0x00773937> # TObjectV00b441c0::v17
|
||||
push 18
|
||||
call +4
|
||||
.deltaof on_add_or_subtract_hp_start, on_add_or_subtract_hp_end
|
||||
@@ -168,7 +173,7 @@ handle_6xE4_end:
|
||||
call on_add_or_subtract_hp_end
|
||||
|
||||
on_add_or_subtract_hp_start: # (TObjectV00b441c0* this @ ecx, int16_t amount @ [esp + 4]) -> bool @ eax
|
||||
test byte [0x00AAB27C], 0x80
|
||||
test byte [<VERS 0x00AA8DFC 0x00AAB27C>], 0x80
|
||||
jz on_add_or_subtract_hp_skip_send
|
||||
movzx eax, word [ecx + 0x1C] # ene->entity_id
|
||||
cmp eax, 0x1000
|
||||
@@ -178,14 +183,14 @@ on_add_or_subtract_hp_start: # (TObjectV00b441c0* this @ ecx, int16_t amount @
|
||||
|
||||
and eax, 0x0FFF
|
||||
imul eax, eax, 0x0C
|
||||
add eax, [0x00AB02B8] # eax = state_for_enemy(cmd->header.entity_id)
|
||||
add eax, [<VERS 0x00AADE38 0x00AB02B8>] # 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], 0x0077444D # Check if callsite is add_hp
|
||||
cmp dword [esp + 0x10], <VERS 0x00775229 0x0077444D> # 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:
|
||||
@@ -198,7 +203,7 @@ on_add_or_subtract_hp_skip_negate_amount:
|
||||
mov [esp + 0x0A], dx # cmd.max_hp
|
||||
mov dword [esp + 0x0C], 0xBF800000 # cmd.factor
|
||||
|
||||
cmp dword [esp + 0x10], 0x0077572B # Check if callsite is Devil's/Demon's
|
||||
cmp dword [esp + 0x10], <VERS 0x00776507 0x0077572B> # 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
|
||||
@@ -216,16 +221,16 @@ on_add_or_subtract_hp_not_proportional:
|
||||
push ecx
|
||||
push 0x10
|
||||
push edx
|
||||
mov ecx, [0x00AAB284]
|
||||
mov edx, 0x007D3F38
|
||||
mov ecx, [<VERS 0x00AA8E04 0x00AAB284>]
|
||||
mov edx, <VERS 0x007D4CBC 0x007D3F38>
|
||||
call edx # send_60(root_protocol, &cmd, sizeof(cmd));
|
||||
pop ecx
|
||||
add esp, 0x10
|
||||
|
||||
on_add_or_subtract_hp_skip_send:
|
||||
mov eax, 0x00777414 # subtract_hp
|
||||
mov edx, 0x007773D4 # add_hp
|
||||
cmp dword [esp], 0x0077444D # Check if callsite is add_hp
|
||||
mov eax, <VERS 0x007781F0 0x00777414> # subtract_hp
|
||||
mov edx, <VERS 0x007781B0 0x007773D4> # add_hp
|
||||
cmp dword [esp], <VERS 0x00775229 0x0077444D> # Check if callsite is add_hp
|
||||
cmove eax, edx
|
||||
jmp eax
|
||||
|
||||
@@ -235,7 +240,7 @@ on_add_or_subtract_hp_end:
|
||||
|
||||
|
||||
push 5
|
||||
push 0x0078781F
|
||||
push <VERS 0x0078864B 0x0078781F>
|
||||
push 1
|
||||
call +4
|
||||
.deltaof on_6x0A_patch_start, on_6x0A_patch_end
|
||||
@@ -244,7 +249,7 @@ on_add_or_subtract_hp_end:
|
||||
call on_6x0A_patch_end
|
||||
|
||||
on_6x0A_patch_start: # (TObjectV00b441c0* this @ ecx, int16_t amount @ [esp + 4]) -> bool @ eax
|
||||
test byte [0x00AAB27C], 0x80
|
||||
test byte [<VERS 0x00AA8DFC 0x00AAB27C>], 0x80
|
||||
jz on_6x0A_patch_skip_write
|
||||
mov [esp + 0x0A], cx
|
||||
on_6x0A_patch_skip_write:
|
||||
+3
-1
@@ -1,3 +1,5 @@
|
||||
.meta visibility="all"
|
||||
.meta key="EnemyDamageSync"
|
||||
.meta name="DMC"
|
||||
.meta description="Mitigates effects\nof enemy health\ndesync"
|
||||
.meta client_flag="0x0000001000000000"
|
||||
@@ -8,7 +10,7 @@ entry_ptr:
|
||||
reloc0:
|
||||
.offsetof start
|
||||
start:
|
||||
.include WriteCodeBlocksGC
|
||||
.include WriteCodeBlocks
|
||||
|
||||
.label TObjectV8047c128_add_hp, <VERS 0x8001143C 0x8001145C 0x800116EC 0x8001140C 0x80011454 0x80011454 0x8001140C 0x80011484>
|
||||
.label TObjectV8047c128_subtract_hp, <VERS 0x8001147C 0x8001149C 0x8001172C 0x8001144C 0x80011494 0x80011494 0x8001144C 0x800114C4>
|
||||
+7
-6
@@ -1,3 +1,5 @@
|
||||
.meta visibility="all"
|
||||
.meta key="EnemyDamageSync"
|
||||
.meta name="DMC"
|
||||
.meta description="Mitigates effects\nof enemy health\ndesync"
|
||||
.meta client_flag="0x0000001000000000"
|
||||
@@ -9,7 +11,7 @@ reloc0:
|
||||
.offsetof start
|
||||
|
||||
write_call_to_code_multi:
|
||||
.include WriteCallToCodeMultiXB
|
||||
.include WriteCallToCodeMulti
|
||||
|
||||
|
||||
|
||||
@@ -284,7 +286,7 @@ on_add_or_subtract_hp_end:
|
||||
|
||||
|
||||
write_static_patches:
|
||||
.include WriteCodeBlocksXB
|
||||
.include WriteCodeBlocks
|
||||
|
||||
|
||||
|
||||
@@ -326,10 +328,9 @@ on_subtract_hp_if_not_in_state_2_end:
|
||||
.deltaof v17_subtract_hp_inlined_callsite_start, v17_subtract_hp_inlined_callsite_end
|
||||
.address <VERS 0x002A60CA 0x002A6BAA 0x002A807A 0x002A7B0A 0x002A7CEA 0x002A7B2A 0x002A7DAA>
|
||||
v17_subtract_hp_inlined_callsite_start:
|
||||
# This must assemble to exactly 0x1A bytes. There is a vfn call shortly after
|
||||
# this, and fortunately it appears eax, ecx, and edx are not used before
|
||||
# then, so we don't have to save any registers here; we just have to move the
|
||||
# args into the right places.
|
||||
# This must assemble to exactly 0x1A bytes. There is a vfn call shortly after this, and fortunately it appears eax,
|
||||
# ecx, and edx are not used before then, so we don't have to save any registers here; we just have to move the args
|
||||
# into the right places.
|
||||
mov cx, ax
|
||||
mov eax, edi
|
||||
call -1 # Overwritten by write_call_to_code_multi later
|
||||
+3
-1
@@ -1,3 +1,5 @@
|
||||
.meta visibility="all"
|
||||
.meta key="EnemyHPBars"
|
||||
.meta name="Enemy HP bars"
|
||||
.meta description="Shows HP bars in\nenemy info windows"
|
||||
|
||||
@@ -7,7 +9,7 @@ entry_ptr:
|
||||
reloc0:
|
||||
.offsetof start
|
||||
start:
|
||||
.include WriteCodeBlocksBB
|
||||
.include WriteCodeBlocks
|
||||
.data <VERS 0x0073197D 0x007318DD>
|
||||
.data 6
|
||||
.binary 81E2FDFFFFFF
|
||||
+6
-3
@@ -1,16 +1,19 @@
|
||||
.meta name="Enemy HP bars"
|
||||
.meta description="Shows HP bars in\nenemy info windows"
|
||||
# Original code by Ralf @ GC-Forever and Aleron Ives
|
||||
# https://www.gc-forever.com/forums/viewtopic.php?t=2050
|
||||
# https://www.gc-forever.com/forums/viewtopic.php?t=2049
|
||||
|
||||
.meta visibility="all"
|
||||
.meta key="EnemyHPBars"
|
||||
.meta name="Enemy HP bars"
|
||||
.meta description="Shows HP bars in\nenemy info windows"
|
||||
|
||||
.versions 3OJ2 3OJ3 3OJ4 3OJ5 3OE0 3OE1 3OE2 3OP0
|
||||
|
||||
entry_ptr:
|
||||
reloc0:
|
||||
.offsetof start
|
||||
start:
|
||||
.include WriteCodeBlocksGC
|
||||
.include WriteCodeBlocks
|
||||
|
||||
.label hook_addr, 0x8000B650
|
||||
.label sprintf, <VERS 0x80395EFC 0x80398904 0x8039A7A4 0x8039A554 0x803971CC 0x80397224 0x8039A924 0x80399414>
|
||||
+8
-5
@@ -1,17 +1,20 @@
|
||||
.meta name="Enemy HP bars"
|
||||
.meta description="Shows HP bars in\nenemy info windows"
|
||||
# Original code by Ralf @ GC-Forever and Aleron Ives
|
||||
# https://www.gc-forever.com/forums/viewtopic.php?t=2050
|
||||
# https://www.gc-forever.com/forums/viewtopic.php?t=2049
|
||||
# Xbox port by fuzziqersoftware
|
||||
|
||||
.meta visibility="all"
|
||||
.meta key="EnemyHPBars"
|
||||
.meta name="Enemy HP bars"
|
||||
.meta description="Shows HP bars in\nenemy info windows"
|
||||
|
||||
.versions 4OED 4OEU 4OJB 4OJD 4OJU 4OPD 4OPU
|
||||
|
||||
entry_ptr:
|
||||
reloc0:
|
||||
.offsetof start
|
||||
start:
|
||||
.include WriteCodeBlocksXB
|
||||
.include WriteCodeBlocks
|
||||
.data <VERS 0x0026B063 0x0026B193 0x0026ABA3 0x0026AF13 0x0026B2F3 0x0026B083 0x0026B193>
|
||||
.data 0x00000001
|
||||
.binary C0
|
||||
@@ -51,8 +54,8 @@ str_data_start:
|
||||
.data 0x00000000
|
||||
str_data_end:
|
||||
|
||||
# WARNING: FlickeringStatusIcons patch starts immediately after this segment;
|
||||
# if the size of this is changed, that patch will have to be changed too
|
||||
# WARNING: FlickeringStatusIcons patch starts immediately after this segment; if the size of this is changed, that
|
||||
# patch will have to be changed too
|
||||
.data <VERS 0x002DB050 0x002DB550 0x002D90E0 0x002D9CB0 0x002DB580 0x002DB080 0x002DB5D0>
|
||||
.deltaof new_code_start, new_code_end
|
||||
new_code_start:
|
||||
+3
-3
@@ -1,7 +1,7 @@
|
||||
# This patch gives you the maximum number of each card. It only works if used
|
||||
# in-game, which means it must be used by running `$patch AllCards`.
|
||||
# This patch gives you the maximum number of each card. It only works if used in-game, which means it must be used by
|
||||
# running `$patch AllCards`.
|
||||
|
||||
.meta hide_from_patches_menu
|
||||
.meta visibility="cheat"
|
||||
.meta name="All cards"
|
||||
.meta description="Gives you the\nmaximum number of\neach card."
|
||||
|
||||
+54
-75
@@ -1,18 +1,16 @@
|
||||
# This patch enables the debug menus in PSO Episode 3 USA. Specifically, it
|
||||
# causes them all to load, but only activates one (selected by uncommenting a
|
||||
# line below). See the comments for more information. Most of these editors are
|
||||
# present in PSO PC and PSO Xbox as well, but not in GC Episodes 1 & 2. There
|
||||
# are notes in the below comments that may help get these editors working on
|
||||
# PSO PC.
|
||||
# This patch enables the debug menus in PSO Episode 3 USA. Specifically, it causes them all to load, but only activates
|
||||
# one (selected by uncommenting a line below). See the comments for more information. Most of these editors are present
|
||||
# in PSO PC and PSO Xbox as well, but not in GC Episodes 1 & 2. There are notes in the below comments that may help get
|
||||
# these editors working on PSO PC.
|
||||
|
||||
# This patch must not be run from the Patches menu - it should only be run with
|
||||
# the $patch command, since the client will likely crash if the player is not
|
||||
# in a game or lobby when the patch runs.
|
||||
# This patch must not be run from the Patches menu - it should only be run with the $patch command, since the client
|
||||
# will likely crash if the player is not in a game or lobby when the patch runs.
|
||||
|
||||
.meta hide_from_patches_menu
|
||||
.meta name="Editors"
|
||||
.meta description="Enables the various\ndebug menus"
|
||||
|
||||
.versions 3SE0
|
||||
|
||||
entry_ptr:
|
||||
reloc0:
|
||||
.offsetof start
|
||||
@@ -25,8 +23,7 @@ start:
|
||||
stw [r1 + 0x0C], r30
|
||||
stw [r1 + 0x08], r29
|
||||
|
||||
# Write a short hook that updates our editors table when TEditor_destroy() is
|
||||
# called
|
||||
# Write a short hook that updates our editors table when TEditor_destroy() is called
|
||||
bl get_TEditor_destroy_hook_addr
|
||||
mr r4, r3
|
||||
bl get_TEditor_destroy_hook_end
|
||||
@@ -48,10 +45,9 @@ start:
|
||||
ori r29, r29, 0x17C4
|
||||
|
||||
construct_editors:
|
||||
# Call the constructors for all the editors and save the object pointers. If
|
||||
# an editor already exists, set its disable flags. (This behavior allows this
|
||||
# patch to run again to switch to a different editor without changing rooms.)
|
||||
# Note: In PSO PC (the version I have, at least) this table is at 00691FA8.
|
||||
# Call the constructors for all the editors and save the object pointers. If an editor already exists, set its
|
||||
# disable flags. (This behavior allows this patch to run again to switch to a different editor without changing
|
||||
# rooms.) In PSO PC (2OJW) this table is at 00691FA8.
|
||||
lis r30, 0x8043
|
||||
ori r30, r30, 0x3760
|
||||
addi r31, r30, 0xB4 # 15 entries * 12 bytes per entry
|
||||
@@ -76,13 +72,11 @@ editor_construction_failed:
|
||||
blt again
|
||||
|
||||
activate_chosen_editor:
|
||||
# All of the editors have flags set at construction time that effectively
|
||||
# disable them (by disabling both the update and render functions). At the
|
||||
# time this code is executed, the flags are already set (and we set them again
|
||||
# in the above loop anyway), so we can unset the flags for whichever editor we
|
||||
# want to run by uncommenting the appropriate lwz opcode below.
|
||||
# Most of these tools expect input from the controller in port 3; the comments
|
||||
# below all refer to inputs from that port.
|
||||
# All of the editors have flags set at construction time that effectively disable them (by disabling both the update
|
||||
# and render functions). At the time this code is executed, the flags are already set (and we set them again in the
|
||||
# above loop anyway), so we can unset the flags for whichever editor we want to run by uncommenting the appropriate
|
||||
# lwz opcode below. Most of these tools expect input from the controller in port 3; the comments below all refer to
|
||||
# inputs from that port.
|
||||
|
||||
li r4, 0
|
||||
lis r29, 0x8000
|
||||
@@ -92,91 +86,76 @@ activate_chosen_editor:
|
||||
# This editor is very similar to TGroupEnemySetEditor (see below).
|
||||
|
||||
# lwz r4, [r29 + 0x04] # TGroupEnemySetEditor
|
||||
# This editor only works in a game; it crashes if loaded in the lobby.
|
||||
# Use the D-pad to choose a value; hold X and use the D-pad to modify the
|
||||
# selected value. Hold R to use the menu on the right.
|
||||
# This editor only works in a game; it crashes if loaded in the lobby. Use the D-pad to choose a value; hold X and
|
||||
# use the D-pad to modify the selected value. Hold R to use the menu on the right.
|
||||
|
||||
# lwz r4, [r29 + 0x08] # TCameraEditor
|
||||
# This editor displays a floating-point value at the bottom of the screen,
|
||||
# which you can modify with C-left and C-right. It's not apparent what this
|
||||
# value represents, though.
|
||||
# This editor displays a floating-point value at the bottom of the screen, which you can modify with C-left and
|
||||
# C-right. It's not apparent what this value represents, though.
|
||||
|
||||
# lwz r4, [r29 + 0x0C] # TParticleEditor
|
||||
# This editor has two modes. Hold A and press X to switch modes. In "MAIN
|
||||
# MODE", use D-left/D-right to pick an effect. Hold L to make the effect
|
||||
# picker manageable (instead of insanely fast). In "ELEMENT MODE", it seems
|
||||
# that any of the displayed values can be modified, but the selector is very
|
||||
# hard to see (the selected section is rendered in FFFFFF, while the others
|
||||
# are rendered in F0F0F0 - very similar colors!). Hold A, Y, or X and use
|
||||
# the D-pad to change a value in the selected section (each of A/Y/X
|
||||
# corresponds to a specific field in the current section).
|
||||
# This editor has two modes. Hold A and press X to switch modes. In "MAIN MODE", use D-left/D-right to pick an
|
||||
# effect. Hold L to make the effect picker manageable (instead of insanely fast). In "ELEMENT MODE", it seems that
|
||||
# any of the displayed values can be modified, but the selector is very hard to see (the selected section is
|
||||
# rendered in FFFFFF, while the others are rendered in F0F0F0 - very similar colors!). Hold A, Y, or X and use the
|
||||
# D-pad to change a value in the selected section (each of A/Y/X corresponds to a specific field in the current
|
||||
# section).
|
||||
|
||||
# lwz r4, [r29 + 0x10] # TFreeCamera
|
||||
# This editor does nothing. Probably it was never implemented or the code
|
||||
# was intentionally deleted (though if it was, it's not clear why only this
|
||||
# editor's code was deleted).
|
||||
# This editor does nothing. Probably it was never implemented or the code was intentionally deleted (though if it
|
||||
# was, it's not clear why only this editor's code was deleted).
|
||||
|
||||
# lwz r4, [r29 + 0x14] # TFogEditor
|
||||
# Use L/R to pick a line, and the D-pad to modify the values. NO specifies
|
||||
# which fog entry you're editing (0-127).
|
||||
# Use L/R to pick a line, and the D-pad to modify the values. NO specifies which fog entry you're editing (0-127).
|
||||
|
||||
# lwz r4, [r29 + 0x18] # TLightEditor
|
||||
# Used for testing character lighting. Use L to select a section and the
|
||||
# D-pad to choose and modify values within that section. COLOR and DIR
|
||||
# specify the properties of the highlight; AMBIENT specifies the color of
|
||||
# the non-highlight lighting. It's not clear what the last section does.
|
||||
# Used for testing character lighting. Use L to select a section and the D-pad to choose and modify values within
|
||||
# that section. COLOR and DIR specify the properties of the highlight; AMBIENT specifies the color of the non-
|
||||
# highlight lighting. It's not clear what the last section does.
|
||||
|
||||
# lwz r4, [r29 + 0x1C] # nothing (type table entry is blank)
|
||||
|
||||
# lwz r4, [r29 + 0x20] # TSeqVarsEdit
|
||||
# Use L/R to change pages, use the D-pad to pick a flag, and use A to toggle
|
||||
# it. There are 8192 flags in total (0x400 bytes).
|
||||
# Use L/R to change pages, use the D-pad to pick a flag, and use A to toggle it. There are 8192 flags in total
|
||||
# (0x400 bytes).
|
||||
|
||||
# lwz r4, [r29 + 0x24] # TSetEvtScriptTest
|
||||
# Use D-left/D-right to change the label value and D-up/D-down to move the
|
||||
# menu selection. Two of the menu items appear to do nothing, and the last
|
||||
# crashes. Maybe it works better on Episodes 1&2.
|
||||
# Use D-left/D-right to change the label value and D-up/D-down to move the menu selection. Two of the menu items
|
||||
# appear to do nothing, and the last crashes. Maybe it works better on Episodes 1&2.
|
||||
|
||||
# lwz r4, [r29 + 0x28] # nothing (type table entry is blank)
|
||||
|
||||
# lwz r4, [r29 + 0x2C] # TQuestScriptChecker (quest debugger)
|
||||
# Use L to change functions, and the D-pad to navigate within each function.
|
||||
# If you set EVENT NO to a very high value, the editor can appear messed up;
|
||||
# what actually happens is the value is shifted one decimal place to the
|
||||
# right, but the cursor remains in the same position with incorrect
|
||||
# highlighting. The value appears to be a signed 32-bit integer. On the
|
||||
# registers page, use D-left/D-right to see more registers; hold X and use
|
||||
# the D-pad to modify a register's value. Similarly, hold X and use the
|
||||
# D-pad on the breakpoints page to change values.
|
||||
# Use L to change functions, and the D-pad to navigate within each function. If you set EVENT NO to a very high
|
||||
# value, the editor can appear messed up; what actually happens is the value is shifted one decimal place to the
|
||||
# right, but the cursor remains in the same position with incorrect highlighting. The value appears to be a signed
|
||||
# 32-bit integer. On the registers page, use D-left/D-right to see more registers; hold X and use the D-pad to
|
||||
# modify a register's value. Similarly, hold X and use the D-pad on the breakpoints page to change values.
|
||||
|
||||
# lwz r4, [r29 + 0x30] # TPlyPKEditor (battle mode options)
|
||||
# Use the D-pad to move the cursor and set options. In Episode 3, it appears
|
||||
# this debugger doesn't do anything. It's likely more functional in Episodes
|
||||
# 1 & 2.
|
||||
# Use the D-pad to move the cursor and set options. In Episode 3, it appears this debugger doesn't do anything.
|
||||
# It's likely more functional in Episodes 1 & 2.
|
||||
|
||||
# lwz r4, [r29 + 0x34] # TEffIndirectEditor
|
||||
# li r0, 1
|
||||
# stw [r4 + 0x38], r0
|
||||
# This editor is missing in PSO PC, but is present in PSOX. It appears to be
|
||||
# used for testing texture overlay effects, but it doesn't work properly in
|
||||
# Episode 3 - none of the effects appear to do anything. All three lines
|
||||
# This editor is missing in PSO PC, but is present in PSOX. It appears to be used for testing texture overlay
|
||||
# effects, but it doesn't work properly in Episode 3 - none of the effects appear to do anything. All three lines
|
||||
# above must be uncommented for it to load.
|
||||
|
||||
# lwz r4, [r29 + 0x38] # TCCScenarioDebug (movie/cutscene tests)
|
||||
# This editor exists only in Episode 3 - it is neither in PSOPC nor PSOX.
|
||||
# Nothing appears immediately after activating this debugger because the
|
||||
# default page is blank. Use C-left and C-right to change major pages; use
|
||||
# L/R to change minor pages (sets of 50 flags within each major page). Use
|
||||
# the D-pad to pick a flag and A to toggle it. On the "STAFFROLL" page, use
|
||||
# the D-pad to pick a movie, and R+A to play it. If you watch the movie to
|
||||
# the end, you'll return to your game and things will work as normal, but
|
||||
# the textures will likely have been overwritten with garbage data.
|
||||
# This editor exists only in Episode 3 - it is neither in PSOPC nor PSOX. Nothing appears immediately after
|
||||
# activating this debugger because the default page is blank. Use C-left and C-right to change major pages; use L/R
|
||||
# to change minor pages (sets of 50 flags within each major page). Use the D-pad to pick a flag and A to toggle it.
|
||||
# On the "STAFFROLL" page, use the D-pad to pick a movie, and R+A to play it. If you watch the movie to the end,
|
||||
# you'll return to your game and things will work as normal, but the textures will likely have been overwritten
|
||||
# with garbage data.
|
||||
|
||||
li r3, 0
|
||||
mr. r0, r4
|
||||
beq skip_enable_editor
|
||||
# Note: The PSO PC TObject structure is a bit different; the flags field is at
|
||||
# +8 instead of +4 (but it is still a 16-bit integer).
|
||||
# Note: The PSO PC TObject structure is a bit different; the flags field is at +8 instead of +4 (but it is still a
|
||||
# 16-bit integer).
|
||||
sth [r4 + 4], r3
|
||||
skip_enable_editor:
|
||||
|
||||
+2
-1
@@ -1,6 +1,7 @@
|
||||
# Original patch by Gigobooma
|
||||
# https://docs.google.com/document/d/1zG73l9joEqp_zB-xNgK9g8pXL0RSpmXfxPFQcdAvess/edit
|
||||
|
||||
.meta visibility="all"
|
||||
.meta name="Episode 3 Plus"
|
||||
.meta description="Enables Episode 3\nPlus features.\nDoes not include\ntext fixes.\n\nOriginally created\nby Gigobooma"
|
||||
|
||||
@@ -11,7 +12,7 @@ reloc0:
|
||||
.offsetof start
|
||||
|
||||
start:
|
||||
.include WriteCodeBlocksGC
|
||||
.include WriteCodeBlocks
|
||||
|
||||
# Disable curse word filter
|
||||
.data <VERS 0x8012FAD0 0x8012FA4C 0x8012FED8>
|
||||
+4
-3
@@ -1,11 +1,12 @@
|
||||
# This patch replaces the prices and contents of Pinz's Shop.
|
||||
# Each entry is structured as follows:
|
||||
# This patch replaces the prices and contents of Pinz's Shop. Each entry is structured as follows:
|
||||
# uint16_t card_id;
|
||||
# int16_t min_clv; // -1 = limit doesn't apply
|
||||
# int16_t max_clv; // -1 = limit doesn't apply
|
||||
# uint16_t relative_chance;
|
||||
# The values in the patch data below are the defaults.
|
||||
# Uncomment the .meta visibility line to make this appear in the Patches menu.
|
||||
|
||||
# .meta visibility="all"
|
||||
.meta name="New Pinz cards"
|
||||
.meta description="Replaces the cards\navailable in Pinz's\nShop"
|
||||
|
||||
@@ -16,7 +17,7 @@ reloc0:
|
||||
.offsetof start
|
||||
|
||||
start:
|
||||
.include WriteCodeBlocksGC
|
||||
.include WriteCodeBlocks
|
||||
|
||||
# Meseta prices
|
||||
.data <VERS 0x80487140 0x80487E80 0x8048A260>
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
.meta visibility="cheat"
|
||||
.meta name="VIP card"
|
||||
.meta description="Gives you a VIP card"
|
||||
.meta hide_from_patches_menu
|
||||
|
||||
.versions 3SJT 3SJ0 3SE0 3SP0
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
# This function implements $exit in a game when no quest is loaded.
|
||||
|
||||
.meta name="Exit anywhere"
|
||||
.meta description=""
|
||||
|
||||
|
||||
entry_ptr:
|
||||
reloc0:
|
||||
.offsetof start
|
||||
|
||||
|
||||
|
||||
.versions 2OJ5 2OJF 2OEF 2OPF
|
||||
start:
|
||||
mov r2, 0
|
||||
mova r0, [addrs]
|
||||
mov.l r1, [r0]
|
||||
mov.l [r1], r2
|
||||
mov.l r1, [r0 + 4]
|
||||
mov.l [r1], r2
|
||||
mov r2, 1
|
||||
mov.l r1, [r0 + 8]
|
||||
rets
|
||||
mov.w [r1], r2
|
||||
.align 4
|
||||
addrs:
|
||||
.data <VERS 0x8C4ED300 0x8C4E6DA0 0x8C4ED300 0x8C4DC800>
|
||||
.data <VERS 0x8C4ED344 0x8C4E6DE4 0x8C4ED344 0x8C4DC844>
|
||||
.data <VERS 0x8C4E8D88 0x8C4E2828 0x8C4E8D88 0x8C4D8288>
|
||||
|
||||
|
||||
|
||||
.versions 3OJ2 3OJ3 3OJ4 3OJ5 3OE0 3OE1 3OE2 3OP0
|
||||
start:
|
||||
li r0, 0
|
||||
stw [r13 - <VERS 0x4760 0x4758 0x4738 0x4738 0x4748 0x4748 0x4728 0x46E8>], r0
|
||||
stw [r13 - <VERS 0x475C 0x4754 0x4734 0x4734 0x4744 0x4744 0x4724 0x46E4>], r0
|
||||
li r0, 1
|
||||
sth [r13 - <VERS 0x4950 0x4948 0x4928 0x4928 0x4938 0x4938 0x4918 0x48D8>], r0
|
||||
blr
|
||||
|
||||
|
||||
|
||||
.versions 4OJB 4OJD 4OJU 4OED 4OEU 4OPD 4OPU
|
||||
start:
|
||||
xor eax, eax
|
||||
mov [<VERS 0x0062D374 0x0062D914 0x0063544C 0x00632934 0x006321CC 0x00632934 0x00632CCC>], eax # is_in_quest = false
|
||||
mov [<VERS 0x0062D370 0x0062D910 0x00635448 0x00632930 0x006321C8 0x00632930 0x00632CC8>], eax # dat_source_type = NONE
|
||||
inc eax
|
||||
mov [<VERS 0x0071E8E8 0x0071EF48 0x00726A88 0x00723F88 0x00723808 0x00723F88 0x00724308>], ax # should_leave_game = true
|
||||
ret
|
||||
|
||||
|
||||
|
||||
.versions 59NJ 59NL
|
||||
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,28 +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
|
||||
|
||||
.versions 2OJ5 2OJF 2OEF 2OPF
|
||||
|
||||
entry_ptr:
|
||||
reloc0:
|
||||
.offsetof start
|
||||
|
||||
start:
|
||||
mov r2, 0
|
||||
mova r0, [addrs]
|
||||
mov.l r1, [r0]
|
||||
mov.l [r1], r2
|
||||
mov.l r1, [r0 + 4]
|
||||
mov.l [r1], r2
|
||||
mov r2, 1
|
||||
mov.l r1, [r0 + 8]
|
||||
rets
|
||||
mov.w [r1], r2
|
||||
.align 4
|
||||
addrs:
|
||||
.data <VERS 0x8C4ED300 0x8C4E6DA0 0x8C4ED300 0x8C4DC800>
|
||||
.data <VERS 0x8C4ED344 0x8C4E6DE4 0x8C4ED344 0x8C4DC844>
|
||||
.data <VERS 0x8C4E8D88 0x8C4E2828 0x8C4E8D88 0x8C4D8288>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user