Skip to content

Instantly share code, notes, and snippets.

@Wunkolo
Last active October 12, 2022 22:19
Show Gist options
  • Save Wunkolo/213aa61eb0c874172aec97ebb8ab89c2 to your computer and use it in GitHub Desktop.
Save Wunkolo/213aa61eb0c874172aec97ebb8ab89c2 to your computer and use it in GitHub Desktop.
Platinum games "DAT" file dumper

DAT/DTT files are general containers found within the compressed .cpk files

struct Header
{
	std::uint32_t Magic; // 'DAT\x00'
	std::uint32_t FileCount;
	std::uint32_t FileTableOffset;
	std::uint32_t ExtensionTableOffset;
	std::uint32_t NameTableOffset;
	std::uint32_t SizeTableOffset;
	std::uint32_t UnknownOffset1C;
	std::uint32_t Unknown20; // Zero
};

All offsets are relative to the beginning of the file.

struct FileEntry
{
	std::uint32_t Offset;
};
union ExtentionTableEntry
{
	char Extension[4]; // 'bin\x00' , 'pak\x00', 'z\x00\x00\x00', etc
	std::uint32_t u32;
};
struct FileSizeEntry
{
	std::uint32_t Size;
};

Each table Offset has FileCount number of entries. NameTable is pre-pended with an std::uint32_t integer of the string's alignment. All strings take up alignment number of bytes including the null-terminator.

Example name table:

0000h:|16 00 00 00|71 31 37 31 5F 35 30 36 61 30 35 63  ....q171_506a05c
0010h: 30 5F 67 72 70 2E 70 61 6B 00|71 31 37 31 5F 31  0_grp.pak.q171_1
0020h: 32 66 32 39 36 33 61 5F 73 63 70 2E 62 69 6E 00| 2f2963a_scp.bin.
0030h: 71 31 37 31 5F 68 61 70 2E 70 61 6B 00 00 00 00  q171_hap.pak....
0040h: 00 00 00 00 00 00|                                ......

would have the alignment 0x16(22) and would have the strings q171_506a05c0_grp.pak\x00, q171_12f2963a_scp.bin\x00, and q171_hap.pak\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00 all padded to 22 bytes.

.dtt

DTT files are DAT files intended to store texture(.wtp) and model(.wmb) data among others(bxm,mot,sop,etc). .wtp files are regular DirectX textures(.dds) and can simply be renamed to a .dds extension. .wmb files contain model data in a deeply nested table format.

#include <iostream>
#include <cstdint>
#include <cstddef>
#include <string>
#include <vector>
#include <fstream>
#include <memory>
#include <experimental/filesystem>
namespace fs = std::experimental::filesystem;
template< typename T >
inline T Read(std::istream& Stream)
{
T Temp;
Stream.read(
reinterpret_cast<char*>(&Temp),
sizeof(T)
);
return Temp;
}
template< typename T >
inline void Read(std::istream& Stream, T& Data)
{
Stream.read(
reinterpret_cast<char*>(&Data),
sizeof(T)
);
}
inline void Read(std::istream& Stream, void* Data, std::size_t Size)
{
Stream.read(
reinterpret_cast<char*>(Data),
Size
);
}
namespace DAT
{
#pragma pack(push, 1)
struct Header
{
std::uint32_t Magic;
std::uint32_t FileCount;
std::uint32_t FileTableOffset;
std::uint32_t ExtensionOffset;
std::uint32_t NameTableOffset;
std::uint32_t SizeTableOffset;
std::uint32_t UnknownOffset;
std::uint32_t Null;
};
struct FileEntry
{
std::uint32_t Offset;
};
struct ExtentionEntry
{
char Extension[4];
};
struct SizeEntry
{
std::uint32_t Size;
};
#pragma pack(pop)
}
int main(int argc, char* argv[])
{
if( argc < 2 )
{
std::cout << "No files to process" << std::endl;
return 1;
}
std::ifstream FileIn(
argv[1],
std::ios::binary
);
if( FileIn.is_open() == false )
{
std::cout << "Error opening file: " << argv[1] << std::endl;
return 1;
}
DAT::Header Head;
Read(FileIn, Head);
if( Head.Magic != '\x00TAD' )
{
std::cout << "Invalid file (Magic Mismatch)" << std::endl;
return 1;
}
std::cout << "File count: " << Head.FileCount << std::endl;
if( Head.FileCount == 0 )
{
std::cout << "No files to extract" << std::endl;
return 1;
}
std::vector<DAT::FileEntry> FileOffsets;
FileOffsets.reserve(Head.FileCount);
FileIn.seekg(Head.FileTableOffset);
for( std::size_t i = 0; i < Head.FileCount; i++ )
{
FileOffsets.push_back(
Read<DAT::FileEntry>(FileIn)
);
}
std::vector<std::string> FileNames;
FileNames.reserve(Head.FileCount);
FileIn.seekg(Head.NameTableOffset);
std::uint32_t NameSize;
Read(FileIn, NameSize);
for( std::size_t i = 0; i < Head.FileCount; i++ )
{
std::string CurName;
CurName.resize(NameSize);
Read(
FileIn,
const_cast<char*>(CurName.data()),
NameSize
);
FileNames.push_back(CurName);
}
std::vector<DAT::SizeEntry> FileSizes;
FileSizes.reserve(Head.FileCount);
FileIn.seekg(Head.SizeTableOffset);
for( std::size_t i = 0; i < Head.FileCount; i++ )
{
FileSizes.push_back(
Read<DAT::SizeEntry>(FileIn)
);
}
fs::path ExtractPath = fs::current_path() / fs::path(argv[1]).stem();
fs::create_directories(ExtractPath);
for( std::size_t i = 0; i < Head.FileCount; i++ )
{
std::cout << " - " << FileNames[i] << " | (" << FileSizes[i].Size << " bytes)" << std::endl;
std::unique_ptr<std::uint8_t[]> Data(new std::uint8_t[FileSizes[i].Size]);
FileIn.seekg(FileOffsets[i].Offset);
FileIn.read(
reinterpret_cast<char*>(Data.get()),
FileSizes[i].Size
);
std::ofstream Extract(
ExtractPath / FileNames[i],
std::ios::binary
);
Extract.write(
reinterpret_cast<char*>(Data.get()),
FileSizes[i].Size
);
}
return 0;
}
@AbdulrahmanHadz
Copy link

How do i use this to unpack the .dat and .dtt files? I'm not familliar with ruby so i am confused. Many thanks.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment