Skip to content

Commit

Permalink
Add support for versions 0x200 and 0x300 of the RFF format (Blood) (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
cher-nov authored Oct 23, 2024
1 parent 69805c1 commit b7a6a03
Showing 1 changed file with 128 additions and 85 deletions.
213 changes: 128 additions & 85 deletions src/Archive/Formats/RffArchive.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -100,18 +100,17 @@ struct RFFLump
uint8_t Flags;
char Extension[3];
char Name[8];
uint32_t IndexNum; // Used by .sfx, possibly others
uint32_t IndexNum; // Used by .sfx, possibly others
};

// -----------------------------------------------------------------------------
// From ZDoom: decrypts RFF data
// -----------------------------------------------------------------------------
void bloodCrypt(void* data, int key, int len)
void bloodCrypt(uint32_t key, void* buffer, size_t len)
{
int p = (uint8_t)key, i;

for (i = 0; i < len; ++i)
((uint8_t*)data)[i] ^= (unsigned char)(p + (i >> 1));
uint8_t* data = (uint8_t*)buffer;
while (len > 0)
{
*data++ ^= key++ >> 1;
--len;
}
}
} // namespace

Expand Down Expand Up @@ -148,7 +147,7 @@ void RffArchive::setEntryOffset(ArchiveEntry* entry, uint32_t offset)
}

// -----------------------------------------------------------------------------
// Reads grp format data from a MemChunk
// Reads rff format data from a MemChunk
// Returns true if successful, false otherwise
// -----------------------------------------------------------------------------
bool RffArchive::open(MemChunk& mc)
Expand All @@ -157,29 +156,44 @@ bool RffArchive::open(MemChunk& mc)
if (!mc.hasData())
return false;

// Read grp header
uint8_t magic[4];
uint32_t version, dir_offset, num_lumps;
// Read rff header
uint32_t magic, dir_offset, num_lumps;
uint16_t version;

mc.seek(0, SEEK_SET);
mc.read(magic, 4); // Should be "RFF\x18"
mc.read(&version, 4); // 0x01 0x03 \x00 \x00
mc.read(&dir_offset, 4); // Offset to directory
mc.read(&num_lumps, 4); // No. of lumps in rff

// Byteswap values for big endian if needed
dir_offset = wxINT32_SWAP_ON_BE(dir_offset);
num_lumps = wxINT32_SWAP_ON_BE(num_lumps);
version = wxINT32_SWAP_ON_BE(version);
mc.read(&magic, 4);
mc.read(&version, 2);
mc.seek(2, SEEK_CUR);
mc.read(&dir_offset, 4); // Absolute offset to directory
mc.read(&num_lumps, 4); // Number of lumps in rff

// Check the header
if (magic[0] != 'R' || magic[1] != 'F' || magic[2] != 'F' || magic[3] != 0x1A || version != 0x301)
if (magic != wxUINT32_SWAP_ON_LE(0x5246461A)) // 'RFF\x1A'
{
log::error("RffArchive::openFile: File {} has invalid header", filename_);
log::error("RffArchive::open: File {} has invalid header", filename_);
global::error = "Invalid rff header";
return false;
}

// Byteswap values for big endian if needed
dir_offset = wxUINT32_SWAP_ON_BE(dir_offset);
num_lumps = wxUINT32_SWAP_ON_BE(num_lumps);
version = wxUINT16_SWAP_ON_BE(version);

// Check the version and select the key if needed
uint32_t key;
switch (version)
{
case 0x200: break;
case 0x300: key = dir_offset; break;
case 0x301: key = dir_offset << 1; break;

default:
log::error("RffArchive::open: File {} has unknown version {}", filename_, version);
global::error = "Unknown rff version";
return false;
};

// Stop announcements (don't want to be announcing modification due to entries being added etc)
ArchiveModSignalBlocker sig_blocker{ *this };

Expand All @@ -188,16 +202,29 @@ bool RffArchive::open(MemChunk& mc)
mc.seek(dir_offset, SEEK_SET);
ui::setSplashProgressMessage("Reading rff archive data");
mc.read(lumps, num_lumps * sizeof(RFFLump));
bloodCrypt(lumps, dir_offset, num_lumps * sizeof(RFFLump));
for (uint32_t d = 0; d < num_lumps; d++)

// Decrypt if there is an encryption
if (version >= 0x300)
bloodCrypt(key, lumps, num_lumps * sizeof(RFFLump));

for (uint32_t d = 0; d < num_lumps; ++d)
{
// Update splash window progress
ui::setSplashProgress(((float)d / (float)num_lumps));

// Read lump info
char name[13] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
uint32_t offset = wxINT32_SWAP_ON_BE(lumps[d].FilePos);
uint32_t size = wxINT32_SWAP_ON_BE(lumps[d].Size);
char name[13] = {};
uint32_t offset = wxUINT32_SWAP_ON_BE(lumps[d].FilePos);
uint32_t size = wxUINT32_SWAP_ON_BE(lumps[d].Size);

// If the lump data goes past the end of the file,
// the rff file is invalid
if (offset + size >= mc.size())
{
log::error("RffArchive::open: rff archive is invalid or corrupt");
global::error = "Archive is invalid and/or corrupt";
return false;
}

// Reconstruct name
int i, j = 0;
Expand All @@ -210,15 +237,6 @@ bool RffArchive::open(MemChunk& mc)
for (name[i++] = '.'; j < 3; ++j)
name[i + j] = lumps[d].Extension[j];

// If the lump data goes past the end of the file,
// the rfffile is invalid
if (offset + size > mc.size())
{
log::error("RffArchive::open: rff archive is invalid or corrupt");
global::error = "Archive is invalid and/or corrupt";
return false;
}

// Create & setup lump
auto nlump = std::make_shared<ArchiveEntry>(name, size);
nlump->setLoaded(false);
Expand All @@ -237,7 +255,7 @@ bool RffArchive::open(MemChunk& mc)
// Detect all entry types
MemChunk edata;
ui::setSplashProgressMessage("Detecting entry types");
for (size_t a = 0; a < numEntries(); a++)
for (size_t a = 0; a < numEntries(); ++a)
{
// Update splash window progress
ui::setSplashProgress((((float)a / (float)num_lumps)));
Expand All @@ -257,7 +275,7 @@ bool RffArchive::open(MemChunk& mc)
uint8_t* cdata = new uint8_t[entry->size()];
memcpy(cdata, edata.data(), entry->size());
int cryptlen = entry->size() < 256 ? entry->size() : 256;
bloodCrypt(cdata, 0, cryptlen);
bloodCrypt(0, cdata, cryptlen);
edata.importMem(cdata, entry->size());
delete[] cdata;
}
Expand Down Expand Up @@ -297,7 +315,7 @@ bool RffArchive::write(MemChunk& mc, bool update)
}

// -----------------------------------------------------------------------------
// Loads an entry's data from the grpfile
// Loads an entry's data from the rff file
// Returns true if successful, false otherwise
// -----------------------------------------------------------------------------
bool RffArchive::loadEntryData(ArchiveEntry* entry)
Expand All @@ -314,7 +332,7 @@ bool RffArchive::loadEntryData(ArchiveEntry* entry)
return true;
}

// Open rfffile
// Open rff file
wxFile file(filename_);

// Check if opening the file failed
Expand All @@ -335,57 +353,68 @@ bool RffArchive::loadEntryData(ArchiveEntry* entry)
}

// -----------------------------------------------------------------------------
// Checks if the given data is a valid Duke Nukem 3D grp archive
// Checks if the given data is a valid Blood rff archive
// -----------------------------------------------------------------------------
bool RffArchive::isRffArchive(MemChunk& mc)
{
// Check size
if (mc.size() < 12)
if (mc.size() < 16)
return false;

// Read grp header
uint8_t magic[4];
uint32_t version, dir_offset, num_lumps;
// Read rff header
uint32_t magic, dir_offset, num_lumps;
uint16_t version;

mc.seek(0, SEEK_SET);
mc.read(magic, 4); // Should be "RFF\x18"
mc.read(&version, 4); // 0x01 0x03 \x00 \x00
mc.read(&dir_offset, 4); // Offset to directory
mc.read(&num_lumps, 4); // No. of lumps in rff

// Byteswap values for big endian if needed
dir_offset = wxINT32_SWAP_ON_BE(dir_offset);
num_lumps = wxINT32_SWAP_ON_BE(num_lumps);
version = wxINT32_SWAP_ON_BE(version);
mc.read(&magic, 4);
mc.read(&version, 2);
mc.seek(2, SEEK_CUR);
mc.read(&dir_offset, 4); // Absolute offset to directory
mc.read(&num_lumps, 4); // Number of lumps in rff

// Check the header
if (magic[0] != 'R' || magic[1] != 'F' || magic[2] != 'F' || magic[3] != 0x1A || version != 0x301)
if (magic != wxUINT32_SWAP_ON_LE(0x5246461A)) // 'RFF\x1A'
return false;

// Byteswap values for big endian if needed
dir_offset = wxUINT32_SWAP_ON_BE(dir_offset);
num_lumps = wxUINT32_SWAP_ON_BE(num_lumps);
version = wxUINT16_SWAP_ON_BE(version);

// Check the version and select the key if needed
uint32_t key;
switch (version)
{
case 0x200: break;
case 0x300: key = dir_offset; break;
case 0x301: key = dir_offset << 1; break;
default: return false;
};

// Compute total size
auto lumps = new RFFLump[num_lumps];
mc.seek(dir_offset, SEEK_SET);
ui::setSplashProgressMessage("Reading rff archive data");
mc.read(lumps, num_lumps * sizeof(RFFLump));
bloodCrypt(lumps, dir_offset, num_lumps * sizeof(RFFLump));
uint32_t totalsize = 12 + num_lumps * sizeof(RFFLump);
uint32_t size = 0;
for (uint32_t a = 0; a < num_lumps; ++a)
{
totalsize += lumps[a].Size;
}

// Decrypt if there is an encryption
if (version >= 0x300)
bloodCrypt(key, lumps, num_lumps * sizeof(RFFLump));

uint32_t totalsize = 16 + num_lumps * sizeof(RFFLump);
for (uint32_t d = 0; d < num_lumps; ++d)
totalsize += wxUINT32_SWAP_ON_BE(lumps[d].Size);

// Check if total size is correct
if (totalsize > mc.size())
return false;

// If it's passed to here it's probably a grp file
// If it's passed to here it's probably a rff file
return true;
}

// -----------------------------------------------------------------------------
// Checks if the file at [filename] is a valid DN3D grp archive
// Checks if the file at [filename] is a valid BLOOD rff archive
// -----------------------------------------------------------------------------
bool RffArchive::isRffArchive(const string& filename)
{
Expand All @@ -397,43 +426,57 @@ bool RffArchive::isRffArchive(const string& filename)
return false;

// Check size
if (file.Length() < 12)
if (file.Length() < 16)
return false;

// Read grp header
uint8_t magic[4];
uint32_t version, dir_offset, num_lumps;
// Read rff header
uint32_t magic, dir_offset, num_lumps;
uint16_t version;

file.Seek(0, wxFromStart);
file.Read(magic, 4); // Should be "RFF\x18"
file.Read(&version, 4); // 0x01 0x03 \x00 \x00
file.Read(&dir_offset, 4); // Offset to directory
file.Read(&num_lumps, 4); // No. of lumps in rff

// Byteswap values for big endian if needed
dir_offset = wxINT32_SWAP_ON_BE(dir_offset);
num_lumps = wxINT32_SWAP_ON_BE(num_lumps);
version = wxINT32_SWAP_ON_BE(version);
file.Read(&magic, 4);
file.Read(&version, 2);
file.Seek(2, wxFromCurrent);
file.Read(&dir_offset, 4); // Absolute offset to directory
file.Read(&num_lumps, 4); // Number of lumps in rff

// Check the header
if (magic[0] != 'R' || magic[1] != 'F' || magic[2] != 'F' || magic[3] != 0x1A || version != 0x301)
if (magic != wxUINT32_SWAP_ON_LE(0x5246461A)) // 'RFF\x1A'
return false;

// Byteswap values for big endian if needed
dir_offset = wxUINT32_SWAP_ON_BE(dir_offset);
num_lumps = wxUINT32_SWAP_ON_BE(num_lumps);
version = wxUINT16_SWAP_ON_BE(version);

// Check the version and select the key if needed
uint32_t key;
switch (version)
{
case 0x200: break;
case 0x300: key = dir_offset; break;
case 0x301: key = dir_offset << 1; break;
default: return false;
};

// Compute total size
auto lumps = new RFFLump[num_lumps];
file.Seek(dir_offset, wxFromStart);
ui::setSplashProgressMessage("Reading rff archive data");
file.Read(lumps, num_lumps * sizeof(RFFLump));
bloodCrypt(lumps, dir_offset, num_lumps * sizeof(RFFLump));
uint32_t totalsize = 12 + num_lumps * sizeof(RFFLump);
for (uint32_t a = 0; a < num_lumps; ++a)
totalsize += lumps[a].Size;

// Decrypt if there is an encryption
if (version >= 0x300)
bloodCrypt(key, lumps, num_lumps * sizeof(RFFLump));

uint32_t totalsize = 16 + num_lumps * sizeof(RFFLump);
for (uint32_t d = 0; d < num_lumps; ++d)
totalsize += wxUINT32_SWAP_ON_BE(lumps[d].Size);

// Check if total size is correct
if (totalsize > file.Length())
return false;

// If it's passed to here it's probably a grp file
// If it's passed to here it's probably a rff file
return true;
}

0 comments on commit b7a6a03

Please sign in to comment.