touhouSE.zip/touhouSE_src/nfa0_v2.cpp
#include "stdafx.h"
namespace NFA0_V2 {
namespace endian = boost::spirit::endian;
template<typename T>
class NFA0Base {
public:
unsigned char rotate(unsigned char c, unsigned int rawShift) {
const unsigned int shift = rawShift % 8;
return (c >> shift) + (c << (8 - shift));
}
void Decrypt() {
for (const unsigned int i : boost::irange(sizeof(T))) {
unsigned char &c = reinterpret_cast<unsigned char *>(this)[i];
c ^= 0x08;
}
}
};
#pragma pack(push, 1)
class Header : /* VC2010だと1byte無用なデータがくっつくためコメントアウト boost::noncopyable, */ public NFA0Base<Header> {
public:
char signature[4];
endian::ulittle16_t version;
endian::ulittle16_t fileCount;
endian::ulittle32_t type;
bool IsValid() const {
return std::equal(&this->signature[0], &this->signature[_countof(this->signature)], "NFA0") &&
this->version == 2;
}
};
class RawFileRecord17 : public NFA0Base<RawFileRecord17> /* std::vectorに入れるためコメントアウト , boost::noncopyable*/ {
public:
endian::ulittle32_t size;
endian::ulittle32_t address;
endian::ulittle32_t unknown;
char name[128];
bool IsValid() const {
return std::find(&this->name[0], &this->name[_countof(this->name)], '\0') != &this->name[_countof(this->name)];
}
const char *GetName(const unsigned int, const std::filesystem::path &) const {
return name;
}
const unsigned int GetSize(const unsigned int) const {
return size;
}
unsigned int GetOrigSize() const {
return size;
}
};
class RawFileRecord1 : public NFA0Base<RawFileRecord1> /* std::vectorに入れるためコメントアウト , boost::noncopyable*/ {
public:
endian::ulittle32_t size;
endian::ulittle32_t address;
endian::ulittle32_t unknown;
bool IsValid() const {
return true;
}
const std::filesystem::path GetName(const unsigned int index, const std::filesystem::path &path) const {
std::ostringstream temp;
temp << index;
std::filesystem::path result = temp.str();
if (path.extension() == ".snd") {
if (path.filename() == "jingle.snd" || path.filename() == "se.snd") {
result.replace_extension(".wav");
} else {
result.replace_extension(".ogg");
}
} else if (path.extension() == ".spk") {
result.replace_extension(".lua.lcd");
} else if (path.wstring().find(L".mov") != std::wstring::npos) {
result.replace_extension(".ogv");
} else {
result.replace_extension();
}
return result;
}
const unsigned int GetSize(const unsigned int) const {
return size;
}
unsigned int GetOrigSize() const {
return size;
}
};
class RawFileRecord5 : public NFA0Base<RawFileRecord5> /* std::vectorに入れるためコメントアウト , boost::noncopyable*/ {
public:
endian::ulittle32_t origSize;
endian::ulittle32_t address;
endian::ulittle32_t unknown;
bool IsValid() const {
return true;
}
const std::filesystem::path GetName(const unsigned int index, const std::filesystem::path &path) const {
std::ostringstream temp;
temp << index;
std::filesystem::path result = temp.str();
if (path.filename() == "font.cat") {
result.replace_extension(".bmf");
}
return result;
}
unsigned int GetSize(const unsigned int nextAddr) const {
return nextAddr - address;
}
unsigned int GetOrigSize() const {
return origSize;
}
};
class FileRecord /* std::vectorに入れるためコメントアウト : boost::noncopyable*/ {
public:
std::filesystem::path path;
unsigned int address;
unsigned int size;
bool crypt;
bool compress;
unsigned int origSize;
unsigned int index;
void Set(const std::filesystem::path &path, const unsigned int address, const unsigned int size, bool crypt) {
this->path = path;
this->address = address;
this->size = size;
this->crypt = crypt;
this->compress = false;
this->origSize = size;
}
void Set(const std::filesystem::path &path, const unsigned int address, const unsigned int size, bool crypt, unsigned int origSize, unsigned int index) {
this->path = path;
this->address = address;
this->size = size;
this->crypt = crypt;
this->compress = size != origSize;
this->origSize = origSize;
this->index = index;
}
};
#pragma pack(pop)
class Owner : public ExtractorBase {
private:
std::istream ∈
const std::shared_ptr<const std::vector<FileRecord> > fileList;
Owner(std::istream &in, const std::shared_ptr<const std::vector<FileRecord> > fileList) :
in(in), fileList(fileList)
{
}
public:
static std::shared_ptr<Owner> Open(std::istream &in, const unsigned long long int fileSize) {
std::shared_ptr<Owner> result;
if (!in.good()) {
return result;
}
Header header;
in.read(reinterpret_cast<char *>(&header), sizeof(header));
if (!in.good() || !header.IsValid()) {
return result;
}
const std::shared_ptr<std::vector<FileRecord> > fileList(new std::vector<FileRecord>());
if (!OpenImpl(in, 0, "", static_cast<unsigned int>(fileSize), false, *fileList)) {
return result;
}
return std::shared_ptr<Owner>(new Owner(in, fileList));
}
template<typename T>
static bool AddFileList(std::istream &in, const unsigned int baseAddr, const Header &header, const std::filesystem::path &path, const unsigned int fileSize, bool crypt, std::vector<FileRecord> &result) {
if (fileSize < sizeof(header) + static_cast<unsigned long long int>(sizeof(T)) * header.fileCount) {
return false;
}
std::vector<T> fileList(header.fileCount);
in.read(reinterpret_cast<char *>(&fileList.front()), sizeof(T) * header.fileCount);
if (!in.good()) {
return false;
}
const std::filesystem::path dir = std::filesystem::path(path).replace_extension();
for (unsigned int i = 0; i < fileList.size(); i++) {
T &file = fileList[i];
if (!crypt) {
file.Decrypt();
}
const unsigned int size = file.GetSize(fileList.size() == i + 1 ? fileSize : fileList[i+1].address);
if (!file.IsValid() || static_cast<unsigned long long int>(file.address) + size > fileSize) {
return false;
}
const std::filesystem::path name = file.GetName(i, path);
if (size != file.GetOrigSize()) {
if (!OpenCompressData(in, baseAddr + file.address, dir / name, size, !crypt, file.GetOrigSize(), result)) {
return false;
}
continue;
}
if (!OpenImpl(in, baseAddr + file.address, dir / name, size, !crypt, result)) {
return false;
}
}
return true;
}
static bool OpenImpl(std::istream &in, const unsigned int baseAddr, const std::filesystem::path &path, const unsigned int fileSize, bool crypt, std::vector<FileRecord> &result) {
if (fileSize < sizeof(Header)) {
result.resize(result.size() + 1);
result.back().Set(path, baseAddr, fileSize, crypt);
return true;
}
in.seekg(baseAddr, std::ios::beg);
Header header;
in.read(reinterpret_cast<char *>(&header), sizeof(header));
if (!in.good()) {
return false;
}
if (crypt) {
header.Decrypt();
}
if (!header.IsValid()) {
result.resize(result.size() + 1);
result.back().Set(path, baseAddr, fileSize, crypt);
return true;
}
if (header.fileCount == 0) {
return true;
}
switch(header.type) {
case 17:
return AddFileList<RawFileRecord17>(in, baseAddr, header, path, fileSize, crypt, result);
case 0:
return AddFileList<RawFileRecord1>(in, baseAddr, header, path, fileSize, !crypt, result);
case 1:
return AddFileList<RawFileRecord1>(in, baseAddr, header, path, fileSize, crypt, result);
case 5:
return AddFileList<RawFileRecord5>(in, baseAddr, header, path, fileSize, crypt, result);
default:
return false;
}
}
static bool OpenCompressData(std::istream &in, const unsigned int baseAddr, const std::filesystem::path &path, const unsigned int fileSize, bool crypt, const unsigned int origSize, std::vector<FileRecord> &result) {
if (fileSize == 0 || origSize == 0) {
return false;
}
std::vector<unsigned char> compData(fileSize);
in.read(reinterpret_cast<char *>(&compData.front()), compData.size());
if (!in.good()) {
return false;
}
if (crypt) {
for (unsigned char &c : compData) {
c ^= 0x08;
}
}
std::vector<unsigned char> origData(origSize);
unsigned long origSizeResult = origSize;
if (Z_OK != ::uncompress(&origData.front(), &origSizeResult, &compData.front(), compData.size()) || origSizeResult != origSize) {
return false;
}
if (!reinterpret_cast<Header *>(&result.front())->IsValid()) {
result.resize(result.size() + 1);
result.back().Set(path, baseAddr, fileSize, crypt, origSize, 0);
return true;
}
boost::iostreams::array_source source(reinterpret_cast<char *>(&compData.front()), compData.size());
boost::iostreams::stream<boost::iostreams::array_source> stream(source);
std::shared_ptr<Owner> owner = Owner::Open(stream, compData.size());
if (!owner) {
return false;
}
const std::filesystem::path dir = std::filesystem::path(path).remove_filename();
for (unsigned int i = 0; i < owner->GetSize(); i++) {
result.resize(result.size() + 1);
result.back().Set(dir / owner->GetFileName(i), baseAddr, fileSize, crypt, origSize, i);
}
return true;
}
bool Extract(const unsigned int index, std::vector<unsigned char> &result) {
if (index >= GetSize() || !in.good()) {
return false;
}
const FileRecord &file = fileList->at(index);
if (file.size == 0) {
result.clear();
return true;
}
result.resize(file.size);
in.seekg(file.address);
in.read(reinterpret_cast<char *>(&result.front()), result.size());
if (!in.good()) {
return false;
}
if (file.crypt) {
for (unsigned char &c : result) {
c ^= 0x08;
}
}
if (!file.compress) {
return true;
}
std::vector<unsigned char> temp(file.origSize);
unsigned long origSize = file.origSize;
if (Z_OK != ::uncompress(&temp.front(), &origSize, &result.front(), result.size())) {
return false;
}
std::swap(result, temp);
if (!reinterpret_cast<Header *>(&result.front())->IsValid()) {
return true;
}
std::vector<unsigned char> sourceImpl;
std::swap(sourceImpl, result);
boost::iostreams::array_source source(reinterpret_cast<char *>(&sourceImpl.front()), sourceImpl.size());
boost::iostreams::stream<boost::iostreams::array_source> stream(source);
std::shared_ptr<Owner> owner = Owner::Open(stream, sourceImpl.size());
if (!owner) {
return false;
}
if (owner->GetSize() <= file.index) {
return false;
}
return owner->Extract(file.index, result);
}
unsigned int GetSize() const {
return fileList->size();
}
std::wstring GetName() const {
return L"不思議の幻想郷2";
}
std::filesystem::path GetFileName(const unsigned int index) const {
return this->fileList->at(index).path;
}
};
ADD_DAT_EXTRACTOR(Owner);
} // NFA0_V2