add extract-bml action
This commit is contained in:
@@ -0,0 +1,105 @@
|
||||
#include "BMLArchive.hh"
|
||||
|
||||
#include <phosg/Encoding.hh>
|
||||
#include <phosg/Filesystem.hh>
|
||||
#include <stdexcept>
|
||||
|
||||
#include "Text.hh"
|
||||
|
||||
using namespace std;
|
||||
|
||||
|
||||
|
||||
template <typename LongT>
|
||||
struct BMLHeader {
|
||||
parray<uint8_t, 0x04> unknown_a1;
|
||||
LongT num_entries;
|
||||
parray<uint8_t, 0x38> unknown_a2;
|
||||
} __attribute__((packed));
|
||||
|
||||
template <typename LongT>
|
||||
struct BMLHeaderEntry {
|
||||
ptext<char, 0x20> filename;
|
||||
LongT compressed_size;
|
||||
parray<uint8_t, 0x04> unknown_a1;
|
||||
LongT decompressed_size;
|
||||
LongT compressed_gvm_size;
|
||||
LongT decompressed_gvm_size;
|
||||
parray<uint8_t, 0x0C> unknown_a2;
|
||||
} __attribute__((packed));
|
||||
|
||||
template <typename LongT>
|
||||
void BMLArchive::load_t() {
|
||||
StringReader r(*this->data);
|
||||
|
||||
const auto& header = r.get<BMLHeader<LongT>>();
|
||||
|
||||
size_t offset = 0x800;
|
||||
while (this->entries.size() < header.num_entries) {
|
||||
const auto& entry = r.get<BMLHeaderEntry<LongT>>();
|
||||
|
||||
if (offset + entry.compressed_size > this->data->size()) {
|
||||
throw runtime_error("BML data entry extends beyond end of data");
|
||||
}
|
||||
size_t data_offset = offset;
|
||||
offset = (offset + entry.compressed_size + 0x1F) & (~0x1F);
|
||||
|
||||
if (offset + entry.compressed_gvm_size > this->data->size()) {
|
||||
throw runtime_error("BML GVM entry extends beyond end of data");
|
||||
}
|
||||
size_t gvm_offset = offset;
|
||||
offset = (offset + entry.compressed_gvm_size + 0x1F) & (~0x1F);
|
||||
|
||||
this->entries.emplace(entry.filename, Entry{
|
||||
data_offset, entry.compressed_size, gvm_offset, entry.compressed_gvm_size});
|
||||
}
|
||||
}
|
||||
|
||||
BMLArchive::BMLArchive(shared_ptr<const string> data, bool big_endian)
|
||||
: data(data) {
|
||||
if (big_endian) {
|
||||
this->load_t<be_uint32_t>();
|
||||
} else {
|
||||
this->load_t<le_uint32_t>();
|
||||
}
|
||||
}
|
||||
|
||||
const unordered_map<string, BMLArchive::Entry> BMLArchive::all_entries() const {
|
||||
return this->entries;
|
||||
}
|
||||
|
||||
pair<const void*, size_t> BMLArchive::get(const std::string& name) const {
|
||||
try {
|
||||
const auto& entry = this->entries.at(name);
|
||||
return make_pair(this->data->data() + entry.offset, entry.size);
|
||||
} catch (const out_of_range&) {
|
||||
throw out_of_range("BML does not contain file: " + name);
|
||||
}
|
||||
}
|
||||
|
||||
pair<const void*, size_t> BMLArchive::get_gvm(const std::string& name) const {
|
||||
try {
|
||||
const auto& entry = this->entries.at(name);
|
||||
return make_pair(this->data->data() + entry.gvm_offset, entry.gvm_size);
|
||||
} catch (const out_of_range&) {
|
||||
throw out_of_range("BML does not contain file: " + name);
|
||||
}
|
||||
}
|
||||
|
||||
string BMLArchive::get_copy(const string& name) const {
|
||||
try {
|
||||
const auto& entry = this->entries.at(name);
|
||||
return this->data->substr(entry.offset, entry.size);
|
||||
} catch (const out_of_range&) {
|
||||
throw out_of_range("BML does not contain file: " + name);
|
||||
}
|
||||
}
|
||||
|
||||
StringReader BMLArchive::get_reader(const string& name) const {
|
||||
try {
|
||||
const auto& entry = this->entries.at(name);
|
||||
return StringReader(this->data->data() + entry.offset, entry.size);
|
||||
} catch (const out_of_range&) {
|
||||
throw out_of_range("BML does not contain file: " + name);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <memory>
|
||||
#include <phosg/Filesystem.hh>
|
||||
#include <phosg/Strings.hh>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
|
||||
|
||||
class BMLArchive {
|
||||
public:
|
||||
BMLArchive(std::shared_ptr<const std::string> data, bool big_endian);
|
||||
~BMLArchive() = default;
|
||||
|
||||
struct Entry {
|
||||
uint64_t offset;
|
||||
uint32_t size;
|
||||
uint64_t gvm_offset;
|
||||
uint32_t gvm_size;
|
||||
};
|
||||
const std::unordered_map<std::string, Entry> all_entries() const;
|
||||
|
||||
std::pair<const void*, size_t> get(const std::string& name) const;
|
||||
std::pair<const void*, size_t> get_gvm(const std::string& name) const;
|
||||
std::string get_copy(const std::string& name) const;
|
||||
StringReader get_reader(const std::string& name) const;
|
||||
|
||||
private:
|
||||
template <typename LongT>
|
||||
void load_t();
|
||||
|
||||
std::shared_ptr<const std::string> data;
|
||||
|
||||
std::unordered_map<std::string, Entry> entries;
|
||||
};
|
||||
+42
-10
@@ -13,9 +13,11 @@
|
||||
#include <thread>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "BMLArchive.hh"
|
||||
#include "CatSession.hh"
|
||||
#include "Compression.hh"
|
||||
#include "DNSServer.hh"
|
||||
#include "GSLArchive.hh"
|
||||
#include "IPStackSimulator.hh"
|
||||
#include "Loggers.hh"
|
||||
#include "NetworkAddresses.hh"
|
||||
@@ -168,10 +170,11 @@ The actions are:\n\
|
||||
sure client sessions are repeatable and code changes don\'t affect existing\n\
|
||||
(working) functionality.\n\
|
||||
extract-gsl [INPUT-FILENAME] [--big-endian]\n\
|
||||
Extract all files from a GSL archive into the current directory.\n\
|
||||
extract-bml [INPUT-FILENAME] [--big-endian]\n\
|
||||
Extract all files from a GSL or BML archive into the current directory.\n\
|
||||
input-filename may be specified. If output-filename is specified, then it\n\
|
||||
is treated as a prefix which is prepended to the filename of each file\n\
|
||||
contained in the GSL archive. If --big-endian is given, the GSL header is\n\
|
||||
contained in the archive. If --big-endian is given, the archive header is\n\
|
||||
read in GameCube format; otherwise it is read in PC/BB format.\n\
|
||||
\n\
|
||||
A few options apply to multiple modes described above:\n\
|
||||
@@ -198,6 +201,7 @@ enum class Behavior {
|
||||
DECODE_QUEST_FILE,
|
||||
DECODE_SJIS,
|
||||
EXTRACT_GSL,
|
||||
EXTRACT_BML,
|
||||
FORMAT_ITEMRT_ENTRY,
|
||||
FORMAT_ITEMRT_REL,
|
||||
SHOW_EP3_DATA,
|
||||
@@ -221,6 +225,7 @@ static bool behavior_takes_input_filename(Behavior b) {
|
||||
(b == Behavior::FORMAT_ITEMRT_ENTRY) ||
|
||||
(b == Behavior::FORMAT_ITEMRT_REL) ||
|
||||
(b == Behavior::EXTRACT_GSL) ||
|
||||
(b == Behavior::EXTRACT_BML) ||
|
||||
(b == Behavior::PARSE_OBJECT_GRAPH) ||
|
||||
(b == Behavior::REPLAY_LOG) ||
|
||||
(b == Behavior::CAT_CLIENT);
|
||||
@@ -234,7 +239,9 @@ static bool behavior_takes_output_filename(Behavior b) {
|
||||
(b == Behavior::ENCRYPT_DATA) ||
|
||||
(b == Behavior::DECRYPT_DATA) ||
|
||||
(b == Behavior::DECRYPT_TRIVIAL_DATA) ||
|
||||
(b == Behavior::DECODE_SJIS);
|
||||
(b == Behavior::DECODE_SJIS) ||
|
||||
(b == Behavior::EXTRACT_GSL) ||
|
||||
(b == Behavior::EXTRACT_BML);
|
||||
}
|
||||
|
||||
enum class QuestFileFormat {
|
||||
@@ -360,6 +367,8 @@ int main(int argc, char** argv) {
|
||||
behavior = Behavior::REPLAY_LOG;
|
||||
} else if (!strcmp(argv[x], "extract-gsl")) {
|
||||
behavior = Behavior::EXTRACT_GSL;
|
||||
} else if (!strcmp(argv[x], "extract-bml")) {
|
||||
behavior = Behavior::EXTRACT_BML;
|
||||
} else {
|
||||
throw invalid_argument(string_printf("unknown command: %s (try --help)", argv[x]));
|
||||
}
|
||||
@@ -672,7 +681,8 @@ int main(int argc, char** argv) {
|
||||
break;
|
||||
}
|
||||
|
||||
case Behavior::EXTRACT_GSL: {
|
||||
case Behavior::EXTRACT_GSL:
|
||||
case Behavior::EXTRACT_BML: {
|
||||
if (!output_filename) {
|
||||
output_filename = "";
|
||||
} else if (!strcmp(output_filename, "-")) {
|
||||
@@ -680,13 +690,35 @@ int main(int argc, char** argv) {
|
||||
}
|
||||
|
||||
string data = read_input_data();
|
||||
|
||||
shared_ptr<string> data_shared(new string(move(data)));
|
||||
GSLArchive gsl(data_shared, big_endian);
|
||||
for (const auto& entry_it : gsl.all_entries()) {
|
||||
auto e = gsl.get(entry_it.first);
|
||||
save_file(output_filename + entry_it.first, e.first, e.second);
|
||||
fprintf(stderr, "... %s\n", entry_it.first.c_str());
|
||||
|
||||
if (behavior == Behavior::EXTRACT_GSL) {
|
||||
GSLArchive arch(data_shared, big_endian);
|
||||
for (const auto& entry_it : arch.all_entries()) {
|
||||
auto e = arch.get(entry_it.first);
|
||||
string out_file = output_filename + entry_it.first;
|
||||
save_file(out_file.c_str(), e.first, e.second);
|
||||
fprintf(stderr, "... %s\n", out_file.c_str());
|
||||
}
|
||||
} else {
|
||||
BMLArchive arch(data_shared, big_endian);
|
||||
for (const auto& entry_it : arch.all_entries()) {
|
||||
{
|
||||
auto e = arch.get(entry_it.first);
|
||||
string data = prs_decompress(e.first, e.second);
|
||||
string out_file = output_filename + entry_it.first;
|
||||
save_file(out_file, data);
|
||||
fprintf(stderr, "... %s\n", out_file.c_str());
|
||||
}
|
||||
|
||||
auto gvm_e = arch.get_gvm(entry_it.first);
|
||||
if (gvm_e.second) {
|
||||
string data = prs_decompress(gvm_e.first, gvm_e.second);
|
||||
string out_file = output_filename + entry_it.first + ".gvm";
|
||||
save_file(out_file, data);
|
||||
fprintf(stderr, "... %s\n", out_file.c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user