mirror of
https://github.com/WerWolv/ImHex-Patterns.git
synced 2026-03-27 23:37:04 -05:00
* Add rar file pattern as requested in #258 * Fix rar pattern Removed some experimental (and apperantly broken) code. * Break on EndOfArchive header instead of EOF --------- Co-authored-by: Nik <werwolv98@gmail.com>
This commit is contained in:
@@ -130,6 +130,7 @@ Everything will immediately show up in ImHex's Content Store and gets bundled wi
|
||||
| QBCL | | [`patterns/qbcl.hexpat`](patterns/qbcl.hexpat) | Qubicle voxel scene project file |
|
||||
| QOI | `image/qoi` | [`patterns/qoi.hexpat`](patterns/qoi.hexpat) | QOI image files |
|
||||
| quantized-mesh | | [`patterns/quantized-mesh.hexpat`](patterns/quantized-mesh.hexpat) | Cesium quantized-mesh terrain |
|
||||
| RAR | `application/x-rar` | [`patterns/rar.hexpat`](patterns/rar.hexpat) | RAR archive file format |
|
||||
| RAS | `image/x-sun-raster` | [`patterns/ras.hexpat`](patterns/ras.hexpat) | RAS image files |
|
||||
| ReFS | | [`patterns/refs.hexpat`](patterns/refs.hexpat) | Microsoft Resilient File System |
|
||||
| RGBDS | | [`patterns/rgbds.hexpat`](patterns/rgbds.hexpat) | [RGBDS](https://rgbds.gbdev.io) object file format |
|
||||
|
||||
361
patterns/rar.hexpat
Normal file
361
patterns/rar.hexpat
Normal file
@@ -0,0 +1,361 @@
|
||||
#pragma author targodan
|
||||
#pragma description RAR archive file format
|
||||
#pragma MIME application/x-rar
|
||||
|
||||
import type.magic;
|
||||
import type.byte;
|
||||
import std.mem;
|
||||
import std.math;
|
||||
import std.string;
|
||||
import std.time;
|
||||
|
||||
// Source: https://www.rarlab.com/technote.htm
|
||||
|
||||
#define RARv5_MAGIC "Rar!\x1a\x07\x01\x00"
|
||||
|
||||
using FILETIME = u64 [[format("format_windows_time")]];
|
||||
using time_t = std::time::EpochTime [[format("format_unix_time")]];
|
||||
using nanotime_t = u64 [[format("format_unix_time")]];
|
||||
|
||||
fn format_unix_time(time_t t) {
|
||||
return std::time::format(std::time::to_local(t), "%c");
|
||||
};
|
||||
|
||||
fn format_windows_time(FILETIME t) {
|
||||
return format_unix_time(std::time::filetime_to_unix(t));
|
||||
};
|
||||
|
||||
struct vint {
|
||||
u8 data[while(std::mem::read_unsigned($, 1) & 0x80 != 0)];
|
||||
u8 last;
|
||||
} [[sealed, transform("vint_value"), format("vint_value")]];
|
||||
|
||||
fn vint_value(vint vi) {
|
||||
u64 value = 0;
|
||||
u8 i = 0;
|
||||
for(i = 0, i < sizeof(vi.data), i = i + 1) {
|
||||
value |= (vi.data[i] & 0x7F) << (i * 7);
|
||||
}
|
||||
value |= vi.last << (i * 7);
|
||||
return value;
|
||||
};
|
||||
|
||||
bitfield LocatorRecordFlags {
|
||||
bool quickOpenRecordOffsetIsPresent : 1;
|
||||
bool recoveryRecordOffsetIsPresent : 1;
|
||||
};
|
||||
|
||||
struct LocatorRecord {
|
||||
LocatorRecordFlags flags; // should be vint but not compatible with bitfield
|
||||
if (flags.quickOpenRecordOffsetIsPresent) {
|
||||
vint quickOpenRecordOffset;
|
||||
}
|
||||
if (flags.recoveryRecordOffsetIsPresent) {
|
||||
vint recoveryRecordOffset;
|
||||
}
|
||||
};
|
||||
|
||||
bitfield MetadataRecordFlags {
|
||||
bool archiveNameIsPresent : 1;
|
||||
bool archiveOriginalCreationTimeIsPresent : 1;
|
||||
bool timeIsUnixTime : 1;
|
||||
bool unixTimeIsNanosecondPrecision : 1;
|
||||
};
|
||||
|
||||
struct MetadataRecord {
|
||||
MetadataRecordFlags flags; // should be vint but not compatible with bitfield
|
||||
if (flags.archiveNameIsPresent) {
|
||||
vint nameLength;
|
||||
char name[nameLength];
|
||||
}
|
||||
if (flags.archiveOriginalCreationTimeIsPresent) {
|
||||
match (flags.timeIsUnixTime, flags.unixTimeIsNanosecondPrecision) {
|
||||
(true, true): nanotime_t originalCreationTime;
|
||||
(true, false): time_t originalCreationTime;
|
||||
(false, _): FILETIME originalCreationTime;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
enum MainHeaderExtraRecordType : vint {
|
||||
Locator = 1,
|
||||
Metadata = 2
|
||||
};
|
||||
|
||||
struct MainHeaderExtraRecord {
|
||||
vint size;
|
||||
MainHeaderExtraRecordType type;
|
||||
match(type) {
|
||||
(MainHeaderExtraRecordType::Locator): LocatorRecord record [[inline]];
|
||||
(MainHeaderExtraRecordType::Metadata): MetadataRecord record [[inline]];
|
||||
}
|
||||
};
|
||||
|
||||
bitfield HeaderFlags {
|
||||
bool extraAreaIsPresent : 1;
|
||||
bool dataAreaIsPresent : 1;
|
||||
};
|
||||
|
||||
bitfield ArchiveFlags {
|
||||
bool archiveIsPartOfMultiVolumeSet : 1;
|
||||
bool volumeNumberFieldIsPresent : 1;
|
||||
bool isSolidArchive : 1;
|
||||
bool recoveryRecordIsPresent : 1;
|
||||
bool isLockedArchive : 1;
|
||||
};
|
||||
|
||||
enum HeaderType : vint {
|
||||
MainHeader = 1,
|
||||
FileHeader,
|
||||
ServiceHeader,
|
||||
EncryptionHeader,
|
||||
EndOfArchive
|
||||
};
|
||||
|
||||
struct MainArchiveHeader {
|
||||
HeaderFlags flags; // should be vint but not compatible with bitfield
|
||||
if (flags.extraAreaIsPresent) {
|
||||
vint extraAreaSize;
|
||||
}
|
||||
ArchiveFlags archiveFlags; // should be vint but not compatible with bitfield
|
||||
if (archiveFlags.volumeNumberFieldIsPresent) {
|
||||
vint volumeNumber;
|
||||
}
|
||||
if (flags.extraAreaIsPresent) {
|
||||
u64 extraEnd = $ + extraAreaSize;
|
||||
MainHeaderExtraRecord extraArea[while($ < extraEnd)];
|
||||
}
|
||||
};
|
||||
|
||||
bitfield EncryptionHeaderFlags {
|
||||
bool passwordCheckDataIsPresent : 1;
|
||||
};
|
||||
|
||||
struct EncryptionHeader {
|
||||
HeaderFlags flags; // should be vint but not compatible with bitfield
|
||||
vint encryptionVersion; // only 0 (AES-256) is supported
|
||||
EncryptionHeaderFlags encryptionFlags;
|
||||
u8 kdfCount;
|
||||
u8 salt[16];
|
||||
if (encryptionFlags.passwordCheckDataIsPresent) {
|
||||
u8 checkValue[12];
|
||||
}
|
||||
|
||||
u8 iv[16];
|
||||
// if this header is present, all following headers are encrypted => gobble up all data
|
||||
u8 encryptedHeaders[while(!std::mem::eof())];
|
||||
// With a bit of guess work, I figured out the exact crypto:
|
||||
// key = PBKDF2(password, salt, keysize=256, iterations=2^kdfCount, hash=sha256)
|
||||
// plainHeaders = AES256Decrypt(key, iv, mode=CBC)
|
||||
};
|
||||
|
||||
bitfield FileAndServiceHeaderFlags {
|
||||
bool isDirectory : 1;
|
||||
bool timeFieldIsPresent : 1;
|
||||
bool crc32FieldIsPresent : 1;
|
||||
bool unpackedSizeIsUnknown : 1;
|
||||
};
|
||||
|
||||
enum HostOS : vint {
|
||||
Windows = 0,
|
||||
Unix = 1
|
||||
};
|
||||
|
||||
bitfield FileEncryptionFlags {
|
||||
bool passwordCheckDataIsPresent : 1;
|
||||
bool useTweakedChecksums : 1;
|
||||
};
|
||||
|
||||
struct FileEncryptionRecord {
|
||||
vint encryptionVersion; // only 0 (AES-256) is supported
|
||||
FileEncryptionFlags flags; // should be vint but not compatible with bitfield
|
||||
u8 kdfCount;
|
||||
u8 salt[16];
|
||||
u8 iv[16];
|
||||
u8 checkValue[12];
|
||||
};
|
||||
|
||||
struct FileHashRecord {
|
||||
vint hashType; // only 0 (BLAKE2sp) hash is documented
|
||||
u8 hashData[32]; // depends on hashType, but only one type is supported
|
||||
};
|
||||
|
||||
bitfield FileTimeFlags {
|
||||
bool timeIsStoredInUnixTime : 1;
|
||||
bool mtimeIsPresent : 1;
|
||||
bool ctimeIsPresent : 1;
|
||||
bool atimeIsPresent : 1;
|
||||
bool unixTimeIsNanosecondPrecision : 1;
|
||||
};
|
||||
|
||||
struct FileTimeRecord {
|
||||
FileTimeFlags flags; // should be vint but not compatible with bitfield
|
||||
if (flags.mtimeIsPresent) {
|
||||
match (flags.timeIsStoredInUnixTime, flags.unixTimeIsNanosecondPrecision) {
|
||||
(true, true): nanotime_t mtime;
|
||||
(true, false): time_t mtime;
|
||||
(false, _): FILETIME mtime;
|
||||
}
|
||||
}
|
||||
if (flags.ctimeIsPresent) {
|
||||
match (flags.timeIsStoredInUnixTime, flags.unixTimeIsNanosecondPrecision) {
|
||||
(true, true): nanotime_t ctime;
|
||||
(true, false): time_t ctime;
|
||||
(false, _): FILETIME ctime;
|
||||
}
|
||||
}
|
||||
if (flags.atimeIsPresent) {
|
||||
match (flags.timeIsStoredInUnixTime, flags.unixTimeIsNanosecondPrecision) {
|
||||
(true, true): nanotime_t atime;
|
||||
(true, false): time_t atime;
|
||||
(false, _): FILETIME atime;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct FileVersionRecord {
|
||||
vint flags;
|
||||
vint versionNumber;
|
||||
};
|
||||
|
||||
enum RedirectionType : vint {
|
||||
unixSymlink = 1,
|
||||
windowsSymlink,
|
||||
windowsJunktion,
|
||||
hardLink,
|
||||
fileCopy
|
||||
};
|
||||
|
||||
struct RedirectionRecord {
|
||||
RedirectionType type;
|
||||
vint flags;
|
||||
vint targetLength;
|
||||
char target[targetLength];
|
||||
};
|
||||
|
||||
bitfield UnixOwnerFlags {
|
||||
bool userNameStringIsPresent : 1;
|
||||
bool groupNameStringIsPresent : 1;
|
||||
bool numericUserIDIsPresent : 1;
|
||||
bool numericGroupIDIsPresent : 1;
|
||||
};
|
||||
|
||||
struct UnixOwnerRecord {
|
||||
UnixOwnerFlags flags; // should be vint but not compatible with bitfield
|
||||
if (flags.userNameStringIsPresent) {
|
||||
vint userNameLength;
|
||||
char userName[usernameLength];
|
||||
}
|
||||
if (flags.groupNameStringIsPresent) {
|
||||
vint groupNameLength;
|
||||
char groupName[groupNameLength];
|
||||
}
|
||||
if (flags.numericUserIDIsPresent) {
|
||||
vint uid;
|
||||
}
|
||||
if (flags.numericGroupIDIsPresent) {
|
||||
vint gid;
|
||||
}
|
||||
};
|
||||
|
||||
enum FSHeaderExtraRecordType : vint {
|
||||
FileEncryption = 1,
|
||||
FileHash,
|
||||
FileTime,
|
||||
FileVersion,
|
||||
Redirection,
|
||||
UnixOwner,
|
||||
ServiceData
|
||||
};
|
||||
|
||||
struct FSHeaderExtraRecord {
|
||||
vint size;
|
||||
FSHeaderExtraRecordType type;
|
||||
match(type) {
|
||||
(FSHeaderExtraRecordType::FileEncryption): FileEncryptionRecord record [[inline]];
|
||||
(FSHeaderExtraRecordType::FileHash): FileHashRecord record [[inline]];
|
||||
(FSHeaderExtraRecordType::FileTime): FileTimeRecord record [[inline]];
|
||||
(FSHeaderExtraRecordType::FileVersion): FileVersionRecord record [[inline]];
|
||||
(FSHeaderExtraRecordType::Redirection): RedirectionRecord record [[inline]];
|
||||
(FSHeaderExtraRecordType::UnixOwner): UnixOwnerRecord record [[inline]];
|
||||
(FSHeaderExtraRecordType::ServiceData): u8 data[size - sizeof(type)]; // couldn't find any info on these records
|
||||
}
|
||||
};
|
||||
|
||||
bitfield _CompressionInformation {
|
||||
version : 6;
|
||||
bool isSolid : 1;
|
||||
method : 3;
|
||||
minDictionarySizeExponent : 5; // min dict size is `128 KB * 2^minDictionarySizeExponent`
|
||||
additionalDictionarySize : 5;
|
||||
bool compressionAlgorithmIsZeroButSizeIsV1 : 1;
|
||||
};
|
||||
|
||||
struct CompressionInformation {
|
||||
vint value; // TODO: I'd like to somehow cast the resulting value to _CompressionInformation
|
||||
};
|
||||
|
||||
struct FileOrServiceHeader {
|
||||
HeaderFlags flags; // should be vint but not compatible with bitfield
|
||||
if (flags.extraAreaIsPresent) {
|
||||
vint extraAreaSize;
|
||||
}
|
||||
if (flags.dataAreaIsPresent) {
|
||||
vint dataAreaSize;
|
||||
}
|
||||
FileAndServiceHeaderFlags fileOrServiceFlags; // should be vint but not compatible with bitfield
|
||||
vint unpackedSize;
|
||||
vint fileAttributes;
|
||||
if (fileOrServiceFlags.timeFieldIsPresent) {
|
||||
u32 fileModificationUnixTime;
|
||||
}
|
||||
if (fileOrServiceFlags.crc32FieldIsPresent) {
|
||||
u32 unpackedCrc32;
|
||||
}
|
||||
CompressionInformation compressionInformation;
|
||||
try {
|
||||
HostOS hostOS;
|
||||
vint nameLength;
|
||||
char name[nameLength];
|
||||
if (flags.extraAreaIsPresent) {
|
||||
u64 extraEnd = $ + extraAreaSize;
|
||||
FSHeaderExtraRecord extraArea[while($ < extraEnd)];
|
||||
}
|
||||
if (flags.dataAreaIsPresent) {
|
||||
u8 data[dataAreaSize] [[sealed]];
|
||||
}
|
||||
} catch {}
|
||||
};
|
||||
|
||||
bitfield EOAHeaderFlags {
|
||||
bool archiveIsVolumeAndNotLastInVolumeSet : 1;
|
||||
};
|
||||
|
||||
struct EndOfArchiveHeader {
|
||||
HeaderFlags flags;
|
||||
EOAHeaderFlags eoaFlags;
|
||||
};
|
||||
|
||||
struct Header {
|
||||
u32 crc32;
|
||||
vint headerSize;
|
||||
HeaderType type;
|
||||
match (type) {
|
||||
(HeaderType::EncryptionHeader): EncryptionHeader [[inline]];
|
||||
(HeaderType::MainHeader): MainArchiveHeader [[inline]];
|
||||
(HeaderType::FileHeader): FileOrServiceHeader [[inline]];
|
||||
(HeaderType::ServiceHeader): FileOrServiceHeader [[inline]];
|
||||
(HeaderType::EndOfArchive): EndOfArchiveHeader [[inline]];
|
||||
}
|
||||
if (type == HeaderType::EndOfArchive) {
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
struct RARv5 {
|
||||
type::Magic<RARv5_MAGIC> magic;
|
||||
Header headers[while(!std::mem::eof())]; // TODO: would be better to read until EOAHeader
|
||||
};
|
||||
|
||||
RARv5 archive @ std::mem::find_string(0, RARv5_MAGIC);
|
||||
|
||||
BIN
tests/patterns/test_data/rar.hexpat.rar
Normal file
BIN
tests/patterns/test_data/rar.hexpat.rar
Normal file
Binary file not shown.
Reference in New Issue
Block a user