add ItemRT.afs decoder
This commit is contained in:
@@ -40,6 +40,7 @@ find_package(resource_file QUIET)
|
||||
# Executable definition
|
||||
|
||||
add_executable(newserv
|
||||
src/AFSArchive.cc
|
||||
src/BattleParamsIndex.cc
|
||||
src/BMLArchive.cc
|
||||
src/CatSession.cc
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
#include "AFSArchive.hh"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <phosg/Encoding.hh>
|
||||
#include <phosg/Filesystem.hh>
|
||||
#include <phosg/Strings.hh>
|
||||
|
||||
using namespace std;
|
||||
|
||||
AFSArchive::AFSArchive(std::shared_ptr<const std::string> data)
|
||||
: data(data) {
|
||||
struct FileHeader {
|
||||
be_uint32_t magic;
|
||||
le_uint32_t num_files;
|
||||
} __attribute__((packed));
|
||||
|
||||
struct FileEntry {
|
||||
le_uint32_t offset;
|
||||
le_uint32_t size;
|
||||
} __attribute__((packed));
|
||||
|
||||
StringReader r(*this->data);
|
||||
const auto& header = r.get<FileHeader>();
|
||||
if (header.magic != 0x41465300) {
|
||||
throw runtime_error("file is not an AFS archive");
|
||||
}
|
||||
|
||||
while (this->entries.size() < header.num_files) {
|
||||
const auto& entry = r.get<FileEntry>();
|
||||
this->entries.emplace_back(Entry{.offset = entry.offset, .size = entry.size});
|
||||
}
|
||||
}
|
||||
|
||||
std::pair<const void*, size_t> AFSArchive::get(size_t index) const {
|
||||
const auto& entry = this->entries.at(index);
|
||||
if (entry.offset > this->data->size()) {
|
||||
throw out_of_range("entry begins beyond end of archive");
|
||||
}
|
||||
if (entry.offset + entry.size > this->data->size()) {
|
||||
throw out_of_range("entry extends beyond end of archive");
|
||||
}
|
||||
|
||||
return make_pair(this->data->data() + entry.offset, entry.size);
|
||||
}
|
||||
|
||||
std::string AFSArchive::get_copy(size_t index) const {
|
||||
auto ret = this->get(index);
|
||||
return string(reinterpret_cast<const char*>(ret.first), ret.second);
|
||||
}
|
||||
|
||||
StringReader AFSArchive::get_reader(size_t index) const {
|
||||
auto ret = this->get(index);
|
||||
return StringReader(ret.first, ret.second);
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <memory>
|
||||
#include <phosg/Filesystem.hh>
|
||||
#include <phosg/Strings.hh>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
class AFSArchive {
|
||||
public:
|
||||
AFSArchive(std::shared_ptr<const std::string> data);
|
||||
~AFSArchive() = default;
|
||||
|
||||
struct Entry {
|
||||
uint64_t offset;
|
||||
uint32_t size;
|
||||
};
|
||||
inline const std::vector<Entry>& all_entries() const {
|
||||
return this->entries;
|
||||
}
|
||||
|
||||
std::pair<const void*, size_t> get(size_t index) const;
|
||||
std::string get_copy(size_t index) const;
|
||||
StringReader get_reader(size_t index) const;
|
||||
|
||||
private:
|
||||
std::shared_ptr<const std::string> data;
|
||||
std::vector<Entry> entries;
|
||||
};
|
||||
+42
-5
@@ -317,6 +317,7 @@ enum class Behavior {
|
||||
FORMAT_RARE_ITEM_SET,
|
||||
CONVERT_ITEMRT_REL_TO_JSON,
|
||||
CONVERT_ITEMRT_GSL_TO_JSON,
|
||||
CONVERT_ITEMRT_AFS_TO_JSON,
|
||||
SHOW_EP3_MAPS,
|
||||
SHOW_EP3_CARDS,
|
||||
GENERATE_EP3_CARDS_HTML,
|
||||
@@ -365,6 +366,7 @@ static bool behavior_takes_input_filename(Behavior b) {
|
||||
(b == Behavior::FORMAT_RARE_ITEM_SET) ||
|
||||
(b == Behavior::CONVERT_ITEMRT_REL_TO_JSON) ||
|
||||
(b == Behavior::CONVERT_ITEMRT_GSL_TO_JSON) ||
|
||||
(b == Behavior::CONVERT_ITEMRT_AFS_TO_JSON) ||
|
||||
(b == Behavior::EXTRACT_GSL) ||
|
||||
(b == Behavior::EXTRACT_BML) ||
|
||||
(b == Behavior::DECODE_TEXT_ARCHIVE) ||
|
||||
@@ -403,6 +405,7 @@ static bool behavior_takes_output_filename(Behavior b) {
|
||||
(b == Behavior::DISASSEMBLE_QUEST_SCRIPT) ||
|
||||
(b == Behavior::CONVERT_ITEMRT_REL_TO_JSON) ||
|
||||
(b == Behavior::CONVERT_ITEMRT_GSL_TO_JSON) ||
|
||||
(b == Behavior::CONVERT_ITEMRT_AFS_TO_JSON) ||
|
||||
(b == Behavior::DECODE_SJIS) ||
|
||||
(b == Behavior::EXTRACT_GSL) ||
|
||||
(b == Behavior::EXTRACT_BML) ||
|
||||
@@ -435,6 +438,7 @@ int main(int argc, char** argv) {
|
||||
bool json = false;
|
||||
bool download = false;
|
||||
bool one_line = false;
|
||||
bool names = false;
|
||||
const char* find_decryption_seed_ciphertext = nullptr;
|
||||
vector<const char*> find_decryption_seed_plaintexts;
|
||||
const char* input_filename = nullptr;
|
||||
@@ -452,6 +456,8 @@ int main(int argc, char** argv) {
|
||||
num_threads = strtoull(&argv[x][10], nullptr, 0);
|
||||
} else if (!strcmp(argv[x], "--one-line")) {
|
||||
one_line = true;
|
||||
} else if (!strcmp(argv[x], "--names")) {
|
||||
names = true;
|
||||
} else if (!strcmp(argv[x], "--download")) {
|
||||
download = true;
|
||||
} else if (!strcmp(argv[x], "--patch")) {
|
||||
@@ -619,6 +625,8 @@ int main(int argc, char** argv) {
|
||||
behavior = Behavior::CONVERT_ITEMRT_REL_TO_JSON;
|
||||
} else if (!strcmp(argv[x], "convert-itemrt-gsl-to-json")) {
|
||||
behavior = Behavior::CONVERT_ITEMRT_GSL_TO_JSON;
|
||||
} else if (!strcmp(argv[x], "convert-itemrt-afs-to-json")) {
|
||||
behavior = Behavior::CONVERT_ITEMRT_AFS_TO_JSON;
|
||||
} else if (!strcmp(argv[x], "show-ep3-maps")) {
|
||||
behavior = Behavior::SHOW_EP3_MAPS;
|
||||
} else if (!strcmp(argv[x], "show-ep3-cards")) {
|
||||
@@ -724,7 +732,9 @@ int main(int argc, char** argv) {
|
||||
filename += ".json";
|
||||
} else if (behavior == Behavior::DISASSEMBLE_QUEST_SCRIPT) {
|
||||
filename += ".txt";
|
||||
} else if ((behavior == Behavior::CONVERT_ITEMRT_REL_TO_JSON) || (behavior == Behavior::CONVERT_ITEMRT_GSL_TO_JSON)) {
|
||||
} else if ((behavior == Behavior::CONVERT_ITEMRT_REL_TO_JSON) ||
|
||||
(behavior == Behavior::CONVERT_ITEMRT_GSL_TO_JSON) ||
|
||||
(behavior == Behavior::CONVERT_ITEMRT_AFS_TO_JSON)) {
|
||||
filename += ".json";
|
||||
} else {
|
||||
filename += ".dec";
|
||||
@@ -734,7 +744,8 @@ int main(int argc, char** argv) {
|
||||
} else if (isatty(fileno(stdout)) &&
|
||||
(behavior != Behavior::DISASSEMBLE_QUEST_SCRIPT) &&
|
||||
(behavior != Behavior::CONVERT_ITEMRT_REL_TO_JSON) &&
|
||||
(behavior != Behavior::CONVERT_ITEMRT_GSL_TO_JSON)) {
|
||||
(behavior != Behavior::CONVERT_ITEMRT_GSL_TO_JSON) &&
|
||||
(behavior != Behavior::CONVERT_ITEMRT_AFS_TO_JSON)) {
|
||||
// If stdout is a terminal and the data is not known to be text, use
|
||||
// print_data to write the result
|
||||
print_data(stdout, data, size);
|
||||
@@ -1570,11 +1581,14 @@ int main(int argc, char** argv) {
|
||||
}
|
||||
|
||||
case Behavior::CONVERT_ITEMRT_REL_TO_JSON:
|
||||
case Behavior::CONVERT_ITEMRT_GSL_TO_JSON: {
|
||||
case Behavior::CONVERT_ITEMRT_GSL_TO_JSON:
|
||||
case Behavior::CONVERT_ITEMRT_AFS_TO_JSON: {
|
||||
shared_ptr<string> data(new string(read_input_data()));
|
||||
unique_ptr<RareItemSet> rs;
|
||||
if (behavior == Behavior::CONVERT_ITEMRT_GSL_TO_JSON) {
|
||||
rs.reset(new GSLRareItemSet(data, big_endian));
|
||||
} else if (behavior == Behavior::CONVERT_ITEMRT_AFS_TO_JSON) {
|
||||
rs.reset(new AFSRareItemSet(data));
|
||||
} else {
|
||||
rs.reset(new RELRareItemSet(data));
|
||||
}
|
||||
@@ -1625,8 +1639,19 @@ int main(int argc, char** argv) {
|
||||
continue;
|
||||
}
|
||||
|
||||
JSON id_json;
|
||||
if (names) {
|
||||
ItemData data;
|
||||
data.data1[0] = spec.item_code[0];
|
||||
data.data1[1] = spec.item_code[1];
|
||||
data.data1[2] = spec.item_code[2];
|
||||
id_json = data.name(false);
|
||||
} else {
|
||||
id_json = primary_identifier;
|
||||
}
|
||||
|
||||
auto frac = reduce_fraction<uint64_t>(spec.probability, 0x100000000);
|
||||
auto specs_json = JSON::list({JSON::list({string_printf("%" PRIu64 "/%" PRIu64, frac.first, frac.second), primary_identifier})});
|
||||
auto specs_json = JSON::list({JSON::list({string_printf("%" PRIu64 "/%" PRIu64, frac.first, frac.second), std::move(id_json)})});
|
||||
for (const auto& enemy_type : enemy_types) {
|
||||
if (enemy_type_valid_for_episode(episode, enemy_type)) {
|
||||
collection_dict.emplace(name_for_enum(enemy_type), std::move(specs_json));
|
||||
@@ -1643,8 +1668,20 @@ int main(int argc, char** argv) {
|
||||
if (primary_identifier == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
JSON id_json;
|
||||
if (names) {
|
||||
ItemData data;
|
||||
data.data1[0] = spec.item_code[0];
|
||||
data.data1[1] = spec.item_code[1];
|
||||
data.data1[2] = spec.item_code[2];
|
||||
id_json = data.name(false);
|
||||
} else {
|
||||
id_json = primary_identifier;
|
||||
}
|
||||
|
||||
auto frac = reduce_fraction<uint64_t>(spec.probability, 0x100000000);
|
||||
area_list.emplace_back(JSON::list({string_printf("%" PRIu64 "/%" PRIu64, frac.first, frac.second), primary_identifier}));
|
||||
area_list.emplace_back(JSON::list({string_printf("%" PRIu64 "/%" PRIu64, frac.first, frac.second), std::move(id_json)}));
|
||||
}
|
||||
|
||||
if (!area_list.empty()) {
|
||||
|
||||
+41
-1
@@ -88,13 +88,53 @@ uint16_t RareItemSet::key_for_params(GameMode mode, Episode episode, uint8_t dif
|
||||
return key;
|
||||
}
|
||||
|
||||
AFSRareItemSet::AFSRareItemSet(shared_ptr<const string> data)
|
||||
: afs(data) {
|
||||
const array<GameMode, 4> modes = {GameMode::NORMAL, GameMode::BATTLE, GameMode::CHALLENGE, GameMode::SOLO};
|
||||
for (GameMode mode : modes) {
|
||||
for (size_t difficulty = 0; difficulty < 4; difficulty++) {
|
||||
for (size_t section_id = 0; section_id < 10; section_id++) {
|
||||
try {
|
||||
size_t index = difficulty * 10 + section_id;
|
||||
auto entry = this->afs.get(index);
|
||||
if (entry.second < sizeof(Table)) {
|
||||
throw runtime_error(string_printf("table %zu is too small", index));
|
||||
}
|
||||
this->tables.emplace(
|
||||
this->key_for_params(mode, Episode::EP1, difficulty, section_id),
|
||||
reinterpret_cast<const Table*>(entry.first));
|
||||
} catch (const out_of_range&) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<RareItemSet::ExpandedDrop> AFSRareItemSet::get_enemy_specs(
|
||||
GameMode mode, Episode episode, uint8_t difficulty, uint8_t secid, uint8_t rt_index) const {
|
||||
try {
|
||||
return this->tables.at(this->key_for_params(mode, episode, difficulty, secid))->get_enemy_specs(rt_index);
|
||||
} catch (const out_of_range&) {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<RareItemSet::ExpandedDrop> AFSRareItemSet::get_box_specs(
|
||||
GameMode mode, Episode episode, uint8_t difficulty, uint8_t secid, uint8_t area) const {
|
||||
try {
|
||||
return this->tables.at(this->key_for_params(mode, episode, difficulty, secid))->get_box_specs(area);
|
||||
} catch (const out_of_range&) {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
GSLRareItemSet::GSLRareItemSet(shared_ptr<const string> data, bool is_big_endian)
|
||||
: gsl(data, is_big_endian) {
|
||||
const array<Episode, 2> episodes = {Episode::EP1, Episode::EP2};
|
||||
const array<GameMode, 4> modes = {GameMode::NORMAL, GameMode::BATTLE, GameMode::CHALLENGE, GameMode::SOLO};
|
||||
for (GameMode mode : modes) {
|
||||
for (Episode episode : episodes) {
|
||||
for (size_t difficulty = 0; difficulty < 3; difficulty++) {
|
||||
for (size_t difficulty = 0; difficulty < 4; difficulty++) {
|
||||
for (size_t section_id = 0; section_id < 10; section_id++) {
|
||||
string filename = string_printf("ItemRT%s%s%c%1zu.rel",
|
||||
((mode == GameMode::CHALLENGE) ? "c" : ""),
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include <random>
|
||||
#include <string>
|
||||
|
||||
#include "AFSArchive.hh"
|
||||
#include "GSLArchive.hh"
|
||||
#include "StaticGameData.hh"
|
||||
|
||||
@@ -64,6 +65,19 @@ protected:
|
||||
static uint16_t key_for_params(GameMode mode, Episode episode, uint8_t difficulty, uint8_t secid);
|
||||
};
|
||||
|
||||
class AFSRareItemSet : public RareItemSet {
|
||||
public:
|
||||
AFSRareItemSet(std::shared_ptr<const std::string> data);
|
||||
virtual ~AFSRareItemSet() = default;
|
||||
virtual std::vector<ExpandedDrop> get_enemy_specs(GameMode mode, Episode episode, uint8_t difficulty, uint8_t secid, uint8_t rt_index) const;
|
||||
virtual std::vector<ExpandedDrop> get_box_specs(GameMode mode, Episode episode, uint8_t difficulty, uint8_t secid, uint8_t area) const;
|
||||
|
||||
private:
|
||||
std::unordered_map<uint16_t, const Table*> tables;
|
||||
|
||||
AFSArchive afs;
|
||||
};
|
||||
|
||||
class GSLRareItemSet : public RareItemSet {
|
||||
public:
|
||||
GSLRareItemSet(std::shared_ptr<const std::string> data, bool is_big_endian);
|
||||
|
||||
Reference in New Issue
Block a user