Skip to main content

libvisio_rs/vsd/
parser.rs

1//! .vsd (OLE2/Compound Binary) parser.
2//!
3//! Reads OLE2 structured storage and parses the VisioDocument stream
4//! containing pointer-based tree of binary records.
5
6use crate::error::{Result, VisioError};
7use crate::model::*;
8use crate::vsd::records::*;
9use crate::vsd::shapes::*;
10use std::io::Read;
11
12/// Parse a .vsd file from bytes.
13pub fn parse_vsd(data: &[u8]) -> Result<Document> {
14    let cursor = std::io::Cursor::new(data);
15    let mut comp = cfb::CompoundFile::open(cursor).map_err(|e| VisioError::Cfb(e.to_string()))?;
16
17    let mut stream_data = Vec::new();
18    {
19        let mut stream = comp
20            .open_stream("/VisioDocument")
21            .map_err(|e| VisioError::Cfb(format!("Cannot open VisioDocument stream: {}", e)))?;
22        stream
23            .read_to_end(&mut stream_data)
24            .map_err(VisioError::Io)?;
25    }
26
27    let mut parser = VsdParser::new(&stream_data);
28    parser.parse()?;
29    Ok(parser.into_document())
30}
31
32#[allow(dead_code)]
33struct VsdParser<'a> {
34    data: &'a [u8],
35    pages: Vec<Page>,
36    current_page: Option<Page>,
37    current_shape: Option<CurrentShape>,
38    current_geom: Option<VsdGeomSection>,
39    colors: Vec<String>, // reserved for future use,
40    fonts: std::collections::HashMap<u32, String>,
41    names: std::collections::HashMap<u32, String>,
42}
43
44struct CurrentShape {
45    shape_id: u32,
46    shape_type: String,
47    xform: VsdXForm,
48    text_xform: Option<VsdTextXForm>,
49    xform_1d: Option<VsdXForm1D>,
50    text: String,
51    geometry: Vec<VsdGeomSection>,
52    char_formats: Vec<VsdCharFormat>,
53    para_formats: Vec<VsdParaFormat>,
54    line_weight: f64,
55    line_color: String,
56    line_pattern: i32,
57    fill_fg: String,
58    fill_bg: String,
59    fill_pattern: i32,
60    shadow_color: String,
61    shadow_pattern: i32,
62    shadow_offset_x: f64,
63    shadow_offset_y: f64,
64    foreign_data: Option<VsdForeignData>,
65    layer_member: String,
66}
67
68impl Default for CurrentShape {
69    fn default() -> Self {
70        Self {
71            shape_id: 0,
72            shape_type: "Shape".to_string(),
73            xform: VsdXForm::default(),
74            text_xform: None,
75            xform_1d: None,
76            text: String::new(),
77            geometry: Vec::new(),
78            char_formats: Vec::new(),
79            para_formats: Vec::new(),
80            line_weight: 0.01,
81            line_color: "#000000".to_string(),
82            line_pattern: 1,
83            fill_fg: String::new(),
84            fill_bg: String::new(),
85            fill_pattern: 1,
86            shadow_color: String::new(),
87            shadow_pattern: 0,
88            shadow_offset_x: 0.0,
89            shadow_offset_y: 0.0,
90            foreign_data: None,
91            layer_member: String::new(),
92        }
93    }
94}
95
96impl<'a> VsdParser<'a> {
97    fn new(data: &'a [u8]) -> Self {
98        Self {
99            data,
100            pages: Vec::new(),
101            current_page: None,
102            current_shape: None,
103            current_geom: None,
104            colors: Vec::new(),
105            fonts: std::collections::HashMap::new(),
106            names: std::collections::HashMap::new(),
107        }
108    }
109
110    fn parse(&mut self) -> Result<()> {
111        if self.data.len() < 0x36 {
112            return Err(VisioError::InvalidFile("File too small".to_string()));
113        }
114        // Try linear chunk scanning from offset 0x36
115        self.parse_chunks_linear(0x36);
116        self.flush_shape();
117        Ok(())
118    }
119
120    fn into_document(self) -> Document {
121        let mut doc = Document::default();
122        doc.pages = self.pages;
123        doc
124    }
125
126    fn flush_shape(&mut self) {
127        if let Some(cs) = self.current_shape.take() {
128            let mut geom = cs.geometry;
129            if let Some(g) = self.current_geom.take() {
130                geom.push(g);
131            }
132            let shape = vsd_shape_to_model(
133                &cs.xform,
134                &cs.text,
135                &geom,
136                cs.line_weight,
137                &cs.line_color,
138                cs.line_pattern,
139                &cs.fill_fg,
140                &cs.fill_bg,
141                cs.fill_pattern,
142                cs.shape_id,
143                &cs.shape_type,
144                cs.text_xform.as_ref(),
145                cs.xform_1d.as_ref(),
146                &cs.char_formats,
147                &cs.para_formats,
148                &cs.shadow_color,
149                cs.shadow_pattern,
150                cs.shadow_offset_x,
151                cs.shadow_offset_y,
152                cs.foreign_data.as_ref(),
153                &cs.layer_member,
154            );
155            if let Some(page) = &mut self.current_page {
156                page.shapes.push(shape);
157            }
158        }
159        self.current_geom = None;
160    }
161
162    fn parse_chunks_linear(&mut self, start: usize) {
163        let mut offset = start;
164        while offset + 19 < self.data.len() {
165            // Skip null bytes
166            while offset < self.data.len() && self.data[offset] == 0 {
167                offset += 1;
168            }
169            if offset + 19 > self.data.len() {
170                break;
171            }
172
173            let hdr = match parse_chunk_header(self.data, offset) {
174                Some((h, new_off)) => {
175                    offset = new_off;
176                    h
177                }
178                None => break,
179            };
180
181            let end_pos = offset + hdr.data_length as usize + hdr.trailer as usize;
182            if end_pos > self.data.len() {
183                break;
184            }
185
186            let chunk_data = &self.data[offset..offset + hdr.data_length as usize];
187            self.handle_chunk(&hdr, chunk_data);
188            offset = end_pos;
189        }
190    }
191
192    fn handle_chunk(&mut self, hdr: &ChunkHeader, data: &[u8]) {
193        match hdr.chunk_type {
194            VSD_SHAPE_GROUP | VSD_SHAPE_SHAPE | VSD_SHAPE_FOREIGN => {
195                self.flush_shape();
196                let mut cs = CurrentShape::default();
197                cs.shape_id = hdr.record_id;
198                if hdr.chunk_type == VSD_SHAPE_GROUP {
199                    cs.shape_type = "Group".to_string();
200                } else if hdr.chunk_type == VSD_SHAPE_FOREIGN {
201                    cs.shape_type = "Foreign".to_string();
202                }
203                self.current_shape = Some(cs);
204            }
205            VSD_PAGE_PROPS => self.read_page_props(data),
206            VSD_XFORM_DATA => self.read_xform(data),
207            VSD_TEXT_XFORM => self.read_text_xform(data),
208            VSD_XFORM_1D => self.read_xform_1d(data),
209            VSD_TEXT => self.read_text(data),
210            VSD_GEOMETRY => self.read_geometry(data),
211            VSD_MOVE_TO => self.read_geom_row("MoveTo", data),
212            VSD_LINE_TO => self.read_geom_row("LineTo", data),
213            VSD_ARC_TO => self.read_geom_row("ArcTo", data),
214            VSD_ELLIPSE => self.read_geom_row("Ellipse", data),
215            VSD_ELLIPTICAL_ARC_TO => self.read_geom_row("EllipticalArcTo", data),
216            VSD_SPLINE_START => self.read_geom_row("SplineStart", data),
217            VSD_SPLINE_KNOT => self.read_geom_row("SplineKnot", data),
218            VSD_INFINITE_LINE => self.read_geom_row("InfiniteLine", data),
219            VSD_NURBS_TO => self.read_nurbs_to(data),
220            VSD_POLYLINE_TO => self.read_polyline_to(data),
221            VSD_LINE => self.read_line_fmt(data),
222            VSD_FILL_AND_SHADOW => self.read_fill(data),
223            VSD_CHAR_IX => self.read_char_ix(data),
224            VSD_PARA_IX => self.read_para_ix(data),
225            VSD_LAYER_MEMBERSHIP => self.read_layer_membership(data),
226            VSD_FOREIGN_DATA_TYPE => self.read_foreign_data_type(data),
227            VSD_FOREIGN_DATA => self.read_foreign_data(data),
228            VSD_PAGE => {
229                self.flush_shape();
230                let page = Page::default();
231                self.current_page = Some(page);
232            }
233            VSD_PAGE_SHEET => {
234                if self.current_page.is_none() {
235                    self.current_page = Some(Page::default());
236                }
237            }
238            _ => {}
239        }
240
241        // After page is complete, push it
242        if hdr.chunk_type == VSD_PAGE && self.current_page.is_some() {
243            // Page will be pushed when the next page starts or at end
244        }
245    }
246
247    fn read_page_props(&mut self, data: &[u8]) {
248        if self.current_page.is_none() {
249            self.current_page = Some(Page::default());
250        }
251        let page = self.current_page.as_mut().unwrap();
252        let mut off = 1usize;
253        if let Some(w) = read_double(data, off) {
254            page.width = w;
255        }
256        off += 9;
257        if let Some(h) = read_double(data, off) {
258            page.height = h;
259        }
260    }
261
262    fn read_xform(&mut self, data: &[u8]) {
263        let cs = match &mut self.current_shape {
264            Some(s) => s,
265            None => return,
266        };
267        let mut off = 1usize;
268        if let Some(v) = read_double(data, off) {
269            cs.xform.pin_x = v;
270        }
271        off += 9;
272        if let Some(v) = read_double(data, off) {
273            cs.xform.pin_y = v;
274        }
275        off += 9;
276        if let Some(v) = read_double(data, off) {
277            cs.xform.width = v;
278        }
279        off += 9;
280        if let Some(v) = read_double(data, off) {
281            cs.xform.height = v;
282        }
283        off += 9;
284        if let Some(v) = read_double(data, off) {
285            cs.xform.loc_pin_x = v;
286        }
287        off += 9;
288        if let Some(v) = read_double(data, off) {
289            cs.xform.loc_pin_y = v;
290        }
291        off += 9;
292        if let Some(v) = read_double(data, off) {
293            cs.xform.angle = v;
294        }
295        off += 8;
296        if off < data.len() {
297            cs.xform.flip_x = data[off] != 0;
298            off += 1;
299        }
300        if off < data.len() {
301            cs.xform.flip_y = data[off] != 0;
302        }
303    }
304
305    fn read_text_xform(&mut self, data: &[u8]) {
306        let cs = match &mut self.current_shape {
307            Some(s) => s,
308            None => return,
309        };
310        let mut txf = VsdTextXForm::default();
311        let mut off = 1usize;
312        if let Some(v) = read_double(data, off) {
313            txf.txt_pin_x = v;
314        }
315        off += 9;
316        if let Some(v) = read_double(data, off) {
317            txf.txt_pin_y = v;
318        }
319        off += 9;
320        if let Some(v) = read_double(data, off) {
321            txf.txt_width = v;
322        }
323        off += 9;
324        if let Some(v) = read_double(data, off) {
325            txf.txt_height = v;
326        }
327        cs.text_xform = Some(txf);
328    }
329
330    fn read_xform_1d(&mut self, data: &[u8]) {
331        let cs = match &mut self.current_shape {
332            Some(s) => s,
333            None => return,
334        };
335        let mut xf = VsdXForm1D::default();
336        let mut off = 1usize;
337        if let Some(v) = read_double(data, off) {
338            xf.begin_x = v;
339        }
340        off += 9;
341        if let Some(v) = read_double(data, off) {
342            xf.begin_y = v;
343        }
344        off += 9;
345        if let Some(v) = read_double(data, off) {
346            xf.end_x = v;
347        }
348        off += 9;
349        if let Some(v) = read_double(data, off) {
350            xf.end_y = v;
351        }
352        cs.xform_1d = Some(xf);
353    }
354
355    fn read_text(&mut self, data: &[u8]) {
356        let cs = match &mut self.current_shape {
357            Some(s) => s,
358            None => return,
359        };
360        if data.len() < 8 {
361            return;
362        }
363        let text_data = &data[8..];
364        // Try UTF-16LE first
365        if text_data.len() >= 2 {
366            let chars: Vec<u16> = text_data
367                .chunks(2)
368                .map(|c| {
369                    if c.len() == 2 {
370                        u16::from_le_bytes([c[0], c[1]])
371                    } else {
372                        0
373                    }
374                })
375                .collect();
376            if let Ok(s) = String::from_utf16(&chars) {
377                let trimmed = s.trim_end_matches('\0').to_string();
378                if !trimmed.is_empty() && !trimmed.chars().all(|c| c == '\u{FFFD}') {
379                    cs.text = trimmed;
380                    return;
381                }
382            }
383        }
384        // Fallback to UTF-8
385        cs.text = String::from_utf8_lossy(text_data)
386            .trim_end_matches('\0')
387            .to_string();
388    }
389
390    fn read_geometry(&mut self, data: &[u8]) {
391        if self.current_shape.is_none() {
392            return;
393        }
394        // Push previous geometry section
395        if let Some(g) = self.current_geom.take() {
396            if let Some(cs) = &mut self.current_shape {
397                cs.geometry.push(g);
398            }
399        }
400        let mut geom = VsdGeomSection::default();
401        if !data.is_empty() {
402            let flags = data[0];
403            geom.no_fill = flags & 1 != 0;
404            geom.no_line = flags & 2 != 0;
405            geom.no_show = flags & 4 != 0;
406        }
407        self.current_geom = Some(geom);
408    }
409
410    fn ensure_geom(&mut self) -> bool {
411        if self.current_shape.is_none() {
412            return false;
413        }
414        if self.current_geom.is_none() {
415            self.current_geom = Some(VsdGeomSection::default());
416        }
417        true
418    }
419
420    fn read_geom_row(&mut self, row_type: &str, data: &[u8]) {
421        if !self.ensure_geom() {
422            return;
423        }
424        let mut row = VsdGeomRow::default();
425        row.row_type = row_type.to_string();
426        let mut off = 1usize;
427        if let Some(v) = read_double(data, off) {
428            row.x = v;
429        }
430        off += 9;
431        if let Some(v) = read_double(data, off) {
432            row.y = v;
433        }
434        off += 9;
435        match row_type {
436            "ArcTo" | "SplineStart" | "SplineKnot" | "InfiniteLine" => {
437                if let Some(v) = read_double(data, off) {
438                    row.a = v;
439                }
440                off += 9;
441                if let Some(v) = read_double(data, off) {
442                    row.b = v;
443                }
444                off += 9;
445            }
446            "Ellipse" | "EllipticalArcTo" => {
447                if let Some(v) = read_double(data, off) {
448                    row.a = v;
449                }
450                off += 9;
451                if let Some(v) = read_double(data, off) {
452                    row.b = v;
453                }
454                off += 9;
455                if let Some(v) = read_double(data, off) {
456                    row.c = v;
457                }
458                off += 9;
459                if let Some(v) = read_double(data, off) {
460                    row.d = v;
461                }
462            }
463            _ => {}
464        }
465        if let Some(geom) = &mut self.current_geom {
466            geom.rows.push(row);
467        }
468    }
469
470    fn read_nurbs_to(&mut self, data: &[u8]) {
471        if !self.ensure_geom() {
472            return;
473        }
474        let mut row = VsdGeomRow::default();
475        row.row_type = "NURBSTo".to_string();
476        let mut off = 1usize;
477        if let Some(v) = read_double(data, off) {
478            row.x = v;
479        }
480        off += 9;
481        if let Some(v) = read_double(data, off) {
482            row.y = v;
483        }
484        off += 9;
485        if let Some(v) = read_double(data, off) {
486            row.knot_last = v;
487        }
488        off += 9;
489        if off + 2 <= data.len() {
490            row.degree = u16::from_le_bytes([data[off], data[off + 1]]);
491            off += 2;
492        }
493        if off < data.len() {
494            row.x_type = data[off];
495            off += 1;
496        }
497        if off < data.len() {
498            row.y_type = data[off];
499            off += 1;
500        }
501        // Read control points
502        while off + 32 <= data.len() {
503            let knot = read_double(data, off).unwrap_or(0.0);
504            off += 8;
505            let weight = read_double(data, off).unwrap_or(0.0);
506            off += 8;
507            let px = read_double(data, off).unwrap_or(0.0);
508            off += 8;
509            let py = read_double(data, off).unwrap_or(0.0);
510            off += 8;
511            row.points.push((px, py, knot, weight));
512        }
513        if let Some(geom) = &mut self.current_geom {
514            geom.rows.push(row);
515        }
516    }
517
518    fn read_polyline_to(&mut self, data: &[u8]) {
519        if !self.ensure_geom() {
520            return;
521        }
522        let mut row = VsdGeomRow::default();
523        row.row_type = "PolylineTo".to_string();
524        let mut off = 1usize;
525        if let Some(v) = read_double(data, off) {
526            row.x = v;
527        }
528        off += 9;
529        if let Some(v) = read_double(data, off) {
530            row.y = v;
531        }
532        off += 9;
533        if off < data.len() {
534            row.x_type = data[off];
535            off += 1;
536        }
537        if off < data.len() {
538            row.y_type = data[off];
539            off += 1;
540        }
541        while off + 16 <= data.len() {
542            let px = read_double(data, off).unwrap_or(0.0);
543            off += 8;
544            let py = read_double(data, off).unwrap_or(0.0);
545            off += 8;
546            row.points.push((px, py, 0.0, 0.0));
547        }
548        if let Some(geom) = &mut self.current_geom {
549            geom.rows.push(row);
550        }
551    }
552
553    fn read_line_fmt(&mut self, data: &[u8]) {
554        let cs = match &mut self.current_shape {
555            Some(s) => s,
556            None => return,
557        };
558        let mut off = 1usize;
559        if let Some(v) = read_double(data, off) {
560            cs.line_weight = v;
561        }
562        off += 9;
563        if off + 3 <= data.len() {
564            let r = data[off];
565            let g = data[off + 1];
566            let b = data[off + 2];
567            cs.line_color = format!("#{:02X}{:02X}{:02X}", r, g, b);
568            off += 4; // skip alpha
569        }
570        if off < data.len() {
571            cs.line_pattern = data[off] as i32;
572        }
573    }
574
575    fn read_fill(&mut self, data: &[u8]) {
576        let cs = match &mut self.current_shape {
577            Some(s) => s,
578            None => return,
579        };
580        let mut off = 1usize;
581        if off + 3 <= data.len() {
582            cs.fill_fg = format!(
583                "#{:02X}{:02X}{:02X}",
584                data[off],
585                data[off + 1],
586                data[off + 2]
587            );
588            off += 4;
589        }
590        off += 1;
591        if off + 3 <= data.len() {
592            cs.fill_bg = format!(
593                "#{:02X}{:02X}{:02X}",
594                data[off],
595                data[off + 1],
596                data[off + 2]
597            );
598            off += 4;
599        }
600        if off < data.len() {
601            cs.fill_pattern = data[off] as i32;
602            off += 1;
603        }
604        // Shadow data
605        if off + 12 <= data.len() {
606            off += 1;
607            if off + 3 <= data.len() {
608                cs.shadow_color = format!(
609                    "#{:02X}{:02X}{:02X}",
610                    data[off],
611                    data[off + 1],
612                    data[off + 2]
613                );
614                off += 4;
615            }
616            if off < data.len() {
617                cs.shadow_pattern = data[off] as i32;
618                off += 1;
619            }
620            off += 1;
621            if let Some(v) = read_double(data, off) {
622                cs.shadow_offset_x = v;
623            }
624            off += 9;
625            if let Some(v) = read_double(data, off) {
626                cs.shadow_offset_y = v;
627            }
628        }
629    }
630
631    fn read_char_ix(&mut self, data: &[u8]) {
632        let cs = match &mut self.current_shape {
633            Some(s) => s,
634            None => return,
635        };
636        if data.len() < 12 {
637            return;
638        }
639        let mut fmt = VsdCharFormat::default();
640        let mut off = 0usize;
641        fmt.char_count = read_u32(data, off);
642        off += 4;
643        fmt.font_id = read_u16(data, off);
644        off += 3;
645        if off + 3 <= data.len() {
646            fmt.color_r = data[off];
647            fmt.color_g = data[off + 1];
648            fmt.color_b = data[off + 2];
649            off += 4;
650        }
651        if off < data.len() {
652            let mods = data[off];
653            fmt.bold = mods & 1 != 0;
654            fmt.italic = mods & 2 != 0;
655            fmt.underline = mods & 4 != 0;
656            off += 5;
657        }
658        if let Some(v) = read_double(data, off) {
659            fmt.font_size = v;
660        }
661        cs.char_formats.push(fmt);
662    }
663
664    fn read_para_ix(&mut self, data: &[u8]) {
665        let cs = match &mut self.current_shape {
666            Some(s) => s,
667            None => return,
668        };
669        if data.len() < 8 {
670            return;
671        }
672        let mut pf = VsdParaFormat::default();
673        let mut off = 0usize;
674        pf.char_count = read_u32(data, off);
675        off += 5;
676        if let Some(v) = read_double(data, off) {
677            pf.indent_first = v;
678        }
679        off += 9;
680        if let Some(v) = read_double(data, off) {
681            pf.indent_left = v;
682        }
683        off += 9;
684        if let Some(v) = read_double(data, off) {
685            pf.indent_right = v;
686        }
687        off += 9;
688        if let Some(v) = read_double(data, off) {
689            pf.spacing_line = v;
690        }
691        off += 9;
692        // Skip spacing before/after
693        off += 18;
694        off += 1;
695        if off < data.len() {
696            pf.horiz_align = data[off];
697            off += 2;
698        }
699        if off < data.len() {
700            pf.bullet = data[off];
701        }
702        cs.para_formats.push(pf);
703    }
704
705    fn read_layer_membership(&mut self, data: &[u8]) {
706        let cs = match &mut self.current_shape {
707            Some(s) => s,
708            None => return,
709        };
710        if data.len() < 2 {
711            return;
712        }
713        let chars: Vec<u16> = data
714            .chunks(2)
715            .map(|c| {
716                if c.len() == 2 {
717                    u16::from_le_bytes([c[0], c[1]])
718                } else {
719                    0
720                }
721            })
722            .collect();
723        if let Ok(s) = String::from_utf16(&chars) {
724            cs.layer_member = s.trim_end_matches('\0').trim().to_string();
725        }
726    }
727
728    fn read_foreign_data_type(&mut self, data: &[u8]) {
729        let cs = match &mut self.current_shape {
730            Some(s) => s,
731            None => return,
732        };
733        if cs.foreign_data.is_none() {
734            cs.foreign_data = Some(VsdForeignData::default());
735        }
736        // Skip image offset/dims, read image type
737        let off = 34; // Skip 4 doubles + 2 bytes
738        if off + 2 <= data.len() {
739            let img_type = u16::from_le_bytes([data[off], data[off + 1]]);
740            let fd = cs.foreign_data.as_mut().unwrap();
741            let (dt, fmt) = match img_type {
742                0 => ("img", "emf"),
743                1 => ("img", "wmf"),
744                2 => ("img", "bmp"),
745                3 => ("ole", "ole"),
746                4 => ("img", "jpg"),
747                5 => ("img", "png"),
748                6 => ("img", "gif"),
749                7 => ("img", "tiff"),
750                _ => ("img", "png"),
751            };
752            fd.data_type = dt.to_string();
753            fd.img_format = fmt.to_string();
754        }
755    }
756
757    fn read_foreign_data(&mut self, data: &[u8]) {
758        let cs = match &mut self.current_shape {
759            Some(s) => s,
760            None => return,
761        };
762        if cs.foreign_data.is_none() {
763            cs.foreign_data = Some(VsdForeignData::default());
764        }
765        cs.foreign_data.as_mut().unwrap().data = data.to_vec();
766    }
767}
768
769// Helper functions for binary reading
770
771fn read_double(data: &[u8], offset: usize) -> Option<f64> {
772    if offset + 8 > data.len() {
773        return None;
774    }
775    Some(f64::from_le_bytes(
776        data[offset..offset + 8].try_into().ok()?,
777    ))
778}
779
780fn read_u32(data: &[u8], offset: usize) -> u32 {
781    if offset + 4 > data.len() {
782        return 0;
783    }
784    u32::from_le_bytes(data[offset..offset + 4].try_into().unwrap_or([0; 4]))
785}
786
787fn read_u16(data: &[u8], offset: usize) -> u16 {
788    if offset + 2 > data.len() {
789        return 0;
790    }
791    u16::from_le_bytes(data[offset..offset + 2].try_into().unwrap_or([0; 2]))
792}
793
794fn parse_chunk_header(data: &[u8], offset: usize) -> Option<(ChunkHeader, usize)> {
795    if offset + 19 > data.len() {
796        return None;
797    }
798    let mut off = offset;
799    let mut hdr = ChunkHeader::default();
800    hdr.chunk_type = read_u32(data, off);
801    off += 4;
802    hdr.record_id = read_u32(data, off);
803    off += 4;
804    hdr.list_flag = read_u32(data, off);
805    off += 4;
806
807    if hdr.list_flag != 0 || LIST_TRAILER_TYPES.contains(&hdr.chunk_type) {
808        hdr.trailer += 8;
809    }
810
811    hdr.data_length = read_u32(data, off);
812    off += 4;
813    hdr.level = read_u16(data, off);
814    off += 2;
815    hdr.unknown = if off < data.len() { data[off] } else { 0 };
816    off += 1;
817
818    if hdr.list_flag != 0
819        || (hdr.level == 2 && hdr.unknown == 0x55)
820        || (hdr.level == 2 && hdr.unknown == 0x54 && hdr.chunk_type == 0xAA)
821        || (hdr.level == 3 && hdr.unknown != 0x50 && hdr.unknown != 0x54)
822    {
823        hdr.trailer += 4;
824    }
825
826    for &tt in TRAILER_TYPES {
827        if hdr.chunk_type == tt && hdr.trailer != 12 && hdr.trailer != 4 {
828            hdr.trailer += 4;
829            break;
830        }
831    }
832
833    if NO_TRAILER_TYPES.contains(&hdr.chunk_type) {
834        hdr.trailer = 0;
835    }
836
837    Some((hdr, off))
838}