diff --git a/README.md b/README.md index 68934d7..7684376 100644 --- a/README.md +++ b/README.md @@ -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 | diff --git a/patterns/smk.hexpat b/patterns/smk.hexpat new file mode 100644 index 0000000..6a1a295 --- /dev/null +++ b/patterns/smk.hexpat @@ -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 { + 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]];