#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <cstring>
#include <system_error>
#include <stdexcept>
#include <vector>
#include <memory>
#include <cstddef>
#include <cstdint>
#include <cstdlib>
#include "json.hpp"
using json = nlohmann::json;
struct istream {
inline virtual ~istream() noexcept = default;
virtual void read(void * buffer, size_t amount) = 0;
template<typename T> T read();
};
struct ostream {
inline virtual ~ostream() noexcept = default;
virtual void write(const void * buffer, size_t amount) = 0;
template<typename T> void write(const T &);
};
struct iostream : istream, ostream {
inline virtual ~iostream() noexcept = default;
};
template<> inline uint8_t istream::read<uint8_t>() {
uint8_t tmp;
read(&tmp, sizeof(tmp));
return tmp;
}
template<> inline uint32_t istream::read<uint32_t>() {
uint32_t tmp;
read(&tmp, sizeof(tmp));
return tmp;
}
class filestream : public iostream {
public:
filestream(const filestream &) = delete;
filestream & operator=(const filestream &) = delete;
static filestream * open(const std::string & filename) {
const auto fd = ::open(filename.c_str(), O_RDONLY);
if (fd < 0) {
throw std::system_error(errno, std:: system_category());
}
return new filestream(fd);
}
static filestream * create(const std::string & filename) {
const auto fd = ::open(filename.c_str(), O_RDWR | O_CREAT | O_TRUNC, 0666);
if (fd < 0) {
throw std::system_error(errno, std:: system_category());
}
return new filestream(fd);
}
~filestream() noexcept {
::close(m_fd);
}
void read(void * buffer, size_t amount) override {
if (::read(m_fd, buffer, amount) != ssize_t(amount)) {
throw std::system_error(errno, std:: system_category());
}
}
void write(const void * buffer, size_t amount) override {
if (::write(m_fd, buffer, amount) != ssize_t(amount)) {
throw std::system_error(errno, std:: system_category());
}
}
protected:
inline explicit filestream(int fd) noexcept : m_fd(fd) {}
int m_fd;
};
void make_containing_folder(const std::string & filename) {
for (
size_t i = 0, end = filename.find('/', 0);
end != std::string::npos;
i = end + 1, end = filename.find('/', i)
) {
const std::string path(filename, 0, end);
if (mkdir(path.c_str(), 0755) != 0) {
if (errno != EEXIST) {
throw std::system_error(errno, std:: system_category());
}
}
}
}
const json::object_t * get_object(
const json & node,
const char * key
) {
const auto it = node.find(key);
if (it != node.end()) {
return it->get_ptr<const json::object_t *>();
}
return nullptr;
}
const json::number_unsigned_t * get_unsigned(
const json & node,
const char * key
) {
const auto it = node.find(key);
if (it != node.end()) {
return it->get_ptr<const json::number_unsigned_t *>();
}
return nullptr;
}
const json::string_t * get_string(
const json & node,
const char * key
) {
const auto it = node.find(key);
if (it != node.end()) {
return it->get_ptr<const json::string_t *>();
}
return nullptr;
}
size_t to_unsigned(const std::string & value) {
return strtoull(value.c_str(), nullptr, 10);
}
struct file_entry {
std::string m_filename;
size_t m_offset;
size_t m_size;
inline file_entry(
std::string filename,
size_t offset,
size_t size
)
: m_filename(filename)
, m_offset(offset)
, m_size(size)
{}
};
void traverse(
const json & node,
const std::string & path,
std::vector<file_entry> & toc
) {
if (const auto files = get_object(node, "files")) {
for (const auto & it : *files) {
traverse(it.second, path + "/" + it.first, toc);
}
} else {
const auto size = get_unsigned(node, "size");
const auto offset_str = get_string(node, "offset");
if (size && offset_str) {
const auto offset = to_unsigned(*offset_str);
toc.emplace_back(path, offset, *size);
}
}
}
void unpack(istream & input) {
// triple-serialized bullshit cause fucktards
const auto header_size_size = input.read<uint32_t>(); // must be 4
if (header_size_size != 4) {
throw std::runtime_error("header_size_size not 4");
}
const auto header_size_1 = input.read<uint32_t>(); // header_size_2 + 4
const auto header_size_2 = input.read<uint32_t>(); // header_length + 4 + null byte
if (header_size_1 < header_size_2 + 4) {
throw std::runtime_error("header_size_2 too big");
}
const auto header_length = input.read<uint32_t>(); // length of header string (sans null byte)
if (header_size_2 < header_length + 4) {
throw std::runtime_error("header_length too big");
}
std::vector<char> json_header(header_length);
input.read(json_header.data(), header_length);
const auto header = json::parse(json_header);
std::vector<file_entry> toc;
if (const auto files = get_object(header, "files")) {
for (const auto & it : *files) {
traverse(it.second, it.first, toc);
}
}
std::sort(
toc.begin(),
toc.end(),
[] (const file_entry & left, const file_entry & right) noexcept -> bool {
return left.m_offset < right.m_offset;
}
);
size_t current_offset = 0;
for (const auto & f : toc) {
printf("%s (@%lu, %lu)\n", f.m_filename.c_str(), f.m_offset, f.m_size);
if (f.m_offset > current_offset) {
std::vector<char> padding(current_offset - f.m_offset);
input.read(padding.data(), padding.size());
}
std::vector<char> buffer(f.m_size);
input.read(buffer.data(), buffer.size());
make_containing_folder(f.m_filename);
std::unique_ptr<filestream> output(filestream::create(f.m_filename));
output->write(buffer.data(), buffer.size());
current_offset = f.m_offset + f.m_size;
}
}
int main(int argc, char ** argv) {
for (int i = 1; i < argc; ++i) {
printf("Parsing %s...\n", argv[i]);
std::unique_ptr<filestream> stream(filestream::open(argv[i]));
unpack(*stream);
}
return 0;
}