From 6fdb39c69c2d5e526aa880dc5b3b9989fd4458dd Mon Sep 17 00:00:00 2001
From: toyobayashi <lifenglin314@outlook.com>
Date: Thu, 19 Sep 2019 20:43:40 +0800
Subject: [PATCH] 2.0.1

---
 CGSSAssetsDownloader.vcxproj            |   7 +-
 CGSSAssetsDownloader.vcxproj.filters    |  21 +-
 lib/ACBExtractor/ACBExtractor.cpp       | 111 ---
 lib/ACBExtractor/AFSArchive.cpp         |  67 --
 lib/ACBExtractor/Reader.cpp             | 163 -----
 lib/ACBExtractor/TrackList.cpp          |  81 ---
 lib/ACBExtractor/UTFTable.cpp           | 313 --------
 lib/ACBExtractor/include/ACBExtractor.h |  23 -
 lib/ACBExtractor/include/AFSArchive.h   |  33 -
 lib/ACBExtractor/include/Reader.h       |  61 --
 lib/ACBExtractor/include/TrackList.h    |  29 -
 lib/ACBExtractor/include/UTFTable.h     |  72 --
 lib/acb/acb.c                           | 915 ++++++++++++++++++++++++
 lib/acb/acb.h                           |  93 +++
 src/ApiClient.cpp                       |   2 +-
 src/CGSSAssetsDownloader.cpp            |  13 +-
 src/download.cpp                        |   2 +-
 17 files changed, 1027 insertions(+), 979 deletions(-)
 delete mode 100644 lib/ACBExtractor/ACBExtractor.cpp
 delete mode 100644 lib/ACBExtractor/AFSArchive.cpp
 delete mode 100644 lib/ACBExtractor/Reader.cpp
 delete mode 100644 lib/ACBExtractor/TrackList.cpp
 delete mode 100644 lib/ACBExtractor/UTFTable.cpp
 delete mode 100644 lib/ACBExtractor/include/ACBExtractor.h
 delete mode 100644 lib/ACBExtractor/include/AFSArchive.h
 delete mode 100644 lib/ACBExtractor/include/Reader.h
 delete mode 100644 lib/ACBExtractor/include/TrackList.h
 delete mode 100644 lib/ACBExtractor/include/UTFTable.h
 create mode 100644 lib/acb/acb.c
 create mode 100644 lib/acb/acb.h

diff --git a/CGSSAssetsDownloader.vcxproj b/CGSSAssetsDownloader.vcxproj
index 6686132..1ba1579 100644
--- a/CGSSAssetsDownloader.vcxproj
+++ b/CGSSAssetsDownloader.vcxproj
@@ -147,11 +147,7 @@
     </Link>
   </ItemDefinitionGroup>
   <ItemGroup>
-    <ClCompile Include="lib\ACBExtractor\ACBExtractor.cpp" />
-    <ClCompile Include="lib\ACBExtractor\AFSArchive.cpp" />
-    <ClCompile Include="lib\ACBExtractor\Reader.cpp" />
-    <ClCompile Include="lib\ACBExtractor\TrackList.cpp" />
-    <ClCompile Include="lib\ACBExtractor\UTFTable.cpp" />
+    <ClCompile Include="lib\acb\acb.c" />
     <ClCompile Include="lib\aes\aes.c" />
     <ClCompile Include="lib\jstype\base64\decode.c" />
     <ClCompile Include="lib\jstype\base64\encode.c" />
@@ -175,6 +171,7 @@
     <ClCompile Include="src\lz4.cpp" />
   </ItemGroup>
   <ItemGroup>
+    <ClInclude Include="lib\acb\acb.h" />
     <ClInclude Include="lib\aes\aes.h" />
     <ClInclude Include="lib\aes\aes.hpp" />
     <ClInclude Include="lib\curl\curl.h" />
diff --git a/CGSSAssetsDownloader.vcxproj.filters b/CGSSAssetsDownloader.vcxproj.filters
index 5c53559..caab509 100644
--- a/CGSSAssetsDownloader.vcxproj.filters
+++ b/CGSSAssetsDownloader.vcxproj.filters
@@ -39,21 +39,6 @@
     <ClCompile Include="src\lz4.cpp">
       <Filter>源文件</Filter>
     </ClCompile>
-    <ClCompile Include="lib\ACBExtractor\ACBExtractor.cpp">
-      <Filter>源文件</Filter>
-    </ClCompile>
-    <ClCompile Include="lib\ACBExtractor\AFSArchive.cpp">
-      <Filter>源文件</Filter>
-    </ClCompile>
-    <ClCompile Include="lib\ACBExtractor\Reader.cpp">
-      <Filter>源文件</Filter>
-    </ClCompile>
-    <ClCompile Include="lib\ACBExtractor\TrackList.cpp">
-      <Filter>源文件</Filter>
-    </ClCompile>
-    <ClCompile Include="lib\ACBExtractor\UTFTable.cpp">
-      <Filter>源文件</Filter>
-    </ClCompile>
     <ClCompile Include="lib\sqlite3\sqlite3.c">
       <Filter>源文件</Filter>
     </ClCompile>
@@ -93,6 +78,9 @@
     <ClCompile Include="lib\jstype\Uuid.cpp">
       <Filter>源文件</Filter>
     </ClCompile>
+    <ClCompile Include="lib\acb\acb.c">
+      <Filter>源文件</Filter>
+    </ClCompile>
   </ItemGroup>
   <ItemGroup>
     <ClInclude Include="lib\sha1\sha1.h">
@@ -200,5 +188,8 @@
     <ClInclude Include="lib\jstype\Uuid.h">
       <Filter>头文件</Filter>
     </ClInclude>
+    <ClInclude Include="lib\acb\acb.h">
+      <Filter>头文件</Filter>
+    </ClInclude>
   </ItemGroup>
 </Project>
\ No newline at end of file
diff --git a/lib/ACBExtractor/ACBExtractor.cpp b/lib/ACBExtractor/ACBExtractor.cpp
deleted file mode 100644
index d6a761e..0000000
--- a/lib/ACBExtractor/ACBExtractor.cpp
+++ /dev/null
@@ -1,111 +0,0 @@
-#include "./include/ACBExtractor.h"
-#include <regex>
-
-ACBExtractor::ACBExtractor(std::string acbFile) {
-  std::regex re("/|\\\\");
-#ifdef _WIN32
-  acbFile = std::regex_replace(acbFile, re, "\\");
-#else
-  std::regex_replace(acbFile, re, "/");
-#endif
-  path = acbFile;
-  headerTable = new UTFTable(acbFile);
-  tracklist = new TrackList(headerTable);
-
-  acbData* awbData = headerTable->get(0, "AwbFile");
-  unsigned char* awbbuf = new unsigned char[awbData->length];
-  headerTable->readBinary(awbbuf, static_cast<unsigned int*>(awbData->data)[0], static_cast<unsigned int*>(awbData->data)[1]);
-  awbFile = new AFSArchive(awbbuf, awbData->length);
-  delete[] awbbuf;
-}
-
-bool ACBExtractor::extract(void (*callback)(std::string filename, unsigned int length)) {
-  std::string dirname, targetDir, filename;
-
-#ifdef _WIN32
-  auto posw = path.find_last_of('\\');
-  if (posw == std::string::npos) {
-    dirname = ".";
-    filename = path;
-  } else {
-    dirname = path.substr(0, posw);
-    filename = path.substr(posw + 1);
-  }
-  targetDir = dirname + "\\_acb_" + filename;
-  std::string cmd = "mkdir " + targetDir;
-#else
-  auto posl = path.find_last_of('/');
-  if (posl == std::string::npos) {
-    dirname = ".";
-    filename = path;
-  } else {
-    dirname = path.substr(0, posl);
-    filename = path.substr(posl + 1);
-  }
-  targetDir = dirname + "/_acb_" + filename;
-  std::string cmd = "mkdir -p " + targetDir;
-#endif
-  
-  system(cmd.c_str());
-  std::ofstream fs;
-  for (unsigned int i = 0; i < tracklist->length; i++) {
-    int inFiles = -1;
-    for (unsigned int j = 0; j < awbFile->header->fileCount; j++) {
-      if (tracklist->tracks[i].wavId == awbFile->files[j].id) {
-        inFiles = j;
-        break;
-      }
-    }
-    if (inFiles != -1) {
-      std::string cueName = tracklist->tracks[i].cueName;
-      std::string encodeType = "";
-      switch (tracklist->tracks[i].encodeType) {
-      case 0: {
-        encodeType = ".adx";
-        break;
-      }
-      case 2: {
-        encodeType = ".hca";
-        break;
-      }
-      case 7: {
-        encodeType = ".at3";
-        break;
-      }
-      case 8: {
-        encodeType = ".vag";
-        break;
-      }
-      case 9: {
-        encodeType = ".bcwav";
-        break;
-      }
-      case 13: {
-        encodeType = ".dsp";
-        break;
-      }
-      default:
-        break;
-      }
-#ifdef _WIN32
-      std::string acbFilename = targetDir + "\\" + cueName + encodeType;
-#else
-      std::string acbFilename = targetDir + "/" + cueName + encodeType;
-#endif
-      fs.open(acbFilename, std::ios::binary);
-      fs.write((const char*)awbFile->files[inFiles].buf, awbFile->files[inFiles].length);
-      if (callback) callback(cueName + encodeType, awbFile->files[inFiles].length);
-      fs.close();
-    }
-    else {
-      return false; 
-    }
-  }
-  return true;
-}
-
-ACBExtractor::~ACBExtractor() {
-  delete headerTable;
-  delete tracklist;
-  delete awbFile;
-}
diff --git a/lib/ACBExtractor/AFSArchive.cpp b/lib/ACBExtractor/AFSArchive.cpp
deleted file mode 100644
index ff6fce0..0000000
--- a/lib/ACBExtractor/AFSArchive.cpp
+++ /dev/null
@@ -1,67 +0,0 @@
-#include "./include/AFSArchive.h"
-
-void AFSArchive::readHeader() {
-  std::streampos back = r->tell();
-  r->seek(5);
-  header = new awbHeader;
-
-  header->offsetSize = r->readUInt8();
-  r->seek(8);
-  header->fileCount = r->readUInt32LE();
-  header->alignment = r->readUInt32LE();
-  header->ids = new unsigned short[header->fileCount];
-  header->fileEndPoints = new unsigned int[header->fileCount + 1];
-
-  for (unsigned int i = 0; i < header->fileCount; i++) {
-    header->ids[i] = r->readUInt16LE();
-  }
-
-  for (unsigned int i = 0; i < header->fileCount + 1; i++) {
-    header->fileEndPoints[i] = r->readUIntLE(header->offsetSize);
-  }
-
-  r->seek(back);
-}
-
-AFSArchive::AFSArchive(unsigned char * awb, unsigned int l) {
-  r = new Reader(awb, l);
-  r->seek(0);
-  unsigned int t = r->readUInt32BE();
-  if (t != 0x41465332) {
-    r->close();
-    delete r;
-    throw "Not AWB file.";
-  }
-  
-  length = l;
-  readHeader();
-
-  files = new entryFile[header->fileCount];
-  for (unsigned int i = 0; i < header->fileCount; i++) {
-    unsigned int tmp = header->fileEndPoints[i] % header->alignment;
-    
-    unsigned int start = (tmp == 0 ? header->fileEndPoints[i] / header->alignment : header->fileEndPoints[i] / header->alignment + 1) * header->alignment;
-    unsigned int length = header->fileEndPoints[i + 1] - start;
-    files[header->ids[i]].buf = new unsigned char[length];
-    files[header->ids[i]].length = length;
-    files[header->ids[i]].id = header->ids[i];
-    
-    std::streampos back = r->tell();
-    r->seek(start);
-    r->read((char*)files[header->ids[i]].buf, length);
-    r->seek(back);
-  }
-  r->close();
-}
-
-AFSArchive::~AFSArchive() {
-  delete r;
-  for (unsigned int i = 0; i < header->fileCount; i++) {
-    delete[] files[i].buf;
-  }
-  delete[] files;
-  delete[] header->fileEndPoints;
-  delete[] header->ids;
-  delete header;
-  
-}
diff --git a/lib/ACBExtractor/Reader.cpp b/lib/ACBExtractor/Reader.cpp
deleted file mode 100644
index b3f6aef..0000000
--- a/lib/ACBExtractor/Reader.cpp
+++ /dev/null
@@ -1,163 +0,0 @@
-#include "./include/Reader.h"
-#include <math.h>
-
-int Reader::isLittleEndian = -1;
-
-void Reader::checkEndian() {
-  if (isLittleEndian == -1) {
-    int x = 0x12;
-    isLittleEndian = *(char*)&x == 0x12 ? 1 : 0;
-  }
-}
-
-// template<typename T>
-// T Reader::readByte(bool isLE) {
-//   unsigned char buf[sizeof(T)];
-//   unsigned int size = sizeof(T);
-//   read((char*)buf, size);
-//   if ((isLittleEndian && !isLE) || (!isLittleEndian && isLE)) {
-//     unsigned char temp;
-//     for (unsigned int i = 0; i < size / 2; i++) {
-//       temp = buf[i];
-//       buf[i] = buf[size - 1 - i];
-//       buf[size - 1 - i] = temp;
-//     }
-//   }
-  
-//   T* result = (T*)buf;
-
-//   return *result;
-// }
-
-Reader::Reader(unsigned char* buf, unsigned int length) {
-  checkEndian();
-  std::string tmpname = std::tmpnam(nullptr);
-  tmpfile = tmpname;
-  
-  std::ofstream fs(tmpname, std::ios::binary);
-  fs.write((const char*)buf, length);
-  fs.close();
-  length = length;
-  open(tmpfile, std::ios::binary);
-}
-
-Reader::Reader() {
-  checkEndian();
-  std::ifstream();
-}
-
-Reader::~Reader() {
-  if (tmpfile != "") remove(tmpfile.c_str());
-}
-
-Reader::Reader(std::string filename, ios_base::openmode mode) {
-  checkEndian();
-  open(filename.c_str(), mode);
-}
-
-void Reader::open(std::string filename, ios_base::openmode mode) {
-  std::ifstream::open(filename.c_str(), mode);
-  std::streampos current = tellg();
-  seekg(0, std::ios::end);
-  length = (unsigned int)tellg();
-  seekg(current, std::ios::beg);
-}
-
-std::streampos Reader::tell() {
-  return tellg();
-}
-
-Reader& Reader::seek(std::streampos at, std::streampos offset) {
-  seekg(at + offset, std::ios::beg);
-  return *this;
-}
-
-unsigned int Reader::readUInt32BE() {
-  return readByte<unsigned int>();
-}
-
-unsigned int Reader::readUInt32LE() {
-  return readByte<unsigned int>(true);
-}
-
-unsigned long long Reader::readUInt64BE() {
-  return readByte<unsigned long long>();
-}
-
-unsigned long long Reader::readUInt64LE() {
-  return readByte<unsigned long long>(true);
-}
-
-unsigned short Reader::readUInt16BE() {
-  return readByte<unsigned short>();
-}
-
-unsigned short Reader::readUInt16LE() {
-  return readByte<unsigned short>(true);
-}
-
-unsigned char Reader::readUInt8() {
-  return readByte<unsigned char>();
-}
-
-char Reader::readInt8() {
-  return readByte<char>();
-}
-
-short Reader::readInt16BE() {
-  return readByte<short>();
-}
-
-short Reader::readInt16LE() {
-  return readByte<short>(true);
-}
-
-int Reader::readInt32BE() {
-  return readByte<int>();
-}
-
-int Reader::readInt32LE() {
-  return readByte<int>(true);
-}
-
-long long Reader::readInt64BE() {
-  return readByte<int>();
-}
-
-long long Reader::readInt64LE() {
-  return readByte<long long>(true);
-}
-
-double Reader::readDoubleBE() {
-  return readByte<double>();
-}
-
-double Reader::readDoubleLE() {
-  return readByte<double>(true);
-}
-
-float Reader::readFloatBE() {
-  return readByte<float>();
-}
-
-float Reader::readFloatLE() {
-  return readByte<float>(true);
-}
-
-unsigned int Reader::readUIntLE(unsigned int size) {
-  unsigned char* buf = new unsigned char[size];
-
-  read((char*)buf, size);
-
-  unsigned int result = 0;
-  for (unsigned int i = 0; i < size; i++) {
-    int high, low, power;
-    high = buf[i] / 16;
-    low = buf[i] % 16;
-    power = 2 * i; // 2 * size - 1 - 2 * i;
-    result += (high * (unsigned int)pow(16, power + 1) + low * (unsigned int)pow(16, power));
-  }
-
-  delete[] buf;
-  return result;
-}
diff --git a/lib/ACBExtractor/TrackList.cpp b/lib/ACBExtractor/TrackList.cpp
deleted file mode 100644
index e24aded..0000000
--- a/lib/ACBExtractor/TrackList.cpp
+++ /dev/null
@@ -1,81 +0,0 @@
-#include "./include/TrackList.h"
-
-TrackList::TrackList(UTFTable* utf) {
-  acbData* cueTableData = utf->get(0, "CueTable");
-  unsigned char* buf1 = new unsigned char[cueTableData->length];
-  utf->readBinary(buf1, static_cast<unsigned int*>(cueTableData->data)[0], static_cast<unsigned int*>(cueTableData->data)[1]);
-  cueTable = new UTFTable(buf1, cueTableData->length);
-  delete[] buf1;
-
-  acbData* cueNameTableData = utf->get(0, "CueNameTable");
-  unsigned char* buf2 = new unsigned char[cueNameTableData->length];
-  utf->readBinary(buf2, static_cast<unsigned int*>(cueNameTableData->data)[0], static_cast<unsigned int*>(cueNameTableData->data)[1]);
-  cueNameTable = new UTFTable(buf2, cueNameTableData->length);
-  delete[] buf2;
-
-  acbData* waveformTableData = utf->get(0, "WaveformTable");
-  unsigned char* buf3 = new unsigned char[waveformTableData->length];
-  utf->readBinary(buf3, static_cast<unsigned int*>(waveformTableData->data)[0], static_cast<unsigned int*>(waveformTableData->data)[1]);
-  waveformTable = new UTFTable(buf3, waveformTableData->length);
-  delete[] buf3;
-
-  acbData* synthTableData = utf->get(0, "SynthTable");
-  unsigned char* buf4 = new unsigned char[synthTableData->length];
-  utf->readBinary(buf4, static_cast<unsigned int*>(synthTableData->data)[0], static_cast<unsigned int*>(synthTableData->data)[1]);
-  synthTable = new UTFTable(buf4, synthTableData->length);
-  delete[] buf4;
-
-  std::string* nameMap = new std::string[cueNameTable->header->rowLength];
-
-  for (unsigned int i = 0; i < cueNameTable->header->rowLength; i++) {
-    acbData* str = cueNameTable->get(i, "CueName");
-    nameMap[*(static_cast<unsigned short*>(cueNameTable->get(i, "CueIndex")->data))] = str ?
-      std::string(static_cast<char*>(str->data)) : "UNKNOWN";
-  }
-
-  tracks = new track[cueTable->header->rowLength];
-  length = cueTable->header->rowLength;
-  for (unsigned int i = 0; i < cueTable->header->rowLength; i++) {
-    unsigned char referenceType = *(static_cast<unsigned char*>(cueTable->get(i, "ReferenceType")->data));
-    if (referenceType != static_cast<unsigned char>(3) && referenceType != static_cast<unsigned char>(8)) {
-      delete cueTable;
-      delete cueNameTable;
-      delete waveformTable;
-      delete synthTable;
-      delete[] tracks;
-      throw "ReferenceType not implemented.";
-    }
-
-    unsigned short referenceIndex = *(static_cast<unsigned short*>(cueTable->get(i, "ReferenceIndex")->data));
-
-    acbData* refitm = synthTable->get(referenceIndex, "ReferenceItems");
-    unsigned char* referenceItems = new unsigned char[refitm->length];
-    synthTable->readBinary(referenceItems, static_cast<unsigned int*>(refitm->data)[0], static_cast<unsigned int*>(refitm->data)[1]);
-    unsigned short b = (referenceItems[2] / 16) * 4096 + (referenceItems[2] % 16) * 256 + (referenceItems[3] / 16) * 16 + (referenceItems[3] % 16);
-    delete[] referenceItems;
-
-    unsigned short wavId;
-    acbData* hasId = waveformTable->get(b, "Id");
-    if (hasId) {
-      wavId = *(static_cast<unsigned short*>(hasId->data));
-    } else {
-      wavId = *(static_cast<unsigned short*>(waveformTable->get(b, "MemoryAwbId")->data));
-    }
-    
-    tracks[i].cueId = *(static_cast<unsigned int*>(cueTable->get(i, "CueId")->data));
-    tracks[i].cueName = nameMap[i];
-    tracks[i].wavId = wavId;
-    tracks[i].encodeType = *(static_cast<unsigned char*>(waveformTable->get(b, "EncodeType")->data));
-    tracks[i].streaming = *(static_cast<unsigned char*>(waveformTable->get(b, "Streaming")->data));
-  }
-
-  delete[] nameMap;
-}
-
-TrackList::~TrackList() {
-  delete cueTable;
-  delete cueNameTable;
-  delete waveformTable;
-  delete synthTable;
-  delete[] tracks;
-}
diff --git a/lib/ACBExtractor/UTFTable.cpp b/lib/ACBExtractor/UTFTable.cpp
deleted file mode 100644
index 98f45d5..0000000
--- a/lib/ACBExtractor/UTFTable.cpp
+++ /dev/null
@@ -1,313 +0,0 @@
-#include "./include/UTFTable.h"
-
-unsigned int UTFTable::dataLength[12] = {
-  1, 1, 2, 2, 4, 4, 8, 8, 4, 8, 4, 8
-};
-
-UTFTable::acbColumnType UTFTable::columnType = {
-  0x10, 0x30, 0x50, 0x70
-};
-
-//std::string UTFTable::dataType[12] = {
-//  "UInt8", // 00
-//  "Int8", // 01
-//  "UInt16", // 02
-//  "Int16", // 03
-//  "UInt32", // 04
-//  "Int32", // 05
-//  "UInt64", // 06
-//  "Int64", // 07
-//  "Float", // 08
-//  "Double", // 09
-//  "String", // 0a
-//  "Binary" // 0b
-//};
-
-UTFTable::UTFTable(std::string acb) {
-  r = new Reader;
-  r->open(acb, std::ios::binary);
-  if (!r->is_open()) {
-    r->close();
-    delete r;
-    throw "Open file failed.";
-  }
-  unsigned int magic = r->readUInt32BE();
-
-  if (magic != 0x40555446) {
-    r->close();
-    delete r;
-    throw "Not acb file.";
-  }
-  length = r->readUInt32BE();
-  if (length != r->length - 8) {
-    r->close();
-    delete r;
-    throw "UTFTable Error length.";
-  }
-
-  readHeader();
-  name = readString(header->tableNameStringOffset, nullptr);
-  readColumns();
-  readRows();
-  
-}
-
-UTFTable::UTFTable(unsigned char* acb, unsigned int l) {
-  r = new Reader(acb, l);
-  unsigned int magic = r->readUInt32BE();
-
-  if (magic != 0x40555446) {
-    r->close();
-    delete r;
-    throw "Not acb file.";
-  }
-  length = r->readUInt32BE();
-  if (length != r->length - 8) {
-    r->close();
-    delete r;
-    throw "UTFTable Error length.";
-  }
-
-  readHeader();
-  name = readString(header->tableNameStringOffset, nullptr);
-  readColumns();
-  readRows();
-
-}
-
-UTFTable::~UTFTable() {
-  r->close();
-  delete r;
-  delete name;
-  for (unsigned int i = 0; i < header->columnLength; i++) {
-    delete[] columns[i].columnName;
-    if (columns[i].columnType == UTFTable::columnType.CONSTANT || columns[i].columnType == UTFTable::columnType.CONSTANT2) {
-      deleteVoid(columns[i]);
-    }
-  }
-
-  for (unsigned int i = 0; i < header->rowLength; i++) {
-    for (unsigned int j = 0; j < header->columnLength; j++) {
-      deleteVoid(rows[i][j]);
-    }
-    delete[] rows[i];
-  }
-  delete header;
-  delete[] columns;
-  delete[] rows;
-}
-
-acbData* UTFTable::get(unsigned int lineNumber, std::string columnName) {
-  if (lineNumber >= header->rowLength) return nullptr;
-  for (unsigned int i = 0; i < header->columnLength; i++) {
-    if (rows[lineNumber][i].columnName == columnName) {
-      return &(rows[lineNumber][i]);
-    }
-  }
-  return nullptr;
-}
-
-void UTFTable::readHeader() {
-  std::streampos back = r->tell();
-  r->seek(8 + 0);
-  header = new acbHeader;
-  
-  header->u1 = r->readUInt16BE();
-  header->tableDataOffset = r->readUInt16BE();
-  header->stringDataOffset = r->readUInt32BE();
-  header->binaryDataOffset = r->readUInt32BE();
-  header->tableNameStringOffset = r->readUInt32BE();
-  header->columnLength = r->readUInt16BE();
-  header->rowTotalByte = r->readUInt16BE();
-  header->rowLength = r->readUInt32BE();
-  r->seek(back);
-}
-
-char* UTFTable::readString(std::streampos offset, unsigned int* outlength) {
-  std::streampos back = r->tell();
-  r->seek(header->stringDataOffset + 8, offset);
-  unsigned int length = 0;
-  while (true) {
-    length++;
-    if (r->readUInt8() == 0) break;
-  }
-  r->seek(header->stringDataOffset + 8, offset);
-  if (outlength) *outlength = length - 1;
-  char* outstr = new char[length];
-  r->read(outstr, length);
-  r->seek(back);
-
-  return outstr;
-}
-
-void UTFTable::readBinary(unsigned char* outbuf, std::streampos offset, std::streampos length) {
-  std::streampos back = r->tell();
-  r->seek(header->binaryDataOffset + 8, offset);
-  r->read((char*)outbuf, length);
-  r->seek(back);
-}
-
-void UTFTable::readColumns() {
-  std::streampos back = r->tell();
-  r->seek(24 + 8);
-  columns = new acbColumn[header->columnLength];
-
-  for (unsigned int i = 0; i < header->columnLength; i++) {
-    unsigned char columnTypeAndDataType = r->readUInt8();
-    unsigned int nameOffset = r->readUInt32BE();
-    unsigned char columnType = columnTypeAndDataType & 0xf0;
-    unsigned char dataType = columnTypeAndDataType & 0x0f;
-
-    char* columnName = readString(nameOffset, nullptr);
-
-    columns[i].columnName = columnName;
-    columns[i].dataType = dataType;
-    columns[i].columnType = columnType;
-
-    if (columnType == UTFTable::columnType.CONSTANT || columnType == UTFTable::columnType.CONSTANT2) {
-      readData(dataType, columns[i]);
-    }
-  }
-  r->seek(back);
-}
-
-void UTFTable::readRows() {
-
-  rows = new acbRow[header->rowLength];
-
-  for (unsigned int ri = 0; ri < header->rowLength; ri++) {
-    rows[ri] = new acbData[header->columnLength];
-    unsigned int dataPos = ri * header->rowTotalByte;
-
-    for (unsigned int i = 0; i < header->columnLength; i++) {
-      rows[ri][i].columnName = columns[i].columnName;
-      rows[ri][i].dataType = columns[i].dataType;
-      if (columns[i].columnType == UTFTable::columnType.CONSTANT || columns[i].columnType == UTFTable::columnType.CONSTANT2) {
-        unsigned char* data = new unsigned char[columns[i].length];
-        for (unsigned int j = 0; j < columns[i].length; j++) {
-          data[j] = static_cast<unsigned char*>(columns[i].data)[j];
-        }
-        rows[ri][i].data = static_cast<void*>(data);
-        rows[ri][i].length = columns[i].length;
-      } else {
-
-        std::streampos back = r->tell();
-        r->seek(header->tableDataOffset + 8, dataPos);
-
-        readData(rows[ri][i].dataType, rows[ri][i]);
-        r->seek(back);
-        dataPos += UTFTable::dataLength[columns[i].dataType];
-      }
-    }
-  }
-}
-
-template<typename T, typename Arg>
-void UTFTable::readDataTemplate(Arg& columnOrData) {
-  T* data = new T;
-  *data = r->readByte<T>();
-  columnOrData.data = static_cast<void*>(data);
-  columnOrData.length = sizeof(T);
-}
-
-template<typename Arg>
-void UTFTable::deleteVoid(Arg& columnOrData) {
-  if (columnOrData.dataType == static_cast<unsigned char>(0x0b))
-    delete[] static_cast<unsigned int*>(columnOrData.data);
-  else if (columnOrData.dataType == static_cast<unsigned char>(0x0a))
-    delete[] static_cast<char*>(columnOrData.data);
-  else if (columnOrData.dataType == static_cast<unsigned char>(0x00))
-    delete static_cast<unsigned char*>(columnOrData.data);
-  else if (columnOrData.dataType == static_cast<unsigned char>(0x01))
-    delete static_cast<char*>(columnOrData.data);
-  else if (columnOrData.dataType == static_cast<unsigned char>(0x02))
-    delete static_cast<unsigned short*>(columnOrData.data);
-  else if (columnOrData.dataType == static_cast<unsigned char>(0x03))
-    delete static_cast<short*>(columnOrData.data);
-  else if (columnOrData.dataType == static_cast<unsigned char>(0x04))
-    delete static_cast<unsigned int*>(columnOrData.data);
-  else if (columnOrData.dataType == static_cast<unsigned char>(0x05))
-    delete static_cast<int*>(columnOrData.data);
-  else if (columnOrData.dataType == static_cast<unsigned char>(0x06))
-    delete static_cast<unsigned long long*>(columnOrData.data);
-  else if (columnOrData.dataType == static_cast<unsigned char>(0x07))
-    delete static_cast<long long*>(columnOrData.data);
-  else if (columnOrData.dataType == static_cast<unsigned char>(0x08))
-    delete static_cast<float*>(columnOrData.data);
-  else if (columnOrData.dataType == static_cast<unsigned char>(0x09))
-    delete static_cast<double*>(columnOrData.data);
-}
-
-template<typename Arg>
-void UTFTable::readData(unsigned char type, Arg& columnOrData) {
-  switch (type) {
-  case 0x00: {
-    readDataTemplate<unsigned char, Arg>(columnOrData);
-    break;
-  }
-
-  case 0x01: {
-    readDataTemplate<char, Arg>(columnOrData);
-    break;
-  }
-
-  case 0x02: {
-    readDataTemplate<unsigned short, Arg>(columnOrData);
-    break;
-  }
-
-  case 0x03: {
-    readDataTemplate<short, Arg>(columnOrData);
-    break;
-  }
-
-  case 0x04: {
-    readDataTemplate<unsigned int, Arg>(columnOrData);
-    break;
-  }
-
-  case 0x05: {
-    readDataTemplate<int, Arg>(columnOrData);
-    break;
-  }
-
-  case 0x06: {
-    readDataTemplate<unsigned long long, Arg>(columnOrData);
-    break;
-  }
-
-  case 0x07: {
-    readDataTemplate<long long, Arg>(columnOrData);
-    break;
-  }
-
-  case 0x08: {
-    readDataTemplate<float, Arg>(columnOrData);
-    break;
-  }
-
-  case 0x09: {
-    readDataTemplate<double, Arg>(columnOrData);
-    break;
-  }
-
-  case 0x0a: {
-    unsigned int l = 0;
-    char* data = readString(r->readUInt32BE(), &l);
-    columnOrData.length = l;
-    columnOrData.data = static_cast<void*>(data);
-    break;
-  }
-  case 0x0b: {
-    unsigned int* data = new unsigned int[2];
-    data[0] = r->readUInt32BE(); // offset
-    data[1] = r->readUInt32BE(); // length
-    // readBinary(data, offset, length);
-    columnOrData.length = data[1];
-    columnOrData.data = static_cast<void*>(data);
-    break;
-  }
-
-  default: break;
-  }
-}
diff --git a/lib/ACBExtractor/include/ACBExtractor.h b/lib/ACBExtractor/include/ACBExtractor.h
deleted file mode 100644
index ee3ad2d..0000000
--- a/lib/ACBExtractor/include/ACBExtractor.h
+++ /dev/null
@@ -1,23 +0,0 @@
-#ifndef _ACB_ACBEXTRACTOR_H_
-#define _ACB_ACBEXTRACTOR_H_
-
-#include "./UTFTable.h"
-#include "./AFSArchive.h"
-#include "./TrackList.h"
-
-class ACBExtractor {
-private:
-
-public:
-  std::string path;
-  UTFTable* headerTable;
-  TrackList* tracklist;
-  AFSArchive* awbFile;
-
-  ACBExtractor(std::string acbFile);
-
-  bool extract(void (*callback)(std::string filename, unsigned int length));
-
-  ~ACBExtractor();
-};
-#endif // !_ACB_ACBEXTRACTOR_H_
diff --git a/lib/ACBExtractor/include/AFSArchive.h b/lib/ACBExtractor/include/AFSArchive.h
deleted file mode 100644
index 5ed9570..0000000
--- a/lib/ACBExtractor/include/AFSArchive.h
+++ /dev/null
@@ -1,33 +0,0 @@
-#ifndef _ACB_AFSARCHIVE_H_
-#define _ACB_AFSARCHIVE_H_
-
-#include "./Reader.h"
-
-typedef struct {
-  unsigned char offsetSize;
-  unsigned int fileCount;
-  unsigned int alignment;
-  unsigned short* ids;
-  unsigned int* fileEndPoints;
-} awbHeader;
-
-typedef struct {
-  unsigned char* buf;
-  unsigned int length;
-  unsigned short id;
-} entryFile;
-
-class AFSArchive {
-private:
-  Reader* r;
-  void readHeader();
-public:
-
-  unsigned int length;
-  awbHeader* header;
-  entryFile* files;
-  
-  AFSArchive(unsigned char* awb, unsigned int l);
-  ~AFSArchive();
-};
-#endif // !_ACB_AFSARCHIVE_H_
diff --git a/lib/ACBExtractor/include/Reader.h b/lib/ACBExtractor/include/Reader.h
deleted file mode 100644
index 4abbff6..0000000
--- a/lib/ACBExtractor/include/Reader.h
+++ /dev/null
@@ -1,61 +0,0 @@
-#ifndef _ACB_READER_H_
-#define _ACB_READER_H_
-
-#include <fstream>
-
-class Reader : public std::ifstream {
-private:
-  
-  static int isLittleEndian;
-  void checkEndian();
-  std::string tmpfile = "";
-
-public:
-  unsigned int length;
-  Reader(unsigned char* buf, unsigned int length);
-  Reader();
-  ~Reader();
-  Reader(std::string filename, ios_base::openmode mode);
-  void open(std::string filename, ios_base::openmode mode);
-  std::streampos tell();
-  Reader& seek(std::streampos at, std::streampos offset = 0);
-  unsigned int readUInt32BE();
-  unsigned int readUInt32LE();
-  unsigned long long readUInt64BE();
-  unsigned long long readUInt64LE();
-  unsigned short readUInt16BE();
-  unsigned short readUInt16LE();
-  unsigned char readUInt8();
-  char readInt8();
-  short readInt16BE();
-  short readInt16LE();
-  int readInt32BE();
-  int readInt32LE();
-  long long readInt64BE();
-  long long readInt64LE();
-  double readDoubleBE();
-  double readDoubleLE();
-  float readFloatBE();
-  float readFloatLE();
-  unsigned int readUIntLE(unsigned int size);
-
-  template<typename T>
-  T readByte(bool isLE = false) {
-    unsigned char buf[sizeof(T)];
-    unsigned int size = sizeof(T);
-    read((char*)buf, size);
-    if ((isLittleEndian && !isLE) || (!isLittleEndian && isLE)) {
-      unsigned char temp;
-      for (unsigned int i = 0; i < size / 2; i++) {
-        temp = buf[i];
-        buf[i] = buf[size - 1 - i];
-        buf[size - 1 - i] = temp;
-      }
-    }
-    
-    T* result = (T*)buf;
-
-    return *result;
-  }
-};
-#endif // !_ACB_READER_H_
diff --git a/lib/ACBExtractor/include/TrackList.h b/lib/ACBExtractor/include/TrackList.h
deleted file mode 100644
index 3ba4f3d..0000000
--- a/lib/ACBExtractor/include/TrackList.h
+++ /dev/null
@@ -1,29 +0,0 @@
-#ifndef _ACB_TRACKLIST_H_
-#define _ACB_TRACKLIST_H_
-
-#include "./Reader.h"
-#include "./UTFTable.h"
-
-typedef struct {
-  unsigned int cueId;
-  std::string cueName;
-  unsigned short wavId;
-  unsigned char encodeType;
-  unsigned char streaming;
-} track;
-
-class TrackList {
-private:
-  
-public:
-  UTFTable* cueTable;
-  UTFTable* cueNameTable;
-  UTFTable* waveformTable;
-  UTFTable* synthTable;
-  track* tracks;
-  unsigned int length;
-
-  TrackList(UTFTable* utf);
-  ~TrackList();
-};
-#endif // !_ACB_TRACKLIST_H_
diff --git a/lib/ACBExtractor/include/UTFTable.h b/lib/ACBExtractor/include/UTFTable.h
deleted file mode 100644
index b692860..0000000
--- a/lib/ACBExtractor/include/UTFTable.h
+++ /dev/null
@@ -1,72 +0,0 @@
-#ifndef _ACB_UTFTABLE_H_
-#define _ACB_UTFTABLE_H_
-
-#include "./Reader.h"
-
-typedef struct {
-  char* columnName;
-  void* data;
-  unsigned char dataType;
-  unsigned int length;
-} acbData;
-
-typedef struct {
-  unsigned char columnType;
-  char* columnName;
-  void* data;
-  unsigned char dataType;
-  unsigned int length;
-} acbColumn;
-
-typedef acbData* acbRow;
-
-typedef struct {
-  unsigned short u1;
-  unsigned short tableDataOffset;
-  unsigned int stringDataOffset;
-  unsigned int binaryDataOffset;
-  unsigned int tableNameStringOffset;
-  unsigned short columnLength;
-  unsigned short rowTotalByte;
-  unsigned int rowLength;
-} acbHeader;
-
-class UTFTable {
-private:
-  typedef struct {
-    const unsigned char ZERO;
-    const unsigned char CONSTANT;
-    const unsigned char PERROW;
-    const unsigned char CONSTANT2;
-  } acbColumnType;
-  
-  static unsigned int dataLength[12];
-  static acbColumnType columnType;
-  Reader* r;
-  void readHeader();
-  char* readString(std::streampos offset, unsigned int* outlength);
-  void readBinary(unsigned char* outbuf, std::streampos offset, std::streampos length);
-  void readColumns();
-  void readRows();
-  template<typename Arg>
-  void readData(unsigned char type, Arg& columnOrData);
-  template<typename T, typename Arg>
-  void readDataTemplate(Arg& columnOrData);
-  template<typename Arg>
-  void deleteVoid(Arg& columnOrData);
-public:
-  friend class TrackList;
-  friend class ACBExtractor;
-
-  unsigned int length;
-  acbHeader* header;
-  acbColumn* columns;
-  acbRow* rows;
-  
-  char* name;
-  UTFTable(std::string acb);
-  UTFTable(unsigned char* acb, unsigned int l);
-  ~UTFTable();
-  acbData* get(unsigned int lineNumber, std::string columnName);
-};
-#endif // !_ACB_UTFTABLE_H_
diff --git a/lib/acb/acb.c b/lib/acb/acb.c
new file mode 100644
index 0000000..ed905a5
--- /dev/null
+++ b/lib/acb/acb.c
@@ -0,0 +1,915 @@
+#ifdef _WIN32
+#include <Windows.h>
+#include <wchar.h>
+#include <direct.h>
+#else
+#include <sys/stat.h>
+#endif // _WIN32
+
+#include "acb.h"
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+
+#define ACB_ENDIAN_BIG (1)
+#define ACB_ENDIAN_LITTLE (0)
+#define BUF_SIZE_MAX (128 * 1024)
+
+static int get_data_type_size(int data_type);
+
+static bool check_memory_endian() {
+  uint32_t n = 1;
+  uint8_t b = (uint8_t)n;
+  if (b) {
+    return ACB_ENDIAN_LITTLE;
+  } else {
+    return ACB_ENDIAN_BIG;
+  }
+}
+
+static uint8_t read_uint_8(uint8_t* buf) {
+  return *buf;
+}
+
+static int8_t read_int_8(uint8_t* buf) {
+  return *((int8_t*)buf);
+}
+
+static uint16_t read_uint_16(uint8_t* buf, bool endian) {
+  if (endian == check_memory_endian()) {
+    return *((uint16_t*)buf);
+  } else {
+    uint8_t tmp[2] = { buf[1], buf[0] };
+    return *((uint16_t*)tmp);
+  }
+}
+
+static int16_t read_int_16(uint8_t* buf, bool endian) {
+  if (endian == check_memory_endian()) {
+    return *((int16_t*)buf);
+  } else {
+    uint8_t tmp[2] = { buf[1], buf[0] };
+    return *((int16_t*)tmp);
+  }
+}
+
+static uint32_t read_uint_32(uint8_t* buf, bool endian) {
+  if (endian == check_memory_endian()) {
+    return *((uint32_t*)buf);
+  } else {
+    uint8_t tmp[4] = { buf[3], buf[2], buf[1], buf[0] };
+    return *((uint32_t*)tmp);
+  }
+}
+
+static int32_t read_int_32(uint8_t* buf, bool endian) {
+  if (endian == check_memory_endian()) {
+    return *((int32_t*)buf);
+  } else {
+    uint8_t tmp[4] = { buf[3], buf[2], buf[1], buf[0] };
+    return *((int32_t*)tmp);
+  }
+}
+
+static int64_t read_int_64(uint8_t* buf, bool endian) {
+  if (endian == check_memory_endian()) {
+    return *((int64_t*)buf);
+  } else {
+    uint8_t tmp[8] = { buf[7], buf[6], buf[5], buf[4], buf[3], buf[2], buf[1], buf[0] };
+    return *((int64_t*)tmp);
+  }
+}
+
+static uint64_t read_uint_64(uint8_t* buf, bool endian) {
+  if (endian == check_memory_endian()) {
+    return *((uint64_t*)buf);
+  } else {
+    uint8_t tmp[8] = { buf[7], buf[6], buf[5], buf[4], buf[3], buf[2], buf[1], buf[0] };
+    return *((uint64_t*)tmp);
+  }
+}
+
+static float read_float(uint8_t* buf, bool endian) {
+  if (endian == check_memory_endian()) {
+    return *((float*)buf);
+  } else {
+    uint8_t tmp[8] = { buf[3], buf[2], buf[1], buf[0] };
+    return *((float*)tmp);
+  }
+}
+
+static double read_double(uint8_t* buf, bool endian) {
+  if (endian == check_memory_endian()) {
+    return *((double*)buf);
+  } else {
+    uint8_t tmp[8] = { buf[7], buf[6], buf[5], buf[4], buf[3], buf[2], buf[1], buf[0] };
+    return *((double*)tmp);
+  }
+}
+
+static int acb_fseek(acb* acbp, long pos) {
+  if (pos > acbp->size) {
+    return 1;
+  }
+  long origin = ftell(acbp->f);
+  int res = fseek(acbp->f, acbp->start + pos, SEEK_SET);
+  if (!res) {
+    acbp->pos = pos;
+    fseek(acbp->f, origin, SEEK_SET);
+    return 0;
+  }
+  return res;
+}
+
+static long acb_ftell(acb* acbp) {
+  return acbp->pos;
+}
+
+static size_t acb_fread(void* dest, size_t size, size_t count, acb* acbp) {
+  if (acbp->pos + (size * count) > (size_t)acbp->size) {
+    return 0;
+  }
+  long origin = ftell(acbp->f);
+  fseek(acbp->f, acbp->start + acbp->pos, SEEK_SET);
+  int read = fread(dest, size, count, acbp->f);
+  if (read != 0) {
+    acbp->pos = acbp->pos + read;
+    fseek(acbp->f, origin, SEEK_SET);
+    return read;
+  }
+  fseek(acbp->f, origin, SEEK_SET);
+  return 0;
+}
+
+static int acb_fclose(acb* acbp) {
+  if (acbp->f && acbp->start == 0) {
+    return fclose(acbp->f);
+    acbp->f = NULL;
+  }
+
+  acbp->start = 0;
+  acbp->size = 0;
+  acbp->pos = 0;
+  return 0;
+}
+
+static void get_encode_type_suffix(uint8_t type, char* out) {
+  switch (type) {
+  case 0:
+  {
+    strcpy(out, ".adx");
+    return;
+  }
+  case 2:
+  {
+    strcpy(out, ".hca");
+    return;
+  }
+  case 7:
+  {
+    strcpy(out, ".at3");
+    return;
+  }
+  case 8:
+  {
+    strcpy(out, ".vag");
+    return;
+  }
+  case 9:
+  {
+    strcpy(out, ".bcwav");
+    return;
+  }
+  case 13:
+  {
+    strcpy(out, ".dsp");
+    return;
+  }
+  default:
+    return;
+  }
+}
+
+acb* acb_open(const char* path) {
+#ifdef _WIN32
+  int len = MultiByteToWideChar(CP_UTF8, 0, path, -1, NULL, 0);
+  wchar_t* wpath = (wchar_t*)malloc(len * sizeof(wchar_t));
+  if (!wpath) return NULL;
+  MultiByteToWideChar(CP_UTF8, 0, path, -1, wpath, len);
+  FILE* f = _wfopen(wpath, L"rb");
+  free(wpath);
+#else
+  FILE* f = fopen(path, "rb");
+#endif // _WIN32
+
+  if (f) {
+    acb* _acb = (acb*)malloc(sizeof(acb));
+    if (!_acb) return NULL;
+    memset(_acb, 0, sizeof(acb));
+
+    _acb->f = f;
+    memset(_acb->path, 0, MAX_PATH_LENGTH);
+    strcpy(_acb->path, path);
+
+    _acb->start = 0;
+    fseek(f, 0L, SEEK_END);
+    _acb->size = ftell(f);
+    fseek(f, 0L, SEEK_SET);
+    _acb->pos = 0;
+
+    acb_read_file_header(_acb, &(_acb->file_header));
+    acb_read_utf_header(_acb, &(_acb->utf_header));
+    _acb->name = acb_read_string(_acb, _acb->utf_header.table_name_string_offset);
+
+    _acb->columns = (acb_utf_column*)malloc(_acb->utf_header.column_length * sizeof(acb_utf_column));
+    acb_read_column(_acb, _acb->columns, _acb->utf_header.column_length);
+
+    return _acb;
+  } else {
+    return NULL;
+  }
+}
+
+acb* acb_memopen(acb* parent, long start, long size) {
+  acb* _acb = (acb*)malloc(sizeof(acb));
+  if (!_acb) return NULL;
+  memset(_acb, 0, sizeof(acb));
+
+  _acb->f = parent->f;
+  memset(_acb->path, 0, MAX_PATH_LENGTH);
+
+  _acb->start = start;
+  _acb->size = size;
+  _acb->pos = 0;
+
+  acb_read_file_header(_acb, &(_acb->file_header));
+  acb_read_utf_header(_acb, &(_acb->utf_header));
+  _acb->name = acb_read_string(_acb, _acb->utf_header.table_name_string_offset);
+
+  _acb->columns = (acb_utf_column*)malloc(_acb->utf_header.column_length * sizeof(acb_utf_column));
+  acb_read_column(_acb, _acb->columns, _acb->utf_header.column_length);
+
+  return _acb;
+}
+
+acb* acb_memopen_awb(acb* parent, long start, long size) {
+  acb* _acb = (acb*)malloc(sizeof(acb));
+  if (!_acb) return NULL;
+  memset(_acb, 0, sizeof(acb));
+
+  _acb->f = parent->f;
+  memset(_acb->path, 0, MAX_PATH_LENGTH);
+
+  _acb->start = start;
+  _acb->size = size;
+  _acb->pos = 0;
+
+  return _acb;
+}
+
+void acb_close(acb* acbp) {
+  acb_fclose(acbp);
+  acbp->f = NULL;
+  acbp->start = 0;
+  acbp->size = 0;
+  acbp->pos = 0;
+
+  if (acbp->name != NULL) {
+    free(acbp->name);
+    acbp->name = NULL;
+  }
+
+  if (acbp->columns != NULL) {
+    for (int i = 0; i < acbp->utf_header.column_length; i++) {
+      if (acbp->columns[i].name) { free(acbp->columns[i].name); acbp->columns[i].name = NULL; }
+      if (acbp->columns[i].const_value) { free(acbp->columns[i].const_value); acbp->columns[i].const_value = NULL; }
+    }
+    free(acbp->columns);
+    acbp->columns = NULL;
+  }
+
+  free(acbp);
+}
+
+void acb_read_file_header(acb* acbp, acb_file_header* header) {
+  if (!acbp) return;
+  long original_pos = acb_ftell(acbp);
+  acb_fseek(acbp, 0);
+  uint8_t magic[4];
+  uint8_t size[4];
+  acb_fread(magic, 1, 4, acbp);
+  acb_fread(size, 1, 4, acbp);
+
+  header->magic = read_uint_32(magic, ACB_ENDIAN_BIG);
+  header->size = read_uint_32(size, ACB_ENDIAN_BIG);
+  acb_fseek(acbp, original_pos);
+}
+
+uint32_t acb_get_size(acb* acbp) {
+  if (!acbp) return 0;
+  return acbp->file_header.size;
+}
+
+void acb_read_utf_header(acb* acbp, acb_utf_header* header) {
+  if (!acbp) return;
+  long original_pos = acb_ftell(acbp);
+  acb_fseek(acbp, 8);
+
+  uint8_t unknown1[2];
+  uint8_t table_data_offset[2];
+  uint8_t string_data_offset[4];
+  uint8_t binary_data_offset[4];
+  uint8_t table_name_string_offset[4];
+  uint8_t column_length[2];
+  uint8_t row_total_byte[2];
+  uint8_t row_length[4];
+
+  acb_fread(unknown1, 1, 2, acbp);
+  acb_fread(table_data_offset, 1, 2, acbp);
+  acb_fread(string_data_offset, 1, 4, acbp);
+  acb_fread(binary_data_offset, 1, 4, acbp);
+  acb_fread(table_name_string_offset, 1, 4, acbp);
+  acb_fread(column_length, 1, 2, acbp);
+  acb_fread(row_total_byte, 1, 2, acbp);
+  acb_fread(row_length, 1, 4, acbp);
+
+  header->unknown1 = read_uint_16(unknown1, ACB_ENDIAN_BIG);
+  header->table_data_offset = read_uint_16(table_data_offset, ACB_ENDIAN_BIG);
+  header->string_data_offset = read_uint_32(string_data_offset, ACB_ENDIAN_BIG);
+  header->binary_data_offset = read_uint_32(binary_data_offset, ACB_ENDIAN_BIG);
+  header->table_name_string_offset = read_uint_32(table_name_string_offset, ACB_ENDIAN_BIG);
+  header->column_length = read_uint_16(column_length, ACB_ENDIAN_BIG);
+  header->row_total_byte = read_uint_16(row_total_byte, ACB_ENDIAN_BIG);
+  header->row_length = read_uint_32(row_length, ACB_ENDIAN_BIG);
+
+  acb_fseek(acbp, original_pos);
+}
+
+char* acb_read_string(acb* acbp, uint32_t offset) {
+  if (!acbp) return NULL;
+  long original_pos = acb_ftell(acbp);
+  acb_fseek(acbp, 8 + acbp->utf_header.string_data_offset + offset);
+
+  char c;
+  uint32_t len = 0;
+
+  while (true) {
+    acb_fread(&c, 1, 1, acbp);
+    len++;
+    if (c == '\0') {
+      break;
+    }
+  }
+
+  char* res = (char*)malloc(len * sizeof(char));
+  acb_fseek(acbp, 8 + acbp->utf_header.string_data_offset + offset);
+  acb_fread(res, 1, len, acbp);
+
+  acb_fseek(acbp, original_pos);
+  return res;
+}
+
+void acb_read_column(acb* acbp, acb_utf_column* columns, int column_length) {
+  if (!acbp) return;
+  long original_pos = acb_ftell(acbp);
+
+  acb_fseek(acbp, 8 + 24);
+
+  for (int i = 0; i < column_length; i++) {
+    uint8_t type[1];
+    uint8_t name_offset[4];
+    acb_fread(type, 1, 1, acbp);
+    acb_fread(name_offset, 1, 4, acbp);
+
+    columns[i].type = type[0];
+    columns[i].index = i;
+    columns[i].name = acb_read_string(acbp, read_uint_32(name_offset, ACB_ENDIAN_BIG));
+    columns[i].const_value = NULL;
+
+    int column_type = columns[i].type & 0xf0;
+    int data_type = columns[i].type & 0x0f;
+
+    if (column_type == 0x30 || column_type == 0x70) {
+      switch (data_type) {
+      case 0x00:
+      {
+        uint8_t* data = (uint8_t*)malloc(1);
+        acb_fread(data, 1, 1, acbp);
+        columns[i].const_value = data;
+        break;
+      }
+      case 0x01:
+      {
+        int8_t* data = (int8_t*)malloc(1);
+        acb_fread(data, 1, 1, acbp);
+        columns[i].const_value = data;
+        break;
+      }
+      case 0x02:
+      {
+        uint8_t data[2];
+        acb_fread(data, 1, 2, acbp);
+        uint16_t* res = (uint16_t*)malloc(2);
+        if (!res) break;
+        *res = read_uint_16(data, ACB_ENDIAN_BIG);
+        columns[i].const_value = res;
+        break;
+      }
+      case 0x03:
+      {
+        uint8_t data[2];
+        acb_fread(data, 1, 2, acbp);
+        int16_t* res = (int16_t*)malloc(2);
+        if (!res) break;
+        *res = read_int_16(data, ACB_ENDIAN_BIG);
+        columns[i].const_value = res;
+        break;
+      }
+      case 0x04:
+      {
+        uint8_t data[4];
+        acb_fread(data, 1, 4, acbp);
+        uint32_t* res = (uint32_t*)malloc(4);
+        if (!res) break;
+        *res = read_uint_32(data, ACB_ENDIAN_BIG);
+        columns[i].const_value = res;
+        break;
+      }
+      case 0x05:
+      {
+        uint8_t data[4];
+        acb_fread(data, 1, 4, acbp);
+        int32_t* res = (int32_t*)malloc(4);
+        if (!res) break;
+        *res = read_int_32(data, ACB_ENDIAN_BIG);
+        columns[i].const_value = res;
+        break;
+      }
+      case 0x08:
+      {
+        uint8_t data[4];
+        acb_fread(data, 1, 4, acbp);
+        float* res = (float*)malloc(4);
+        if (!res) break;
+        *res = read_float(data, ACB_ENDIAN_BIG);
+        columns[i].const_value = res;
+        break;
+      }
+      case 0x06:
+      {
+        uint8_t data[8];
+        acb_fread(data, 1, 8, acbp);
+        uint64_t* res = (uint64_t*)malloc(8);
+        if (!res) break;
+        *res = read_uint_64(data, ACB_ENDIAN_BIG);
+        columns[i].const_value = res;
+        break;
+      }
+      case 0x07:
+      {
+        uint8_t data[8];
+        acb_fread(data, 1, 8, acbp);
+        int64_t* res = (int64_t*)malloc(8);
+        if (!res) break;
+        *res = read_int_64(data, ACB_ENDIAN_BIG);
+        columns[i].const_value = res;
+        break;
+      }
+      case 0x09:
+      {
+        uint8_t data[8];
+        acb_fread(data, 1, 8, acbp);
+        double* res = (double*)malloc(8);
+        if (!res) break;
+        *res = read_double(data, ACB_ENDIAN_BIG);
+        columns[i].const_value = res;
+        break;
+      }
+      case 0x0A:
+      {
+        uint8_t data[4];
+        acb_fread(data, 1, 4, acbp);
+        columns[i].const_value = acb_read_string(acbp, read_uint_32(data, ACB_ENDIAN_BIG));
+        break;
+      }
+      case 0x0B:
+      {
+        uint8_t offset[4];
+        uint8_t length[4];
+
+        acb_fread(offset, 1, 4, acbp);
+        acb_fread(length, 1, 4, acbp);
+
+        acb_utf_binary_desc* desc = (acb_utf_binary_desc*)malloc(sizeof(acb_utf_binary_desc));
+        if (!desc) break;
+        desc->offset = read_uint_32(offset, ACB_ENDIAN_BIG);
+        desc->length = read_uint_32(length, ACB_ENDIAN_BIG);
+        columns[i].const_value = desc;
+        break;
+      }
+      default:
+        break;
+      }
+    }
+  }
+
+  acb_fseek(acbp, original_pos);
+}
+
+int get_data_type_size(int data_type) {
+  switch (data_type) {
+  case 0x00: { return 1; }
+  case 0x01: { return 1; }
+  case 0x02: { return 2; }
+  case 0x03: { return 2; }
+  case 0x04: { return 4; }
+  case 0x05: { return 4; }
+  case 0x06: { return 8; }
+  case 0x07: { return 8; }
+  case 0x08: { return 4; }
+  case 0x09: { return 8; }
+  case 0x0A: { return 4; }
+  case 0x0B: { return 8; }
+  default: { return 0; }
+  }
+}
+
+int acb_get_row_data(acb* acbp, uint32_t row_index, const char* column_name, void* res, uint32_t buf_size) {
+  if (!acbp) return 0;
+
+  int column_index = -1;
+  int i = 0;
+  for (i = 0; i < acbp->utf_header.column_length; i++) {
+    if (strcmp(acbp->columns[i].name, column_name) == 0) {
+      column_index = i;
+      break;
+    }
+  }
+
+  if (column_index == -1) return 0;
+
+  int data_type = acbp->columns[column_index].type & 0x0f;
+
+  if (res == NULL) {
+    return data_type;
+  }
+
+  int column_type = acbp->columns[column_index].type & 0xf0;
+
+  if ((column_type == 0x30 || column_type == 0x70) && acbp->columns[column_index].const_value != NULL) {
+    switch (data_type) {
+    case 0x00: { *((uint8_t*)res) = *((uint8_t*)(acbp->columns[column_index].const_value)); return data_type; }
+    case 0x01: { *((int8_t*)res) = *((int8_t*)(acbp->columns[column_index].const_value)); return data_type; }
+    case 0x02: { *((uint16_t*)res) = *((uint16_t*)(acbp->columns[column_index].const_value)); return data_type; }
+    case 0x03: { *((int16_t*)res) = *((int16_t*)(acbp->columns[column_index].const_value)); return data_type; }
+    case 0x04: { *((uint32_t*)res) = *((uint32_t*)(acbp->columns[column_index].const_value)); return data_type; }
+    case 0x05: { *((int32_t*)res) = *((int32_t*)(acbp->columns[column_index].const_value)); return data_type; }
+    case 0x06: { *((uint64_t*)res) = *((uint64_t*)(acbp->columns[column_index].const_value)); return data_type; }
+    case 0x07: { *((int64_t*)res) = *((int64_t*)(acbp->columns[column_index].const_value)); return data_type; }
+    case 0x08: { *((float*)res) = *((float*)(acbp->columns[column_index].const_value)); return data_type; }
+    case 0x09: { *((double*)res) = *((double*)(acbp->columns[column_index].const_value)); return data_type; }
+    case 0x0A:
+    {
+      if (strlen((char*)(acbp->columns[column_index].const_value)) > buf_size - 1) {
+        strncpy((char*)res, (char*)(acbp->columns[column_index].const_value), buf_size - 1);
+      } else {
+        strcpy((char*)res, (char*)(acbp->columns[column_index].const_value));
+      }
+      return data_type;
+    }
+    case 0x0B:
+    {
+      ((acb_utf_binary_desc*)res)->offset = ((acb_utf_binary_desc*)acbp->columns[column_index].const_value)->offset;
+      ((acb_utf_binary_desc*)res)->length = ((acb_utf_binary_desc*)acbp->columns[column_index].const_value)->length;
+      return data_type;
+    }
+    default: { return 0; }
+    }
+  }
+
+  long original_pos = acb_ftell(acbp);
+
+  uint32_t pos = 8 + acbp->utf_header.table_data_offset + row_index * acbp->utf_header.row_total_byte;
+
+  for (i = 0; i < column_index; i++) {
+    int column_type = acbp->columns[i].type & 0xf0;
+    if (column_type == 0x30 || column_type == 0x70) {
+      continue;
+    }
+    int data_type = acbp->columns[i].type & 0x0f;
+    pos += get_data_type_size(data_type);
+  }
+
+  acb_fseek(acbp, pos);
+
+  switch (data_type) {
+  case 0x00: { uint8_t tmp[1]; acb_fread(tmp, 1, 1, acbp); *((uint8_t*)res) = read_uint_8(tmp); break; }
+  case 0x01: { uint8_t tmp[1]; acb_fread(tmp, 1, 1, acbp); *((int8_t*)res) = read_int_8(tmp); break; }
+  case 0x02: { uint8_t tmp[2]; acb_fread(tmp, 1, 2, acbp); *((uint16_t*)res) = read_uint_16(tmp, ACB_ENDIAN_BIG); break; }
+  case 0x03: { uint8_t tmp[2]; acb_fread(tmp, 1, 2, acbp); *((int16_t*)res) = read_int_16(tmp, ACB_ENDIAN_BIG); break; }
+  case 0x04: { uint8_t tmp[4]; acb_fread(tmp, 1, 4, acbp); *((uint32_t*)res) = read_uint_32(tmp, ACB_ENDIAN_BIG); break; }
+  case 0x05: { uint8_t tmp[4]; acb_fread(tmp, 1, 4, acbp); *((int32_t*)res) = read_int_32(tmp, ACB_ENDIAN_BIG); break; }
+  case 0x06: { uint8_t tmp[8]; acb_fread(tmp, 1, 8, acbp); *((uint64_t*)res) = read_uint_64(tmp, ACB_ENDIAN_BIG); break; }
+  case 0x07: { uint8_t tmp[8]; acb_fread(tmp, 1, 8, acbp); *((int64_t*)res) = read_int_64(tmp, ACB_ENDIAN_BIG); break; }
+  case 0x08: { uint8_t tmp[4]; acb_fread(tmp, 1, 4, acbp); *((float*)res) = read_float(tmp, ACB_ENDIAN_BIG); break; }
+  case 0x09: { uint8_t tmp[8]; acb_fread(tmp, 1, 8, acbp); *((double*)res) = read_double(tmp, ACB_ENDIAN_BIG); break; }
+  case 0x0A:
+  {
+    uint8_t tmp[4];
+    acb_fread(tmp, 1, 4, acbp);
+    char* tmpstr = acb_read_string(acbp, read_uint_32(tmp, ACB_ENDIAN_BIG));
+    if (strlen(tmpstr) > buf_size - 1) {
+      strncpy((char*)res, tmpstr, buf_size - 1);
+    } else {
+      strcpy((char*)res, tmpstr);
+    }
+    free(tmpstr);
+    break;
+  }
+  case 0x0B:
+  {
+    uint8_t offset[4];
+    uint8_t length[4];
+
+    acb_fread(offset, 1, 4, acbp);
+    acb_fread(length, 1, 4, acbp);
+    ((acb_utf_binary_desc*)res)->offset = read_uint_32(offset, ACB_ENDIAN_BIG);
+    ((acb_utf_binary_desc*)res)->length = read_uint_32(length, ACB_ENDIAN_BIG);
+    break;
+  }
+  default: { break; }
+  }
+
+  acb_fseek(acbp, original_pos);
+  return data_type;
+}
+
+acb_track* acb_get_track_list(acb* acbp, uint32_t* len) {
+  if (!acbp) return NULL;
+  if (acbp->start != 0) return NULL;
+
+  acb_utf_binary_desc desc_cue_table;
+  acb_utf_binary_desc desc_cue_name_table;
+  acb_utf_binary_desc desc_wave_form_table;
+  acb_utf_binary_desc desc_synth_table;
+
+  acb_get_row_data(acbp, 0, "CueTable", &desc_cue_table, 0);
+  acb_get_row_data(acbp, 0, "CueNameTable", &desc_cue_name_table, 0);
+  acb_get_row_data(acbp, 0, "WaveformTable", &desc_wave_form_table, 0);
+  acb_get_row_data(acbp, 0, "SynthTable", &desc_synth_table, 0);
+
+  acb* cue_table = acb_memopen(acbp, 8 + acbp->utf_header.binary_data_offset + desc_cue_table.offset, desc_cue_table.length);
+  acb* name_table = acb_memopen(acbp, 8 + acbp->utf_header.binary_data_offset + desc_cue_name_table.offset, desc_cue_name_table.length);
+  acb* wave_form_table = acb_memopen(acbp, 8 + acbp->utf_header.binary_data_offset + desc_wave_form_table.offset, desc_wave_form_table.length);
+  acb* synth_table = acb_memopen(acbp, 8 + acbp->utf_header.binary_data_offset + desc_synth_table.offset, desc_synth_table.length);
+
+  acb_track* track_list = (acb_track*)malloc(cue_table->utf_header.row_length * sizeof(acb_track));
+  if (track_list) {
+    memset(track_list, 0, cue_table->utf_header.row_length * sizeof(acb_track));
+    for (uint16_t i = 0; i < cue_table->utf_header.row_length; i++) {
+      int found = 0;
+      for (uint16_t j = 0; j < name_table->utf_header.row_length; j++) {
+        uint16_t cue_index = 0;
+        acb_get_row_data(name_table, j, "CueIndex", &cue_index, 0);
+        if (cue_index == i) {
+          acb_get_row_data(name_table, j, "CueName", (track_list + i)->cue_name, 64);
+          found = 1;
+        }
+      }
+
+      if (!found) {
+        strcpy((track_list + i)->cue_name, "UNKNOWN");
+      }
+
+      uint32_t cue_id = 0;
+      acb_get_row_data(cue_table, i, "CueId", &cue_id, 0);
+
+      (track_list + i)->cue_id = cue_id;
+
+      uint8_t reference_type = 0;
+      acb_get_row_data(cue_table, i, "ReferenceType", &reference_type, 0);
+      if (reference_type != 3 && reference_type != 8) {
+        printf("ReferenceType %d not implemented.\n", reference_type);
+        break;
+      }
+
+      uint16_t reference_index = 0;
+      acb_get_row_data(cue_table, i, "ReferenceIndex", &reference_index, 0);
+
+      acb_utf_binary_desc desc_ref_item;
+      acb_get_row_data(synth_table, reference_index, "ReferenceItems", &desc_ref_item, 0);
+
+
+      uint8_t _b[2];
+      long origin = acb_ftell(synth_table);
+      acb_fseek(synth_table, 8 + synth_table->utf_header.binary_data_offset + desc_ref_item.offset + 2);
+      acb_fread(_b, 1, 2, synth_table);
+      acb_fseek(synth_table, origin);
+      uint16_t b = read_uint_16(_b, ACB_ENDIAN_BIG);
+
+      uint16_t wav_id = 0;
+      // if (b >= wave_form_table->utf_header.row_length) {
+      if (b < wave_form_table->utf_header.row_length) {
+        int res = acb_get_row_data(wave_form_table, b, "Id", &wav_id, 0);
+        if (res == 0) {
+          acb_get_row_data(wave_form_table, b, "MemoryAwbId", &wav_id, 0);
+        }
+      } else {
+        acb_get_row_data(wave_form_table, b, "MemoryAwbId", &wav_id, 0);
+      }
+
+      (track_list + i)->wav_id = wav_id;
+
+      uint8_t encode_type = 0;
+      uint8_t streaming = 0;
+      acb_get_row_data(wave_form_table, b, "EncodeType", &encode_type, 0);
+      acb_get_row_data(wave_form_table, b, "Streaming", &streaming, 0);
+
+      (track_list + i)->encode_type = encode_type;
+      (track_list + i)->streaming = streaming;
+    }
+  }
+
+  *len = cue_table->utf_header.row_length;
+
+  acb_close(cue_table);
+  acb_close(name_table);
+  acb_close(wave_form_table);
+  acb_close(synth_table);
+
+  return track_list;
+}
+
+
+int acb_extract(acb* acbp, const char* target_dir, acb_extract_callback callback) {
+  if (!acbp) return 1;
+  if (acbp->start != 0) return 2;
+
+  uint32_t track_list_length = 0;
+  acb_track* track_list = acb_get_track_list(acbp, &track_list_length);
+
+  acb_utf_binary_desc desc_awb;
+  acb_get_row_data(acbp, 0, "AwbFile", &desc_awb, 0);
+
+  acb* awb = acb_memopen_awb(acbp, 8 + acbp->utf_header.binary_data_offset + desc_awb.offset, desc_awb.length);
+
+  uint8_t magic[4];
+  acb_fread(magic, 1, 4, awb);
+  if (read_uint_32(magic, ACB_ENDIAN_BIG) != 0x41465332) {
+    acb_close(awb);
+    free(track_list);
+    return 3;
+  }
+
+  uint8_t offset_size = 0;
+  acb_fseek(awb, 5);
+  acb_fread(&offset_size, 1, 1, awb);
+  acb_fseek(awb, 8);
+  uint8_t file_count_buf[4];
+  uint8_t alignment_buf[4];
+  acb_fread(file_count_buf, 1, 4, awb);
+  acb_fread(alignment_buf, 1, 4, awb);
+  uint32_t file_count = read_uint_32(file_count_buf, ACB_ENDIAN_LITTLE);
+  uint32_t alignment = read_uint_32(alignment_buf, ACB_ENDIAN_LITTLE);
+
+  uint16_t* id_arr = (uint16_t*)malloc(file_count * sizeof(uint16_t));
+  if (!id_arr) {
+    acb_close(awb);
+    free(track_list);
+    return 4;
+  }
+
+  uint8_t id_buf[2];
+  for (uint32_t i = 0; i < file_count; i++) {
+    acb_fread(id_buf, 1, 2, awb);
+    *(id_arr + i) = read_uint_16(id_buf, ACB_ENDIAN_LITTLE);
+  }
+
+  uint64_t* file_end_points = (uint64_t*)malloc((file_count + 1) * sizeof(uint64_t));
+  if (!file_end_points) {
+    free(id_arr);
+    acb_close(awb);
+    free(track_list);
+    return 4;
+  }
+
+  uint8_t ep_8[8] = { 0 };
+  for (uint32_t i = 0; i < file_count + 1; i++) {
+    acb_fread(ep_8, 1, offset_size, awb);
+    *(file_end_points + i) = read_uint_64(ep_8, ACB_ENDIAN_LITTLE);
+  }
+
+  acb_awb_wav_file* files = (acb_awb_wav_file*)malloc(file_count * sizeof(acb_awb_wav_file));
+  if (!files) {
+    free(file_end_points);
+    free(id_arr);
+    acb_close(awb);
+    free(track_list);
+    return 5;
+  }
+
+  for (uint32_t i = 0; i < file_count; i++) {
+    (files + i)->id = *(id_arr + i);
+    (files + i)->start = (uint32_t)ceil((double)(file_end_points[i]) / (double)(alignment)) * alignment;
+    (files + i)->length = (uint32_t)(*(file_end_points + i + 1)) - (files + i)->start;
+  }
+
+  char name[MAX_PATH_LENGTH] = { 0 };
+  uint8_t* buffer = (uint8_t*)malloc(BUF_SIZE_MAX * sizeof(uint8_t));
+  if (!buffer) {
+    free(files);
+    free(file_end_points);
+    free(id_arr);
+    acb_close(awb);
+    free(track_list);
+    return 6;
+  }
+
+#ifdef _WIN32
+  wchar_t* target_dir_w = NULL;
+  if (target_dir) {
+    int len = MultiByteToWideChar(CP_UTF8, 0, target_dir, -1, NULL, 0);
+    target_dir_w = (wchar_t*)malloc(len * sizeof(wchar_t));
+    if (target_dir_w) {
+      MultiByteToWideChar(CP_UTF8, 0, target_dir, -1, target_dir_w, len);
+      _wmkdir(target_dir_w);
+      free(target_dir_w);
+    }
+  }
+#else
+  mkdir(target_dir, 0777);
+#endif
+
+  for (uint32_t i = 0; i < track_list_length; i++) {
+    acb_track* track = (track_list + i);
+    bool result = false;
+    for (uint32_t j = 0; j < file_count; j++) {
+      if ((files + j)->id == track->wav_id) {
+        acb_awb_wav_file* file = (files + track->wav_id);
+        uint32_t total = 0;
+        uint32_t read = 0;
+        acb_fseek(awb, file->start);
+        memset(name, 0, MAX_PATH_LENGTH);
+        if (target_dir) {
+          strcat(name, target_dir);
+#ifdef _WIN32
+          strcat(name, "\\");
+#else
+          strcat(name, "/");
+#endif
+        }
+        strcat(name, track->cue_name);
+        char suffix[16] = { 0 };
+        get_encode_type_suffix(track->encode_type, suffix);
+        strcat(name, suffix);
+#ifdef _WIN32
+        int len = MultiByteToWideChar(CP_UTF8, 0, name, -1, NULL, 0);
+        wchar_t* wpath = (wchar_t*)malloc(len * sizeof(wchar_t));
+        if (!wpath) break;
+        MultiByteToWideChar(CP_UTF8, 0, name, -1, wpath, len);
+        FILE* wp = _wfopen(wpath, L"wb+");
+        free(wpath);
+#else
+        FILE* wp = fopen(name, "wb+");
+#endif
+        if (!wp) {
+          free(files);
+          free(file_end_points);
+          free(id_arr);
+          acb_close(awb);
+          free(track_list);
+          return 7;
+        }
+        while (1) {
+          if (total + BUF_SIZE_MAX <= file->length) {
+            read = acb_fread(buffer, 1, BUF_SIZE_MAX, awb);
+            total += read;
+            fwrite(buffer, 1, read, wp);
+          } else {
+            read = acb_fread(buffer, 1, file->length - total, awb);
+            total += read;
+            fwrite(buffer, 1, read, wp);
+            break;
+          }
+        }
+
+        fclose(wp);
+        result = true;
+        break;
+      }
+    }
+
+    if (callback) {
+      callback(i + 1, track_list_length, name, result);
+    }
+  }
+
+  free(buffer);
+  free(files);
+  free(file_end_points);
+  free(id_arr);
+  acb_close(awb);
+  free(track_list);
+
+  return 0;
+}
diff --git a/lib/acb/acb.h b/lib/acb/acb.h
new file mode 100644
index 0000000..b05e79e
--- /dev/null
+++ b/lib/acb/acb.h
@@ -0,0 +1,93 @@
+#ifndef __ACB_H__
+#define __ACB_H__
+
+#ifdef _WIN32
+#define MAX_PATH_LENGTH 260
+#else
+#define MAX_PATH_LENGTH 1024
+#endif
+
+#include <stdint.h>
+#include <stdbool.h>
+#include <stdio.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+  typedef struct acb_file_header {
+    uint32_t size;
+    uint32_t magic;
+  } acb_file_header;
+
+  typedef struct acb_utf_header {
+    uint16_t unknown1;
+    uint16_t table_data_offset;
+    uint32_t string_data_offset;
+    uint32_t binary_data_offset;
+    uint32_t table_name_string_offset;
+    uint16_t column_length;
+    uint16_t row_total_byte;
+    uint32_t row_length;
+  } acb_utf_header;
+
+  typedef struct acb_utf_column {
+    char* name;
+    uint32_t index;
+    uint8_t type;
+    void* const_value;
+  } acb_utf_column;
+
+  typedef struct acb_track {
+    int32_t cue_id;
+    char cue_name[64];
+    uint16_t wav_id;
+    uint8_t encode_type;
+    uint8_t streaming;
+  } acb_track;
+
+  typedef struct acb_awb_wav_file {
+    uint16_t id;
+    uint32_t start;
+    uint32_t length;
+  } acb_awb_wav_file;
+
+  typedef struct acb {
+    char path[MAX_PATH_LENGTH];
+
+    FILE* f;
+    long start;
+    long size;
+    long pos;
+
+    acb_file_header file_header;
+    acb_utf_header utf_header;
+    char* name;
+    acb_utf_column* columns;
+  } acb;
+
+  typedef struct acb_utf_binary_desc {
+    uint32_t offset;
+    uint32_t length;
+  } acb_utf_binary_desc;
+
+  typedef void (*acb_extract_callback)(uint32_t completed, uint32_t total, const char* file, bool status);
+
+  acb* acb_open(const char* path);
+  acb* acb_memopen(acb* parent, long start, long size);
+  acb* acb_memopen_awb(acb* parent, long start, long size);
+  void acb_close(acb* acbp);
+  void acb_read_file_header(acb* acbp, acb_file_header* header);
+  void acb_read_utf_header(acb* acbp, acb_utf_header* header);
+  char* acb_read_string(acb* acbp, uint32_t offset);
+  void acb_read_column(acb* acbp, acb_utf_column* columns, int column_length);
+  int acb_get_row_data(acb* acbp, uint32_t row_index, const char* column_name, void* out, uint32_t buf_size);
+  uint32_t acb_get_size(acb* acbp);
+  acb_track* acb_get_track_list(acb* acbp, uint32_t* len);
+  int acb_extract(acb* acbp, const char* target_dir, acb_extract_callback callback);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/src/ApiClient.cpp b/src/ApiClient.cpp
index e642d1e..f049541 100644
--- a/src/ApiClient.cpp
+++ b/src/ApiClient.cpp
@@ -54,7 +54,7 @@ nlohmann::json ApiClient::post(const String& path, nlohmann::json& args) {
   headers = curl_slist_append(headers, "KEYCHAIN: ");
   headers = curl_slist_append(headers, "PLATFORM-OS-VERSION: Android OS 13.3.7 / API-42 (XYZZ1Y/74726f6c6c)");
   headers = curl_slist_append(headers, (String("PARAM: ") + sha1Encrypt(_udid + _viewer + path + plain)).toCString());
-  headers = curl_slist_append(headers, "X-Unity-Version': '2017.4.2f2");
+  headers = curl_slist_append(headers, "X-Unity-Version': '2018.3.8f1");
   headers = curl_slist_append(headers, "CARRIER: google");
   headers = curl_slist_append(headers, (String("RES-VER: ") + _resVer).toCString());
   headers = curl_slist_append(headers, (String("UDID: ") + encode(_udid)).toCString());
diff --git a/src/CGSSAssetsDownloader.cpp b/src/CGSSAssetsDownloader.cpp
index 15dbdc6..c710bfc 100644
--- a/src/CGSSAssetsDownloader.cpp
+++ b/src/CGSSAssetsDownloader.cpp
@@ -6,7 +6,8 @@
 #include "./lz4.h"
 #include "./clHCA.h"
 #include "./CGSSAssetsDownloader.h"
-#include "../lib/ACBExtractor/include/ACBExtractor.h"
+// #include "../lib/ACBExtractor/include/ACBExtractor.h"
+#include "../lib/acb/acb.h"
 #include "../lib/lame/lame.h"
 #include "ApiClient.h"
 #include "../lib/jstype/fs.h"
@@ -214,7 +215,7 @@ void Downloader::download_single(string file) {
 }
 
 void show_introduction() {
-  printf("CGSSAssetsDownloader VERSION 2.0.0\n\n");
+  printf("CGSSAssetsDownloader VERSION 2.0.1\n\n");
 
   printf("Usage: \n");
   printf("CGSSAssetsDownloader [-v resource_version] [-a] [-u] [-mp3]\n");
@@ -262,14 +263,18 @@ void show_introduction() {
 }
 
 bool extract_acb (string acbFile) {
-  try {
+  acb* acbp = acb_open(acbFile.c_str());
+  int res = acb_extract(acbp, path::join(path::dirname(acbFile), String("_acb_") + path::basename(acbFile)).toCString(), nullptr);
+  acb_close(acbp);
+  return res == 0;
+  /*try {
     ACBExtractor extractor(acbFile);
     bool result = extractor.extract(nullptr);
     return result;
   } catch (const char* err) {
     printf("ACBExtractor Error: %s\n", err);
     return false;
-  }
+  }*/
 }
 
 int string_index_of (char* arr[], const char* str, int length) {
diff --git a/src/download.cpp b/src/download.cpp
index b1955a5..eeaea3a 100644
--- a/src/download.cpp
+++ b/src/download.cpp
@@ -131,7 +131,7 @@ bool download (string url, string path) {
   CURL* curl = curl_easy_init();
   struct curl_slist* headers = NULL;
 
-  headers = curl_slist_append(headers, "X-Unity-Version: 2017.4.2f2");
+  headers = curl_slist_append(headers, "X-Unity-Version: 2018.3.8f1");
   headers = curl_slist_append(headers, "Connection: Keep-Alive");
   headers = curl_slist_append(headers, "Accept-Encoding: gzip");
   headers = curl_slist_append(headers, "Accept: */*");