Skip to content

Commit 74e0862

Browse files
xZiseWerWolv
andauthored
patterns: Add support for flc/flic files (#396)
* patterns: Add support for flc/flic files * patterns: Add #pragma description for flc --------- Co-authored-by: Nik <werwolv98@gmail.com>
1 parent a860c39 commit 74e0862

File tree

3 files changed

+269
-0
lines changed

3 files changed

+269
-0
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ Everything will immediately show up in ImHex's Content Store and gets bundled wi
7474
| FFX | | [`patterns/ffx/*`](https://gitlab.com/EvelynTSMG/imhex-ffx-pats) | Various Final Fantasy X files |
7575
| File System | `application/x-ima` | [`patterns/fs.hexpat`](patterns/fs.hexpat) | Drive File System |
7676
| FLAC | `audio/flac` | [`patterns/flac.hexpat`](patterns/flac.hexpat) | Free Lossless Audio Codec, FLAC Audio Format |
77+
| FLC/FLIC | | [`patterns/flc.hexpat`](patterns/flc.hexpat) | FLC/FLIC animation file |
7778
| Flipper Zero Settings | | [`patterns/flipper_settings.hexpat`](patterns/flipper_settings.hexpat) | Flipper Zero Settings Files |
7879
| GB | `application/x-gameboy-rom` | [`patterns/gb.hexpat`](patterns/gb.hexpat) | Game Boy ROM |
7980
| GBA | `application/x-gameboy-advance-rom` | [`patterns/gba.hexpat`](patterns/gba.hexpat) | Game Boy Advance ROM header |

patterns/flc.hexpat

Lines changed: 268 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,268 @@
1+
#pragma description FLC/FLIC animation file
2+
#pragma endian little
3+
#pragma magic [12 AF] @ 0x4
4+
5+
// A flic file could contain many DELTA_FLC chunks, which contain many RLE packets
6+
//#define DECODE_DELTA_FLC
7+
8+
import std.mem;
9+
import std.core;
10+
import std.io;
11+
import type.color;
12+
13+
using Color = type::RGB8 [[hex::inline_visualize("color", r, g, b, 0xff)]];
14+
bitfield Color64 {
15+
r : 6;
16+
padding : 2;
17+
g : 6;
18+
padding : 2;
19+
b : 6;
20+
padding : 2;
21+
22+
u8 r8 = r << 2 | r >> 4;
23+
u8 g8 = g << 2 | g >> 4;
24+
u8 b8 = b << 2 | b >> 4;
25+
} [[hex::inline_visualize("color", r8, g8, b8, 0xff)]];
26+
27+
enum FlicType : u16 {
28+
FLI = 0xAF11,
29+
FLC_8bit = 0xAF12,
30+
FLIC_other = 0xAF44,
31+
FLIC_Huffmann = 0xAF30,
32+
FLIC_frame_shift = 0xAF31,
33+
};
34+
35+
enum ChunkType : u16 {
36+
CEL_DATA = 3, // Registration and transparency
37+
COLOR_256 = 4, // 256-level colour palette
38+
DELTA_FLC = 7, // Delta image, word oriented RLE (FLI_SS2)
39+
COLOR_64 = 11, // 64-level colour palette
40+
DELTA_FLI = 12, // Delta image, byte oriented RLE (FLI_LC)
41+
BLACK = 13, // Full black frame (rare)
42+
BYTE_RUN = 15, // Full image, byte oriented RLE (FLI_BRUN)
43+
FLI_COPY = 16, // Uncompressed image (rare)
44+
PSTAMP = 18, // Postage stamp (icon of the first frame)
45+
DTA_BRUN = 25, // Full image, pixel oriented RLE
46+
DTA_COPY = 26, // Uncompressed image
47+
DTA_LC = 27, // Delta image, pixel oriented RLE
48+
LABEL = 31, // Frame label
49+
BMP_MASK = 32, // Bitmap mask
50+
MLEV_MASK = 33, // Multilevel mask
51+
SEGMENT = 34, // Segment information
52+
KEY_IMAGE = 35, // Key image, similar to BYTE_RUN / DTA_BRUN
53+
KEY_PAL = 36, // Key palette, similar to COLOR_256
54+
REGION = 37, // Region of frame differences
55+
WAVE = 38, // Digitized audio
56+
USERSTRING = 39, // General purpose user data
57+
RGN_MASK = 40, // Region mask
58+
LABELEX = 41, // Extended frame label (includes symbolic name)
59+
SHIFT = 42, // Scanline delta shifts (compression)
60+
PATHMAP = 43, // Path map (segment transitions)
61+
62+
PREFIX_TYPE = 0xF100, // Prefix chunk
63+
SCRIPT_CHUNK = 0xF1E0, // Embedded "Small" script
64+
FRAME_TYPE = 0xF1FA, // Frame chunk
65+
SEGMENT_TABLE = 0xF1FB, // Segment table chunk
66+
HUFFMAN_TABLE = 0xF1FC // Huffman compression table chunk
67+
};
68+
69+
bitfield ExtFlags {
70+
segment_table_present : 1;
71+
regular_key_imagees : 1;
72+
identical_palette : 1;
73+
huffmann_compressed : 1;
74+
bwt_huffmann_compressed : 1;
75+
bitmap_masks_present : 1;
76+
multilevel_masks_present : 1;
77+
region_data_present : 1;
78+
password_protected : 1;
79+
digitized_audio : 1;
80+
contains_script : 1;
81+
region_masks_present : 1;
82+
contains_overlays : 1;
83+
frame_shift_compressed : 1;
84+
padding : 2;
85+
};
86+
87+
u16 pixels_per_line = 0;
88+
u16 lines = 0;
89+
90+
struct Header {
91+
u32 size [[comment("Size of FLIC including this header")]];
92+
FlicType type [[comment("File type 0xAF11, 0xAF12, 0xAF30, 0xAF44, ...")]];
93+
u16 frames [[comment("Number of frames in first segment")]];
94+
u16 width [[comment("FLIC width in pixels")]];
95+
u16 height [[comment("FLIC height in pixels")]];
96+
u16 depth [[comment("Bits per pixel (usually 8)")]];
97+
u16 flags [[comment("Set to zero or to three")]];
98+
u32 speed [[comment("Delay between frames")]];
99+
u16 reserved1 [[comment("Set to zero")]];
100+
u32 created [[comment("Date of FLIC creation (FLC only)")]];
101+
u32 creator [[comment("Serial number or compiler id (FLC only)")]];
102+
u32 updated [[comment("Date of FLIC update (FLC only)")]];
103+
u32 updater [[comment("Serial number (FLC only), see creator")]];
104+
u16 aspect_dx [[comment("Width of square rectangle (FLC only)")]];
105+
u16 aspect_dy [[comment("Height of square rectangle (FLC only)")]];
106+
ExtFlags ext_flags [[comment("EGI: flags for specific EGI extensions")]];
107+
u16 keyframes [[comment("EGI: key-image frequency")]];
108+
u16 totalframes [[comment("EGI: total number of frames (segments)")]];
109+
u32 req_memory [[comment("EGI: maximum chunk size (uncompressed)")]];
110+
u16 max_regions [[comment("EGI: max. number of regions in a CHK_REGION chunk")]];
111+
u16 transp_num [[comment("EGI: number of transparent levels")]];
112+
u8 reserved2[24] [[comment("Set to zero")]];
113+
u32 oframe1 [[comment("Offset to frame 1 (FLC only)")]];
114+
u32 oframe2 [[comment("Offset to frame 2 (FLC only)")]];
115+
u8 reserved3[40] [[comment("Set to zero")]];
116+
117+
pixels_per_line = width;
118+
lines = height;
119+
};
120+
121+
namespace DeltaFLCChunk {
122+
enum OpcodeType : u8 {
123+
Count = 0b00,
124+
Undefined = 0b01,
125+
LowByte = 0b10,
126+
LineSkipCount = 0b11,
127+
};
128+
129+
struct Packet {
130+
u8 skip_count;
131+
s8 count;
132+
if (count < 0) {
133+
u8 replicate_bytes[2];
134+
} else {
135+
u8 literal_bytes[count * 2];
136+
}
137+
};
138+
139+
u16 packets_in_line;
140+
141+
fn format_opcode(auto opcode) {
142+
match (opcode.type) {
143+
(OpcodeType::Count): return std::format("Packet(s): {0}", opcode.value);
144+
(OpcodeType::Undefined): return "Undefined";
145+
(OpcodeType::LowByte): return std::format("Last byte: {0:02x}", opcode.value);
146+
(OpcodeType::LineSkipCount): {
147+
s16 total = 0xC000 | opcode.value;
148+
return std::format("Lines skipped: {0}", total);
149+
}
150+
}
151+
};
152+
153+
bitfield Opcode {
154+
value: 14;
155+
OpcodeType type: 2;
156+
157+
if (type == OpcodeType::Count) {
158+
packets_in_line = value;
159+
break;
160+
}
161+
} [[format("DeltaFLCChunk::format_opcode")]];
162+
163+
struct Line {
164+
packets_in_line = 0;
165+
Opcode opcodes[while(true)];
166+
Packet packets[packets_in_line];
167+
};
168+
}
169+
170+
namespace ByteRunChunk {
171+
u16 pixels_in_line_counter = 0;
172+
173+
struct Packet {
174+
s8 count;
175+
if (count < 0) {
176+
pixels_in_line_counter = -count + pixels_in_line_counter;
177+
u8 literal_bytes[-count];
178+
} else {
179+
pixels_in_line_counter = count + pixels_in_line_counter;
180+
u8 replicate_byte;
181+
}
182+
} [[format("format_byte_run_packet")]];
183+
184+
struct Line {
185+
pixels_in_line_counter = 0;
186+
u8 packet_count;
187+
Packet packets[while(pixels_in_line_counter < pixels_per_line)];
188+
};
189+
}
190+
191+
struct ColorPalettePacket<C> {
192+
u8 skip;
193+
u8 copy;
194+
if (copy == 0) {
195+
C color[256];
196+
} else {
197+
C color[copy];
198+
}
199+
};
200+
201+
fn format_chunk(auto chunk) {
202+
return std::format("{}", chunk.type);
203+
};
204+
205+
fn format_byte_run_packet(auto packet) {
206+
if (packet.count == -1) {
207+
return std::format("One {}", packet.literal_bytes[0]);
208+
} else if (packet.count < 0) {
209+
return std::format("{} bytes", -packet.count);
210+
} else {
211+
return std::format("{}x color {}", packet.count, packet.replicate_byte);
212+
}
213+
};
214+
215+
struct Chunk {
216+
u32 size;
217+
ChunkType type;
218+
219+
if (type == ChunkType::FRAME_TYPE) {
220+
u16 chunks;
221+
u16 delay;
222+
u16 reserved;
223+
u16 width;
224+
u16 height;
225+
// Not sure if this is the intended operation here?
226+
if (width > 0) {
227+
pixels_per_line = width;
228+
}
229+
if (height > 0) {
230+
lines = height;
231+
}
232+
Chunk subchunks[chunks];
233+
} else if (type == ChunkType::COLOR_256) {
234+
u16 num_packets;
235+
ColorPalettePacket<Color> packets[num_packets];
236+
} else if (type == ChunkType::COLOR_64) {
237+
u16 num_packets;
238+
ColorPalettePacket<Color64> packets[num_packets];
239+
} else if (type == ChunkType::BYTE_RUN) {
240+
ByteRunChunk::Line lines[lines];
241+
} else if (type == ChunkType::DELTA_FLC) {
242+
u16 line_count;
243+
#ifdef DECODE_DELTA_FLC
244+
DeltaFLCChunk::Line lines[line_count];
245+
#endif
246+
#ifndef DECODE_DELTA_FLC
247+
u8 data[size - ($ - addressof(this))];
248+
#endif
249+
} else if (type == ChunkType::PSTAMP) {
250+
u16 height;
251+
u16 width;
252+
u16 xlate;
253+
u8 data[size - ($ - addressof(this))];
254+
} else if (type == ChunkType::FLI_COPY) {
255+
u8 pixels[lines * pixels_per_line];
256+
}
257+
s128 remainingBytes = size - ($ - addressof(this));
258+
// When the chunk size is rounded up to the next even number, it might need a padding byte.
259+
// Unrecognized chunk types will also fill out the padding
260+
if (remainingBytes > 0) {
261+
padding[size - ($ - addressof(this))];
262+
} else if (remainingBytes < 0) {
263+
std::warning("Chunk size wasn't large enough");
264+
}
265+
} [[format("format_chunk")]];
266+
267+
Header header @ $;
268+
Chunk chunks[while(!std::mem::eof())] @ $;
4.98 KB
Binary file not shown.

0 commit comments

Comments
 (0)