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 |
|
| QBCL | | [`patterns/qbcl.hexpat`](patterns/qbcl.hexpat) | Qubicle voxel scene project file |
|
||||||
| QOI | `image/qoi` | [`patterns/qoi.hexpat`](patterns/qoi.hexpat) | QOI image files |
|
| 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 |
|
| 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 |
|
| 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 |
|
| 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 |
|
| 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