patterns: Add support for smk (#399)

* patterns: Add support for smk

* patterns: Use builtin function and separate SMK struct

---------

Co-authored-by: paxcut <53811119+paxcut@users.noreply.github.com>
This commit is contained in:
Fabian Neundorf
2025-06-17 23:57:36 +02:00
committed by GitHub
parent ad1e300674
commit 7716b9d6e7
2 changed files with 219 additions and 0 deletions

View File

@@ -156,6 +156,7 @@ Everything will immediately show up in ImHex's Content Store and gets bundled wi
| Shell Link | `application/x-ms-shortcut` | [`patterns/lnk.hexpat`](patterns/lnk.hexpat) | Windows Shell Link file format |
| shp | | [`patterns/shp.hexpat`](patterns/shp.hexpat) | ESRI shape file |
| shx | | [`patterns/shx.hexpat`](patterns/shx.hexpat) | ESRI index file |
| smk | | [`patterns/smk.hexpat`](patterns/smk.hexpat) | Smacker video file |
| sup | | [`patterns/sup.hexpat`](patterns/sup.hexpat) | PGS Subtitle |
| SPIRV | | [`patterns/spirv.hexpat`](patterns/spirv.hexpat) | SPIR-V header and instructions |
| STDF | | [`patterns/stdfv4.hexpat`](patterns/stdfv4.hexpat) | Standard test data format for IC testers |

218
patterns/smk.hexpat Normal file
View File

@@ -0,0 +1,218 @@
#pragma author xZise
#pragma description Smacker video file
#pragma endian little
#pragma magic [53 4D 4B 32] @ 0x0
import std.core;
import std.io;
import std.mem;
bitfield HeaderFlags {
ContainsRingFrame : 1;
YInterlaced : 1;
YDoubled : 1;
padding : 29;
};
bitfield HeaderAudioFlags {
isCompressed : 1;
hasAudio : 1;
is16BitAudio : 1;
isStereo : 1;
soundDecompression : 2;
padding : 2;
audioSampleRate : 24;
};
fn format_frame_rate(auto frame_rate) {
float fps;
if (frame_rate > 0) {
fps = 1000.0 / frame_rate;
} else if (frame_rate < 0) {
fps = 100000.0 / (-frame_rate);
} else {
fps = 10;
}
return std::format("{} fps", fps);
};
struct Header {
char Signature[4];
u32 Width;
u32 Height;
u32 FrameCount;
s32 FrameRate [[format("format_frame_rate")]];
HeaderFlags Flags;
u32 AudioSize[7];
u32 TreesSize;
u32 MMap_Size;
u32 MClr_Size;
u32 Full_Size;
u32 Type_Size;
HeaderAudioFlags AudioRate[7];
padding[4];
};
fn format_size(auto size) {
if (size.keyframe != 0) {
return std::format("[K] {} B", size.size);
}
return std::format("[ ] {} B", size.size);
};
bitfield Size {
keyframe : 1;
padding : 1;
dwordCount : 30;
u32 size = dwordCount * 4;
} [[format("format_size")]];
bitfield FrameType {
ContainsPaletteRecord : 1;
ContainsAudioTrack0 : 1;
ContainsAudioTrack1 : 1;
ContainsAudioTrack2 : 1;
ContainsAudioTrack3 : 1;
ContainsAudioTrack4 : 1;
ContainsAudioTrack5 : 1;
ContainsAudioTrack6 : 1;
};
fn format_palette_copy_only(auto copy_only) {
return std::format("copy: {0}", copy_only.copy);
};
bitfield PaletteCopyOnly {
copy: 7;
mode: 1;
} [[format("format_palette_copy_only")]];
fn format_palette_copy_skip(auto copy_skip) {
return std::format("copy: {0}, skip: {1}", copy_skip.copy, copy_skip.skip);
};
bitfield PaletteCopySkip {
copy: 6;
mode: 2;
skip: 8;
} [[format("format_palette_copy_skip")]];
bitfield PaletteColor {
blue: 6 [[color("0000FF")]];
mode: 2;
green: 6 [[color("00FF00")]];
padding: 2;
red: 6 [[color("FF0000")]];
padding: 2;
u8 r8 = red << 2 | red >> 4;
u8 g8 = green << 2 | green >> 4;
u8 b8 = blue << 2 | blue >> 4;
} [[hex::inline_visualize("color", r8, g8, b8, 0xff)]];
fn format_palette_chunk_block(auto entry) {
u8 first = entry.type;
if (first & 0x80 == 0x80) {
return format_palette_copy_only(entry.copy);
} else if (first & 0x40 == 0x40) {
return format_palette_copy_skip(entry.copy_skip);
} else {
return std::format("color: 0x{0:02x}{1:02x}{2:02x}", entry.color.r8, entry.color.g8, entry.color.b8);
}
};
enum PaletteChunkBlockType: u8 {
Color = 0x00 ... 0x3F,
CopySkip = 0x40 ... 0x7F,
CopyOnly = 0x80 ... 0xFF
};
// Unfortunately the match expression does not support ranges, so this is a helper for the following code:
// PaletteChunkBlockType type = std::mem::read_unsigned($, 1)
fn get_palette_chunk_block_type() {
u8 type = std::mem::read_unsigned($, 1);
if (type & 0x80 == 0x80) {
return PaletteChunkBlockType::CopyOnly;
} else if (type & 0x40 == 0x40) {
return PaletteChunkBlockType::CopySkip;
} else {
return PaletteChunkBlockType::Color;
}
};
fn get_last_position() {
PaletteChunkBlockType type = get_palette_chunk_block_type();
match (type) {
(PaletteChunkBlockType::CopyOnly): return $;
(PaletteChunkBlockType::CopySkip): return $ + 1;
(PaletteChunkBlockType::Color): return $ + 2;
}
};
struct PaletteChunkBlock {
PaletteChunkBlockType type = get_palette_chunk_block_type() [[export]];
match (type) {
(PaletteChunkBlockType::CopyOnly): PaletteCopyOnly copy;
(PaletteChunkBlockType::CopySkip): PaletteCopySkip copy_skip;
(PaletteChunkBlockType::Color): PaletteColor color;
}
} [[format("format_palette_chunk_block")]];
struct PaletteChunk {
u8 length;
u128 end = $ + length * 4 - 1;
PaletteChunkBlock blocks[while(get_last_position() < end)];
if ($ < end) {
padding[end - $];
}
};
struct AudioTrack<auto trackIndex> {
u32 length;
if (parent.parent.header.AudioRate[trackIndex].isCompressed) {
u32 decompressedSize;
}
u8 trackData[length - ($ - addressof(this))];
};
struct FramesData {
u32 frame_index = std::core::array_index();
u32 size = parent.sizes[frame_index].size;
if (parent.frameTypes[frame_index].ContainsPaletteRecord) {
PaletteChunk palette;
}
if (parent.frameTypes[frame_index].ContainsAudioTrack0) {
AudioTrack<0> audioTrack0;
}
if (parent.frameTypes[frame_index].ContainsAudioTrack1) {
AudioTrack<1> audioTrack1;
}
if (parent.frameTypes[frame_index].ContainsAudioTrack2) {
AudioTrack<2> audioTrack2;
}
if (parent.frameTypes[frame_index].ContainsAudioTrack3) {
AudioTrack<3> audioTrack3;
}
if (parent.frameTypes[frame_index].ContainsAudioTrack4) {
AudioTrack<4> audioTrack4;
}
if (parent.frameTypes[frame_index].ContainsAudioTrack5) {
AudioTrack<5> audioTrack5;
}
if (parent.frameTypes[frame_index].ContainsAudioTrack6) {
AudioTrack<6> audioTrack6;
}
u8 video[size - ($ - addressof(this))];
};
struct SMK {
Header header;
Size sizes[header.FrameCount];
FrameType frameTypes[header.FrameCount] ;
u8 trees[header.TreesSize];
FramesData frames[header.FrameCount];
};
SMK smk @ 0x00 [[inline]];