Skip to content

Instantly share code, notes, and snippets.

@fetus-hina
Created May 6, 2012 10:48
Show Gist options
  • Save fetus-hina/2621603 to your computer and use it in GitHub Desktop.
Save fetus-hina/2621603 to your computer and use it in GitHub Desktop.
PULLTOP の新しい *.arc ファイルとりあえずこれで読めた
#pragma once
#include <stdint.h>
#include <stdio.h>
#include <iostream>
#include <string>
#include <vector>
#include <map>
#include <exception>
#include <stdexcept>
#include <boost/noncopyable.hpp>
#include <boost/foreach.hpp>
#include <iconv.h>
class ArcFile : boost::noncopyable {
struct InnerFileInfo {
uint_least32_t offset_in_file;
uint_least32_t file_size;
};
public:
ArcFile(const std::string &arcfile);
~ArcFile();
void saveAll(const std::string &directory) const;
private:
void load();
std::pair<uint32_t, uint32_t> loadArcHeader();
void loadFileList(uint32_t file_num, uint32_t offset);
std::string convertFilename(const std::vector<uint16_t> &utf16) const;
FILE *m_file;
std::map<std::string, InnerFileInfo> m_inner;
};
inline ArcFile::ArcFile(const std::string &arcfile) :
m_file(NULL)
{
m_file = ::fopen(arcfile.c_str(), "rb");
if(!m_file) {
throw std::runtime_error(
std::string("Could not open file: ") + arcfile);
}
try {
load();
} catch(...) {
::fclose(m_file);
}
}
inline ArcFile::~ArcFile() {
::fclose(m_file);
}
inline void ArcFile::load() {
const std::pair<uint32_t, uint32_t> pos = loadArcHeader();
loadFileList(pos.first, pos.second);
}
// *.arc ファイルのヘッダ相当部分を読み込む
// ファイル構造は、ファイルの先頭から
// uint32 内包ファイル数
// uint32 コンテンツ開始位置(ヘッダ終了場所起点)
inline std::pair<uint32_t, uint32_t> ArcFile::loadArcHeader() {
::fseek(m_file, 0, SEEK_SET);
// 内包するファイル数
uint32_t file_num = 0;
if(::fread(&file_num, sizeof(file_num), 1, m_file) != 1) {
throw std::runtime_error("Cannot read from file: file_num");
}
// コンテンツの開始位置(ヘッダの終わった位置を 0 とした値)
uint32_t data_offset = 0;
if(::fread(&data_offset, sizeof(data_offset), 1, m_file) != 1) {
throw std::runtime_error("Cannot read from file: data_offset");
}
// コンテンツの開始位置がファイルの開始位置とずれるのは
// 実際問題扱いづらいのでファイルの先頭からの位置に補正する
data_offset += sizeof(file_num) + sizeof(data_offset);
std::cout << "file_num = " << file_num << std::endl;
std::cout << "data_offset = " << data_offset << std::endl;
return std::pair<uint32_t, uint32_t>(file_num, data_offset);
}
// *.arc の内包ファイルの一覧部分を読み込む
// 構造は、ファイルヘッダ終了場所からファイル数だけ
// uint32 ファイル長さ
// uint32 ファイルオフセット(コンテンツ開始位置起点)
// char16_t[] ファイル名(NUL終端) : UTF-16 LE
void ArcFile::loadFileList(const uint32_t file_num, const uint32_t data_offset) {
::fseek(m_file, sizeof(uint32_t) * 2, SEEK_SET);
for(uint32_t i = 0; i < file_num; ++i) {
// ファイルの長さ
uint32_t inner_size = 0;
if(::fread(&inner_size, sizeof(inner_size), 1, m_file) != 1) {
throw std::runtime_error("Cannot read from file: inner_size");
}
// オフセット
uint32_t inner_offset = 0;
if(::fread(&inner_offset, sizeof(inner_offset), 1, m_file) != 1) {
throw std::runtime_error("Cannot read from file: inner_offset");
}
// ファイル名
std::vector<uint16_t> filename_utf16;
filename_utf16.reserve(16); // このくらいあればいいよね普通というだけの値
uint16_t filename_tmp;
while(true) {
if(::fread(&filename_tmp, sizeof(filename_tmp), 1, m_file) != 1) {
throw std::runtime_error("Cannot read from file: filename (EOF)");
}
if(filename_tmp == 0x0000U) {
break;
}
filename_utf16.push_back(filename_tmp);
}
if(filename_utf16.size() > FILENAME_MAX) {
// ファイル名の最大長はいろいろな環境に依存するので本当は定数にするのはよろしくないが
// 実際問題このツールで困ることは無い(読み込みエラーだけどうにかしたい)のでとりあえずこれで
throw std::runtime_error("Cannot read from file: filename (too long)");
}
// ファイル名の UTF-8 への変換
const std::string filename_utf8 = convertFilename(filename_utf16);
std::cout << std::endl;
std::cout << "File: " << i + 1 << std::endl;
std::cout << " " << "name = " << filename_utf8 << std::endl;
std::cout << " " << "offset = " << inner_offset << " + " << data_offset << std::endl;
std::cout << " " << "size = " << inner_size << std::endl;
const ArcFile::InnerFileInfo inner_info = { inner_offset + data_offset, inner_size };
m_inner[filename_utf8] = inner_info;
}
}
inline std::string ArcFile::convertFilename(const std::vector<uint16_t> &utf16) const {
iconv_t icd = ::iconv_open("UTF-8", "UTF-16LE");
if(icd == (iconv_t)-1) {
throw std::runtime_error("iconv_open() failed");
}
try {
std::string in_buf(reinterpret_cast<const char *>(&utf16[0]), utf16.size() * sizeof(uint16_t));
std::vector<char> out_buf(utf16.size() * 6 + 1); // UTF-8 は最大 6 バイトなのでこれで足りるはず
size_t in_left = static_cast<size_t>(in_buf.size());
size_t out_left = static_cast<size_t>(out_buf.size());
char *in_ptr = &in_buf[0];
char *out_ptr = &out_buf[0];
const size_t iret = ::iconv(icd, &in_ptr, &in_left, &out_ptr, &out_left);
if(iret == (size_t)-1) {
throw std::runtime_error("iconv() failed");
}
::iconv_close(icd);
return &out_buf[0];
} catch(...) {
::iconv_close(icd);
throw;
}
}
inline void ArcFile::saveAll(const std::string &directory) const {
BOOST_FOREACH(const auto &pair, m_inner) {
const std::string out_path = directory + "/" + pair.first;
std::cout << "Writing: " << out_path << std::endl;
::fseek(m_file, pair.second.offset_in_file, SEEK_SET);
FILE *out = ::fopen(out_path.c_str(), "wb");
if(!out) {
std::cerr << "Could not open file to write mode: " << out_path << std::endl;
throw std::runtime_error("Could not open file to write mode");
}
try {
size_t copy_left = pair.second.file_size;
std::vector<char> copy_buf;
while(copy_left > 0) {
copy_buf.resize(std::min<size_t>(copy_left, 65536));
const size_t read_size = ::fread(&copy_buf[0], 1, copy_buf.size(), m_file);
if(read_size < 1) {
throw std::runtime_error("Copy failed");
}
if(::fwrite(&copy_buf[0], read_size, 1, out) != 1) {
throw std::runtime_error("Copy failed");
}
copy_left -= read_size;
}
::fclose(out);
} catch(...) {
::fclose(out);
throw;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment