Das File RDAHandler.d habt ihr abe schon entdeckt, oder? So ver/entwurstelt RD selber die Daten..
Keine Ahnung, ob man da was lernenkann, aber hier ist noch die Source:
module RDAHandler;
import common;
import main;
import mainform;
import dfl.listview;
version (Tango)
{
import
tango.text.convert.Format,
tango.io.device.FileMap,
tango.io.Stdout;
}
else
{
import std.stdio : writefln;
import
std.string, std.utf,
std.zlib,
std.stream,
std.file, std.path,
std.c.time;
}
/// main file header
struct Header
{
wchar magic[260];
ubyte uk[524];
uint firstBlock; // offset
}
/// structure describing a block's properties
struct BlockHeader
{
uint flags; // 2 = encrypted, 1 = compressed, 4 = memoryresident, 8 = deleted, (16 = filter?)
uint numFiles;
uint dirSize;
uint decompressedSize; // directory decompressed size
uint nextBlock; // offset of the following block (or filesize if last block)
istring toString()
{
version (Tango)
return Format("flags: {:b8} numFiles: {} dirSize: {} decompressed: {} nextBlock: {:X}", flags, numFiles, dirSize, decompressedSize, nextBlock);
else
return std.string.format("flags: %.8b numFiles: %d dirSize: %d decompressed: %d nextBlock: %X", flags, numFiles, dirSize, decompressedSize, nextBlock);
}
}
/// a single entry in the directory
struct DirEntry
{
wchar filename[260];
uint offset;
uint compressed;
uint filesize;
int timestamp;
uint uk;
istring toString()
{
version (Tango)
return Format("filename: {} offset: {} compress: {} filesize: {} timestamp: {} unknown: {}", filename, offset, compressed, filesize, timestamp, uk);
else
return std.string.format("filename: %s offset: %d compress: %d filesize: %d timestamp: %d unknown: %d", filename, offset, compressed, filesize, timestamp, uk);
}
}
/// internal structure to hold all needed information for an archive
struct Block
{
BlockHeader head;
uint offset; // for memoryresident files
uint compressed; // size of consecutive data
uint datasize; // decompressed size
DirEntry[] directory;
}
/// class to handle Anno 1404 RDA archives
class RDAHandler
{
MainForm _form;
istring _archivename;
Block[] _archive;
uint _numFiles;
public:
this(istring archivename, MainForm form = null)
{
_archivename = archivename;
_form = form;
processArchive();
}
/// process the archive and add them to the list
void processArchive()
{
Header header;
BlockHeader block;
DirEntry[] directory;
ubyte[] buffer;
bool done = false; // for loop
_numFiles = 0;
_archive = [];
// for memoryresident data
uint offset, compressed, datasize;
version (Tango) {} else
File hIn = new File();
size_t filePos; // current offset in the file
try
{
version (Tango)
{
scope hIn = new MappedFile(_archivename, File.ReadShared);
scope(exit) hIn.close();
ubyte[] archiveData = hIn.map;
}
else
{
hIn.open(_archivename, FileMode.In);
// read header
hIn.readExact(&header, Header.sizeof);
}
version (Tango)
header = *cast(Header *) archiveData.ptr;
if (header.magic[0..18] != "Resource File V2.0"w)
throw new Exception("No valid 1404 RDA file!");
version (Tango)
filePos = header.firstBlock;
else
hIn.seekSet(header.firstBlock);
while(!done)
{
version (Tango)
{
// get block header
block = *cast(BlockHeader *)archiveData[filePos];
// strangely an empty block exists in Data5.rda
if (block.numFiles == 0)
{
if (block.nextBlock >= archiveData.length)
done = true;
else
filePos = block.nextBlock;
continue;
}
// get the directory before it
filePos += - block.dirSize; // - BlockHeader.sizeof;
// if block data is memoryresident, the block is preceded by compressed and uncompressed size
if (block.flags & 4)
filePos -= 8;
version (D_Version2)
buffer = archiveData[filePos .. filePos + block.dirSize].idup;
else
buffer = archiveData[filePos .. filePos + block.dirSize].dup;
filePos += block.dirSize;
if (block.flags & 2) // if it is encrypted
decrypt(buffer);
if (block.flags & 1) // if it is compressed
{
// debug _form.log("Starting decompression...");
buffer = cast(ubyte[]) uncompress(buffer, block.decompressedSize);
}
directory = cast(DirEntry[]) buffer;
// memoryresident data?
if (block.flags & 4)
{
compressed = *cast(uint*) archiveData[filePos];
datasize = *cast(uint*) archiveData[filePos+4];
offset = cast(uint)(filePos - block.dirSize - compressed);
}
// is this the last block?
if (block.nextBlock >= archiveData.length)
done = true;
else
filePos = block.nextBlock;
}
else
{
// get block header
hIn.readExact(&block, BlockHeader.sizeof);
// strangely an empty block exists in Data5.rda
if (block.numFiles == 0)
{
if (block.nextBlock >= hIn.size())
done = true;
else
hIn.seekSet(block.nextBlock);
continue;
}
// get the directory before it
hIn.seekCur(- cast(int)block.dirSize - cast(int)BlockHeader.sizeof);
// if block data is memoryresident, the block is preceded by compressed and uncompressed size
if (block.flags & 4)
hIn.seekCur(-
;
buffer.length = block.dirSize;
hIn.readExact(buffer.ptr, buffer.length);
if (block.flags & 2) // if it is encrypted
decrypt(buffer);
if (block.flags & 1) // if it is compressed
{
// debug _form.log("Starting decompression...");
buffer = cast(ubyte[]) uncompress(buffer, block.decompressedSize);
}
directory = cast(DirEntry[]) buffer;
// memoryresident data?
if (block.flags & 4)
{
hIn.read(compressed);
hIn.read(datasize);
offset = cast(uint)(hIn.position - 8 - block.dirSize - compressed);
}
// is this the last block?
if (block.nextBlock >= hIn.size())
done = true;
else
hIn.seekSet(block.nextBlock);
/* if (block.flags &
// if files are deleted
{
debug listView.addRow([]);
continue;
}
*/
} // else version (Tango)
_archive ~= Block(block, offset, compressed, datasize, directory.dup);
// process this directory
if (_form !is null)
foreach (entry; directory)
{
_numFiles++;
_form.log(_numFiles);
debug
_form.addEntry([toUTF8(entry.filename), any2string(entry.offset), any2string(entry.compressed),
any2string(entry.filesize), any2string(ctime(&entry.timestamp))[0..$-1], any2string(entry.uk),
(block.flags&16?"F":"") ~ (block.flags&8?"D":"") ~ (block.flags&4?"M":"") ~ (block.flags&2?"E":"") ~ (block.flags&1?"C":"")]);
// block.flags&8?"1":"0", block.flags&4?"1":"0", block.flags&2?"1":"0", block.flags&1?"1":"0"]);
else
_form.addEntry([toUTF8(entry.filename), any2string(entry.offset), any2string(entry.compressed),
any2string(entry.filesize), any2string(ctime(&entry.timestamp))[0..$-1], any2string(entry.uk)]);
}
}
if (_form !is null)
_form.log("Read %d files from %s...", _numFiles, _archivename);
}
catch(Exception e)
{
if (_form !is null)
_form.log("Error while reading archive %s: %s", _archivename, e);
else
Stdout("Error while reading archive {}: {}", _archivename, e).newline;
}
/* finally
{
hIn.close();
}
*/ }
/// extract files in the archive
void extract(cstring path, ListView.SelectedIndexCollection selectedIndices = null)
{
File hIn, hOut;
ubyte[] buffer = new ubyte[10_485_760], data;
int counter = -1; // to count the file index, starts with 0
try
{
chdir(path);
if (_form !is null)
_form.progressBarMinMax(0, (selectedIndices !is null) ? selectedIndices.length : _numFiles);
hIn = new File(_archivename);
foreach (block; _archive)
{
if (block.head.flags & 4) // memoryresident
{
hIn.seekSet(block.offset);
if (buffer.length < block.compressed)
buffer.length = block.compressed;
hIn.readExact(buffer.ptr, block.compressed);
data = buffer[0 .. block.compressed];
if (block.head.flags & 2)
decrypt(data);
if (block.head.flags & 1)
data = cast(ubyte[]) uncompress(data, block.datasize);
}
foreach (entry; block.directory)
{
counter++;
// only selected files shall be extracted and this one isn't chosen
if (selectedIndices !is null && !selectedIndices.contains(counter))
continue;
if (_form !is null)
_form.progressBarStep(); // show the progress
// if files are marked as deleted
if (block.head.flags &
continue;
//writefln(getDirName(toUTF8(entry.filename[0 .. wstrlen(entry.filename)])));
//mkdirRecursive(getDirName(toUTF8(entry.filename[0 .. wstrlen(entry.filename)])));
// create the necessary directories
uint pos, lastpos;
auto name = toUTF8(entry.filename);
while (name[pos] != '\0')
{
if (name[pos] == '/')
{
if (!exists(name[lastpos .. pos]))
mkdir(name[lastpos .. pos]);
chdir(name[lastpos .. pos]);
lastpos = pos+1;
}
pos++;
}
// write out the file
try
{
// writefln(name[lastpos .. pos]);
hOut = new File(name[lastpos .. pos], FileMode.Out);
if (entry.compressed == 0 && entry.offset == 0)
continue;
if (block.head.flags & 4) // memoryresident
{
hOut.writeExact(data.ptr + entry.offset, entry.filesize);
}
else
{
if (entry.compressed != 0) // to support empty files
{
hIn.seekSet(entry.offset);
if (buffer.length < entry.compressed)
buffer.length = entry.compressed;
hIn.readExact(buffer.ptr, entry.compressed);
data = buffer[0 .. entry.compressed]; // create a view into the buffer
if (block.head.flags & 2)
decrypt(data);
if (block.head.flags & 1)
data = cast(ubyte[]) uncompress(data, entry.filesize);
// if we have a savegame, also decompress the save.sww
if (name[0 .. 8] == "save.sww")
{
uint size = *cast(uint *)(data.ptr+4);
// data = cast(ubyte[]) uncompress(data[8 .. $], size);
}
hOut.writeExact(data.ptr, data.length);
// free the uncompressed data buffer
if ((block.head.flags & 1)) // || (name[0 .. 8] == "save.sww"))
delete data;
}
}
}
catch(Exception e)
{
if (_form !is null)
_form.log("Error while writing out file %s: %s", name, e.toString());
else
writefln("Error while writing out file %s: %s", name, e.toString());
}
finally
{
hOut.close();
if (entry.timestamp)
setTimes(name[lastpos .. pos], entry.timestamp);
chdir(path); // change back to the base folder
}
} // foreach (entry; block.directory)
// free the memory used by memoryresident data
if ((block.head.flags & 4) && (block.head.flags & 1))
delete data;
} // foreach (block; _archive)
}
catch(Exception e)
{
if (_form !is null)
_form.log("Error while extracting: %s", e);
else
writefln("Error while extracting: %s", e);
}
finally
{
hIn.close();
}
}
void pack(cstring basedir)
{
traverseRecurse();
version(D_Version2)
{
foreach (std.file.DirEntry e; dirEntries(".", SpanMode.depth))
{
}
}
}
private void traverseRecurse()
{
}
/// decrypts a data block
static void decrypt(void[] buffer)
{
ushort* buf = cast(ushort *) buffer.ptr;
int x=0xA2C2A; short y=0;
for(int i=0; i<buffer.length>>1; i++) // halve the size cause we decrypt 2 bytes at once
{
x=x*0x343FD + 0x269EC3;
y=x>>16 & 0x7FFF;
buf
^= y;
}
}
}
void createFile(cstring path)
{
}
void mkdirRecursive(cstring pathname)
{
auto left = getDirName(pathname);
if (left.length)
{
(exists(left) || mkdirRecursive(left));
}
mkdir(pathname);
}
uint wstrlen(wchar[260] string)
{
int len;
while (string[len] != 0)
len++;
return len;
}
import std.c.time;
import std.c.windows.windows;
extern(Windows) BOOL SetFileTime(HANDLE hFile, in FILETIME *lpCreationTime, in FILETIME *lpLastAccessTime, in FILETIME *lpLastWriteTime);
version(Windows) void setTimes(cstring name, time_t ftm)
{
auto time = gmtime(&ftm);
SYSTEMTIME systime = SYSTEMTIME(cast(ushort) (1900 + time.tm_year), cast(ushort) (1 + time.tm_mon), cast(ushort) time.tm_wday,
cast(ushort) time.tm_mday, cast(ushort) time.tm_hour, cast(ushort) time.tm_min, cast(ushort) time.tm_sec, 0);
FILETIME ftime;
if (SystemTimeToFileTime(&systime, &ftime))
{
auto h = CreateFileA(toMBSz(name), GENERIC_WRITE, 0, null, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, HANDLE.init);
if (h != INVALID_HANDLE_VALUE)
{
scope(exit) CloseHandle(h);
SetFileTime(h, &ftime, &ftime, &ftime);
}
else
assert(0, "Couldn't open file for setting timestamp!");
}
else
assert(0, "Timestamp conversion failed! " ~ any2string(GetLastError()));
}