touhouSE_th145BGMOnly.zip/touhouSE_src/dx_archive_v4.cpp
#include "stdafx.h"
namespace TouhouSE {
namespace DX_ARCHIVE_V4 {
namespace endian = boost::spirit::endian;
namespace {
void decrypt(const unsigned int size, const unsigned int addr, const std::array<unsigned char, 12> &key, unsigned char * const buf) {
unsigned int table_index = addr % key.size();
for (unsigned int i = 0; i < size; i++) {
buf[i] ^= key[table_index];
table_index = (table_index + 1) % key.size();
}
}
bool decompress(const std::vector<unsigned char> &in, std::vector<unsigned char> &out) {
if (in.size() < 9) {
return false;
}
const unsigned int out_size = *reinterpret_cast<const unsigned int *>(&in[0]);
const unsigned int in_size = *reinterpret_cast<const unsigned int *>(&in[4]);
if (in_size != in.size()) {
return false;
}
out.resize(out_size);
const unsigned char compress_flag = in[8];
for (unsigned int i = 9, out_index = 0; true;) {
if (in.size() <= i) {
if (i != in.size() || out_index != out.size()) {
return false;
}
break;
}
if (in[i] != compress_flag) {
if (out.size() <= out_index) {
return false;
}
out[out_index] = in[i];
i++;
out_index++;
continue;
}
if (in.size() <= i + 1) {
return false;
}
if (in[i+1] == compress_flag) {
if (out.size() <= out_index) {
return false;
}
out[out_index] = in[i];
i += 2;
out_index++;
continue;
}
if (in.size() <= i + 2) {
return false;
}
const unsigned char temp = (in[i+1] > compress_flag ? in[i+1] - 1 : in[i+1]);
const unsigned int index_size = (temp & 0x03) + 1;
const bool is_ex_length = ((temp & 0x04) != 0);
const unsigned int len = (is_ex_length ? in[i + 2] << 5 : 0) + (temp >> 3) + 4;
if (is_ex_length) {
i += 3;
} else {
i += 2;
}
const unsigned int index = 1 + (
index_size == 1 ?
in[i]
:
(index_size == 2 ?
*reinterpret_cast<const unsigned short *>(&in[i])
:
*reinterpret_cast<const unsigned int *>(&in[i]) & 0x00FFFFFF
)
);
i += index_size;
if (out_index < index) {
return false;
}
for (unsigned int j = 0; j < len; j++) {
out[out_index] = out[out_index - index];
out_index++;
}
}
return true;
}
class Header : boost::noncopyable {
public:
endian::ulittle8_t signature[2];
endian::ulittle16_t version;
endian::ulittle32_t footer_size;
endian::ulittle32_t header_size;
endian::ulittle32_t file_name_table_address;
endian::ulittle32_t file_data_table_offset;
endian::ulittle32_t directory_data_table_offset;
endian::ulittle32_t unknown;
bool isValid() const {
const char expected_signature[] = {0x44, 0x58};
return std::equal(expected_signature, &expected_signature[_countof(expected_signature)], signature)
&& version == 4 && header_size == sizeof(Header) && file_data_table_offset < footer_size
&& directory_data_table_offset < footer_size && directory_data_table_offset > file_data_table_offset;
}
};
class DirectoryData : boost::noncopyable {
public:
static const unsigned int directory_data_size = 16;
endian::ulittle32_t directory_data_offset;
endian::ulittle32_t parent_directory_offset;
endian::ulittle32_t file_count;
endian::ulittle32_t file_data_offset;
std::vector<boost::shared_ptr<DirectoryData> > child;
static boost::shared_ptr<DirectoryData> read(std::istream &in, const unsigned long long int file_size, const Header &header, const std::array<unsigned char, 12> &key) {
boost::shared_ptr<DirectoryData> result;
const unsigned int directory_data_footer_size = static_cast<unsigned int>(file_size) - header.file_name_table_address - header.directory_data_table_offset;
if (directory_data_footer_size == 0 || (directory_data_footer_size % directory_data_size) != 0) {
return result;
}
const unsigned int directory_count = directory_data_footer_size / directory_data_size;
const unsigned int base_addr = header.file_name_table_address + header.directory_data_table_offset;
in.seekg(base_addr, std::ios::beg);
if (!in.good()) {
return result;
}
std::map<unsigned int, boost::shared_ptr<DirectoryData> > data_list;
unsigned int read_size = 0;
for (unsigned int i = 0; i < directory_count; i++) {
const boost::shared_ptr<DirectoryData> data = boost::make_shared<DirectoryData>();
in.read(reinterpret_cast<char *>(data.get()), directory_data_size);
if (!in.good()) {
return result;
}
const unsigned int addr = base_addr + read_size;
decrypt(directory_data_size, addr, key, reinterpret_cast<unsigned char *>(data.get()));
data_list.insert(std::make_pair(addr, data));
read_size += directory_data_size;
}
typedef std::pair<unsigned int, boost::shared_ptr<DirectoryData> > DirectoryDataPair;
BOOST_FOREACH(const DirectoryDataPair &data, data_list) {
if (data.second->parent_directory_offset == 0xFFFFFFFF) {
continue;
}
const std::map<unsigned int, boost::shared_ptr<DirectoryData> >::iterator it = data_list.find(
header.file_name_table_address
+ header.directory_data_table_offset
+ data.second->parent_directory_offset);
if (it == data_list.end()) {
return result;
}
it->second->child.push_back(data.second);
}
result = data_list.begin()->second;
return result;
}
};
class FileData : boost::noncopyable {
public:
endian::ulittle32_t name_offset;
endian::ulittle32_t flag;
endian::ulittle64_t create_time;
endian::ulittle64_t last_access_time;
endian::ulittle64_t last_update_time;
endian::ulittle32_t body_offset;
endian::ulittle32_t body_size;
endian::ulittle32_t body_compress_size;
static boost::shared_ptr<std::map<unsigned int, boost::shared_ptr<const FileData> > > read(
std::istream &in,
const unsigned long long int file_size,
const Header &header,
const std::array<unsigned char, 12> &key)
{
boost::shared_ptr<std::map<unsigned int, boost::shared_ptr<const FileData> > > result;
const unsigned int file_data_footer_size = header.directory_data_table_offset - header.file_data_table_offset;
if (file_data_footer_size == 0 || (file_data_footer_size % sizeof(FileData)) != 0) {
return result;
}
const unsigned int file_data_count = file_data_footer_size / sizeof(FileData);
const unsigned int base_addr = header.file_name_table_address + header.file_data_table_offset;
in.seekg(base_addr, std::ios::beg);
if (!in.good()) {
return result;
}
const boost::shared_ptr<std::map<unsigned int, boost::shared_ptr<const FileData> > > data_list = boost::make_shared<std::map<unsigned int, boost::shared_ptr<const FileData> > >();
unsigned int read_size = 0;
for (unsigned int i = 0; i < file_data_count; i++) {
boost::shared_ptr<FileData> data = boost::make_shared<FileData>();
in.read(reinterpret_cast<char *>(data.get()), sizeof(FileData));
if (!in.good()) {
return result;
}
const unsigned int addr = base_addr + read_size;
decrypt(sizeof(FileData), addr, key, reinterpret_cast<unsigned char *>(data.get()));
data_list->insert(std::make_pair(addr, data));
read_size += sizeof(FileData);
}
result = data_list;
return result;
}
bool isDir() const {
return (flag & 16) != 0;
}
};
class Filename : boost::noncopyable {
private:
public:
const std::string name;
Filename() {
}
Filename(const char * const name) : name(name) {
}
static boost::shared_ptr<std::map<unsigned int, boost::shared_ptr<const Filename> > > read(
std::istream &in,
const unsigned long long int file_size,
const Header &header,
const std::array<unsigned char, 12> &key)
{
boost::shared_ptr<std::map<unsigned int, boost::shared_ptr<const Filename> > > result;
in.seekg(header.file_name_table_address, std::ios::beg);
if (!in.good()) {
return result;
}
const boost::shared_ptr<std::map<unsigned int, boost::shared_ptr<const Filename> > > data_list = boost::make_shared<std::map<unsigned int, boost::shared_ptr<const Filename> > >();
unsigned int read_size = 0;
while (read_size < header.file_data_table_offset) {
unsigned int length = 0;
in.read(reinterpret_cast<char *>(&length), 2);
if (!in.good()) {
return result;
}
const unsigned int addr = header.file_name_table_address + read_size;
decrypt(2, addr, key, reinterpret_cast<unsigned char *>(&length));
length = length * 4;
in.seekg(2, std::ios::cur);
read_size += 4;
if (length == 0) {
data_list->insert(std::make_pair(addr, boost::make_shared<const Filename>()));
continue;
}
std::vector<char> name(length);
in.seekg(name.size(), std::ios::cur);
read_size += name.size();
in.read(&name.front(), name.size());
decrypt(name.size(), header.file_name_table_address + read_size, key, reinterpret_cast<unsigned char *>(&name.front()));
read_size += name.size();
data_list->insert(std::make_pair(addr, boost::make_shared<const Filename>(&name.front())));
}
result = data_list;
return result;
}
};
class FileRecord : boost::noncopyable {
private:
static bool createListImpl(
const Header &header,
const DirectoryData &directory_data,
const std::map<unsigned int, boost::shared_ptr<const FileData> > &file_data_list,
const std::map<unsigned int, boost::shared_ptr<const Filename> > &filename_list,
std::vector<boost::shared_ptr<const FileRecord> > &out,
const boost::filesystem::path &cur = L"data")
{
for (unsigned int i = 0; i < directory_data.file_count; i++) {
const unsigned int file_data_addr =
header.file_name_table_address
+ header.file_data_table_offset
+ directory_data.file_data_offset
+ i * sizeof(FileData);
const std::map<unsigned int, boost::shared_ptr<const FileData> >::const_iterator file_data_it = file_data_list.find(file_data_addr);
if (file_data_it == file_data_list.end()) {
return false;
}
const FileData &file_data = *file_data_it->second;
if (file_data.isDir()) {
continue;
}
const unsigned int filename_addr = header.file_name_table_address + file_data.name_offset;
const std::map<unsigned int, boost::shared_ptr<const Filename> >::const_iterator filename_it = filename_list.find(filename_addr);
if (filename_it == filename_list.end()) {
return false;
}
const Filename &filename = *filename_it->second;
out.push_back(boost::make_shared<const FileRecord>(
sizeof(Header) + file_data.body_offset,
cur / filename.name,
file_data.body_size,
file_data.body_compress_size));
}
BOOST_FOREACH (const boost::shared_ptr<const DirectoryData> childImpl, directory_data.child) {
const DirectoryData &child = *childImpl;
const unsigned int file_data_addr = header.file_name_table_address + header.file_data_table_offset + child.directory_data_offset;
const std::map<unsigned int, boost::shared_ptr<const FileData> >::const_iterator file_data_it = file_data_list.find(file_data_addr);
if (file_data_it == file_data_list.end()) {
return false;
}
const FileData &file_data = *file_data_it->second;
const unsigned int filename_addr = header.file_name_table_address + file_data.name_offset;
const std::map<unsigned int, boost::shared_ptr<const Filename> >::const_iterator filename_it = filename_list.find(filename_addr);
if (filename_it == filename_list.end()) {
return false;
}
const Filename &filename = *filename_it->second;
if (!createListImpl(header, child, file_data_list, filename_list, out, cur / filename.name)) {
return false;
}
}
return true;
}
public:
const unsigned int addr;
const boost::filesystem::path path;
const unsigned int file_size;
const unsigned int file_compress_size;
FileRecord(const unsigned int addr, const boost::filesystem::path &path, const unsigned int file_size, const unsigned int file_compress_size) :
addr(addr), path(path), file_size(file_size), file_compress_size(file_compress_size)
{
}
static boost::shared_ptr<std::vector<boost::shared_ptr<const FileRecord> > > createList(
const Header &header,
const DirectoryData &directory_data,
const std::map<unsigned int, boost::shared_ptr<const FileData> > &file_data,
const std::map<unsigned int, boost::shared_ptr<const Filename> > &filename_list)
{
boost::shared_ptr<std::vector<boost::shared_ptr<const FileRecord> > > result;
const boost::shared_ptr<std::vector<boost::shared_ptr<const FileRecord> > > list = boost::make_shared<std::vector<boost::shared_ptr<const FileRecord> > >();
if (!createListImpl(header, directory_data, file_data, filename_list, *list)) {
return result;
}
result = list;
return result;
}
};
} // anonymous
class Owner : public ExtractorBase {
private:
std::istream ∈
const unsigned long long int file_size;
const std::array<unsigned char, 12> key;
const boost::shared_ptr<const Header> header;
const boost::shared_ptr<std::vector<boost::shared_ptr<const FileRecord> > > file_record_list;
Owner(
std::istream &in,
const unsigned long long int file_size,
const std::array<unsigned char, 12> key,
const boost::shared_ptr<const Header> header,
const boost::shared_ptr<std::vector<boost::shared_ptr<const FileRecord> > > file_record_list)
:
in(in), file_size(file_size), key(key), header(header), file_record_list(file_record_list) {
}
static bool readHeaderAndKey(
std::istream &in,
const unsigned long long int file_size,
boost::shared_ptr<Header> &header,
std::array<unsigned char, 12> &key)
{
if (file_size < sizeof(Header)) {
return false;
}
header.reset(new Header());
in.read(reinterpret_cast<char *>(header.get()), sizeof(Header));
if (!in.good()) {
return false;
}
*reinterpret_cast<unsigned int *>(&key[0]) = *reinterpret_cast<const unsigned int *>(&reinterpret_cast<const unsigned char *>(header.get())[0]) ^ 0x00045844;
*reinterpret_cast<unsigned int *>(&key[8]) = *reinterpret_cast<const unsigned int *>(&reinterpret_cast<const unsigned char *>(header.get())[8]) ^ 0x0000001C;
const unsigned int file_name_table_address = header->file_name_table_address ^ *reinterpret_cast<const unsigned int *>(&key[0]);
if (file_name_table_address > file_size) {
return false;
}
const unsigned int footer_size = static_cast<unsigned int>(file_size)-file_name_table_address;
*reinterpret_cast<unsigned int *>(&key[4]) = *reinterpret_cast<const unsigned int *>(&reinterpret_cast<const unsigned char *>(header.get())[4]) ^ footer_size;
decrypt(sizeof(Header), 0, key, reinterpret_cast<unsigned char *>(header.get()));
if (!header->isValid()) {
return false;
}
return header->file_name_table_address < file_size;
}
public:
static boost::shared_ptr<Owner> Open(std::istream &in, const unsigned long long int file_size) {
boost::shared_ptr<Owner> result;
std::array<unsigned char, 12> key;
boost::shared_ptr<Header> headerImpl;
if (!readHeaderAndKey(in, file_size, headerImpl, key)) {
return result;
}
boost::shared_ptr<const Header> header = headerImpl;
const boost::shared_ptr<DirectoryData> directory_data = DirectoryData::read(in, file_size, *header, key);
if (!directory_data) {
return result;
}
const boost::shared_ptr<std::map<unsigned int, boost::shared_ptr<const FileData> > > file_data = FileData::read(in, file_size, *header, key);
if (!file_data) {
return result;
}
const boost::shared_ptr<std::map<unsigned int, boost::shared_ptr<const Filename> > > filename_list = Filename::read(in, file_size, *header, key);
if (!filename_list) {
return result;
}
const boost::shared_ptr<std::vector<boost::shared_ptr<const FileRecord> > > file_record_list = FileRecord::createList(
*header,
*directory_data,
*file_data,
*filename_list);
if (!file_record_list) {
return result;
}
result.reset(new Owner(in, file_size, key, header, file_record_list));
return result;
}
bool Extract(const unsigned int index, std::vector<unsigned char> &result) {
const FileRecord &file = *(*file_record_list)[index];
in.seekg(file.addr, std::ios::beg);
if (!in.good()) {
return false;
}
bool isCompress = (file.file_compress_size != 0xFFFFFFFF);
const unsigned int read_size = (isCompress ? file.file_compress_size : file.file_size);
result.resize(read_size);
if (result.size() == 0) {
return true;
}
in.read(reinterpret_cast<char *>(&result.front()), read_size);
if (!in.good()) {
return false;
}
decrypt(result.size(), file.addr, key, reinterpret_cast<unsigned char *>(&result.front()));
if (isCompress) {
std::vector<unsigned char> temp;
if (!decompress(result, temp)) {
return false;
}
result.swap(temp);
}
return true;
}
unsigned int GetSize() const {
return file_record_list->size();
}
std::wstring GetName() const {
return L"DxArchiveV4";
}
boost::filesystem::path GetFileName(unsigned int index) const {
return (*file_record_list)[index]->path;
}
};
ADD_DAT_EXTRACTOR(Owner);
} // DX_ARCHIVE_V4
} // ToudouSE