pyaxml_rs/
proto_conv.rs

1//! Conversions between the internal [`Axml`] representation and the
2//! prost-generated protobuf types in [`crate::proto`].
3
4use prost::Message;
5
6use crate::axml::Axml;
7use crate::error::AxmlError;
8use crate::proto;
9use crate::resource_map::ResourceMap;
10use crate::string_pool::{StringBlocks, StringPool};
11use crate::xml_element::{Attribute, XmlElement};
12
13// ─────────────────────────── Public API ──────────────────────────────────────
14
15impl Axml {
16    /// Serialize this AXML file as a binary protobuf message (proto encoding,
17    /// not the Android binary AXML format).
18    pub fn to_proto_bytes(&self) -> Vec<u8> {
19        axml_to_proto(self).encode_to_vec()
20    }
21
22    /// Deserialize an AXML file from binary protobuf bytes produced by
23    /// [`to_proto_bytes`].
24    pub fn from_proto_bytes(data: &[u8]) -> Result<Self, AxmlError> {
25        let p = proto::Axml::decode(data)?;
26        proto_to_axml(p)
27    }
28
29    /// Convert to the prost-generated [`proto::Axml`] message for in-process
30    /// manipulation.
31    pub fn to_proto(&self) -> proto::Axml {
32        axml_to_proto(self)
33    }
34
35    /// Reconstruct an [`Axml`] from a prost-generated [`proto::Axml`] message.
36    pub fn from_proto(p: proto::Axml) -> Result<Self, AxmlError> {
37        proto_to_axml(p)
38    }
39
40    /// Recompute `self.proto` and `self.stringblocks.proto` from the current
41    /// in-memory state.  Called automatically at the end of `from_axml()` and
42    /// `from_xml()`.  Call manually if you modify `stringblocks.inner` or
43    /// `elements` directly.
44    pub fn update_proto(&mut self) {
45        let sb_proto: proto::StringBlocks = string_pool_to_proto(&self.stringblocks.inner);
46        self.stringblocks.proto = sb_proto.clone();
47        self.proto = proto::Axml {
48            header_xml: Some(proto::AxmlHeader {
49                r#type: proto::ResType::ResXmlType as i32,
50                header_size: self.file_header_size as u32,
51                size: self.total_size,
52            }),
53            stringblocks: Some(sb_proto),
54            resourcemap: self.resource_map.as_ref().map(resource_map_to_proto),
55            resourcexml: Some(elements_to_proto(&self.elements)),
56        };
57    }
58}
59
60// ───────────────────────── Axml → proto ──────────────────────────────────────
61
62pub(crate) fn axml_to_proto(axml: &Axml) -> proto::Axml {
63    proto::Axml {
64        header_xml: Some(proto::AxmlHeader {
65            r#type: proto::ResType::ResXmlType as i32,
66            header_size: axml.file_header_size as u32,
67            size: axml.total_size,
68        }),
69        stringblocks: Some(string_pool_to_proto(&axml.stringblocks.inner)),
70        resourcemap: axml.resource_map.as_ref().map(resource_map_to_proto),
71        resourcexml: Some(elements_to_proto(&axml.elements)),
72    }
73}
74
75pub(crate) fn string_pool_to_proto(sp: &StringPool) -> proto::StringBlocks {
76    // Read all layout fields directly from the struct to avoid a full re-serialization.
77    let string_count = sp.strings.len() as u32;
78    let style_count = sp.style_offsets.len() as u32;
79
80    // When the pool is clean, read the exact byte offsets from the cached raw buffer;
81    // otherwise compute them mathematically (pack_from_strings never writes styles, so
82    // strings_start = header(28) + 4*string_count).
83    let (pool_size, strings_start, styles_start) =
84        if let Some(raw) = sp.raw.as_ref().filter(|_| !sp.dirty) {
85            if raw.len() >= 28 {
86                (
87                    raw.len() as u32,
88                    u32::from_le_bytes([raw[20], raw[21], raw[22], raw[23]]),
89                    u32::from_le_bytes([raw[24], raw[25], raw[26], raw[27]]),
90                )
91            } else {
92                (0u32, 28 + 4 * string_count, 0u32)
93            }
94        } else {
95            // pool_size is informational; 0 is safe when dirty.
96            (0u32, 28 + 4 * string_count + 4 * style_count, 0u32)
97        };
98
99    let hnd = proto::AxmlHeaderStringpool {
100        hnd: Some(proto::AxmlHeader {
101            r#type: proto::ResType::ResStringPoolType as i32,
102            header_size: 28,
103            size: pool_size,
104        }),
105        len_stringblocks: string_count,
106        len_styleblocks: style_count,
107        flag: sp.flags,
108        stringoffset: strings_start,
109        styleoffset: styles_start,
110    };
111
112    // Reuse cached string offsets when the pool is clean; they are informational in
113    // the proto view and recomputed from scratch by pack_from_strings() on dirty pools.
114    let stringoffsets = if !sp.dirty && !sp.string_offsets.is_empty() {
115        sp.string_offsets.clone()
116    } else {
117        Vec::new()
118    };
119
120    let styleoffsets = sp.style_offsets.clone();
121
122    // Build StringBlock entries from the raw string bytes stored in the pool
123    let stringblocks: Vec<proto::StringBlock> = sp
124        .strings
125        .iter()
126        .map(|raw| {
127            let (size, redundant_size, padding) = if sp.is_utf8 {
128                let char_count = String::from_utf8_lossy(raw).chars().count() as u32;
129                let byte_count = raw.len() as u32;
130                (char_count, Some(byte_count), vec![0u8])
131            } else {
132                (raw.len() as u32 / 2, None, vec![0u8, 0u8])
133            };
134            proto::StringBlock {
135                size,
136                redundant_size,
137                data: raw.clone(),
138                padding,
139            }
140        })
141        .collect();
142
143    proto::StringBlocks {
144        hnd: Some(hnd),
145        stringoffsets,
146        styleoffsets,
147        stringblocks,
148        pad_stringblocks: vec![],
149        styleblocks: vec![],
150        pad_styleblocks: vec![],
151    }
152}
153
154fn resource_map_to_proto(rm: &ResourceMap) -> proto::ResourceMap {
155    proto::ResourceMap {
156        header: Some(proto::AxmlHeader {
157            r#type: proto::ResType::ResXmlResourceMapType as i32,
158            header_size: 8,
159            size: (8 + rm.ids.len() * 4) as u32,
160        }),
161        res: rm.ids.clone(),
162    }
163}
164
165fn elements_to_proto(elements: &[XmlElement]) -> proto::ResourceXml {
166    proto::ResourceXml {
167        elts: elements.iter().map(xml_element_to_proto).collect(),
168    }
169}
170
171fn xml_element_to_proto(elt: &XmlElement) -> proto::XmlElement {
172    let chunk_size = elt.pack().len() as u32;
173    let chunk_type = elt.chunk_type() as i32;
174
175    let _header = Some(proto::AxmlHeader {
176        r#type: chunk_type,
177        header_size: 16,
178        size: chunk_size,
179    });
180
181    match elt {
182        XmlElement::StartNamespace {
183            header_size,
184            chunk_size,
185            line_number,
186            comment,
187            prefix,
188            uri,
189            ..
190        } => proto::XmlElement {
191            header: Some(proto::AxmlHeader {
192                r#type: proto::ResType::ResXmlFirstChunkType as i32,
193                header_size: *header_size as u32,
194                size: *chunk_size,
195            }),
196            start_ns: Some(proto::ResXmlStartNamespace {
197                generic: Some(res_xml_generic(*line_number, *comment)),
198                prefix: *prefix,
199                uri: *uri,
200            }),
201            ..Default::default()
202        },
203
204        XmlElement::EndNamespace {
205            header_size,
206            chunk_size,
207            line_number,
208            comment,
209            prefix,
210            uri,
211            ..
212        } => proto::XmlElement {
213            header: Some(proto::AxmlHeader {
214                r#type: proto::ResType::ResXmlEndNamespaceType as i32,
215                header_size: *header_size as u32,
216                size: *chunk_size,
217            }),
218            end_ns: Some(proto::ResXmlEndNamespace {
219                generic: Some(res_xml_generic(*line_number, *comment)),
220                prefix: *prefix,
221                uri: *uri,
222            }),
223            ..Default::default()
224        },
225
226        XmlElement::StartElement {
227            header_size,
228            chunk_size,
229            line_number,
230            comment,
231            namespace_uri,
232            name,
233            at_start,
234            at_size,
235            style_attribute,
236            class_attribute,
237            attributes,
238            ..
239        } => {
240            let proto_attrs = attributes
241                .iter()
242                .map(|a| proto::Attribute {
243                    namespace_uri: a.namespace_uri,
244                    name: a.name,
245                    value: a.value,
246                    r#type: a.type_,
247                    data: a.data,
248                    padding: a.padding.clone(),
249                })
250                .collect();
251
252            proto::XmlElement {
253                header: Some(proto::AxmlHeader {
254                    r#type: proto::ResType::ResXmlStartElementType as i32,
255                    header_size: *header_size as u32,
256                    size: *chunk_size,
257                }),
258                start_elt: Some(proto::ResXmlStartElement {
259                    generic: Some(res_xml_generic(*line_number, *comment)),
260                    namespace_uri: *namespace_uri,
261                    name: *name,
262                    at_start: *at_start as u32,
263                    at_size: *at_size as u32,
264                    attributevalue: 0,
265                    len_attributes: attributes.len() as u32,
266                    style_attribute: *style_attribute as i32,
267                    class_attribute: *class_attribute as i32,
268                    attributes: proto_attrs,
269                }),
270                ..Default::default()
271            }
272        }
273
274        XmlElement::EndElement {
275            header_size,
276            chunk_size,
277            line_number,
278            comment,
279            namespace_uri,
280            name,
281            ..
282        } => proto::XmlElement {
283            header: Some(proto::AxmlHeader {
284                r#type: proto::ResType::ResXmlEndElementType as i32,
285                header_size: *header_size as u32,
286                size: *chunk_size,
287            }),
288            end_elt: Some(proto::ResXmlEndElement {
289                generic: Some(res_xml_generic(*line_number, *comment)),
290                namespace_uri: *namespace_uri,
291                name: *name,
292            }),
293            ..Default::default()
294        },
295
296        XmlElement::CData {
297            header_size,
298            chunk_size,
299            line_number,
300            comment,
301            name,
302            res_size,
303            res_res0,
304            res_data_type,
305            res_data,
306            ..
307        } => proto::XmlElement {
308            header: Some(proto::AxmlHeader {
309                r#type: proto::ResType::ResXmlCdataType as i32,
310                header_size: *header_size as u32,
311                size: *chunk_size,
312            }),
313            cdata: Some(proto::ResXmlcdata {
314                generic: Some(res_xml_generic(*line_number, *comment)),
315                name: *name,
316                res: Some(proto::ArscResStringPoolRef {
317                    size: *res_size as u32,
318                    res0: *res_res0 as u32,
319                    data_type: *res_data_type as u32,
320                    data: *res_data,
321                }),
322            }),
323            ..Default::default()
324        },
325    }
326}
327
328#[inline]
329fn res_xml_generic(line_number: u32, comment: u32) -> proto::ResXml {
330    proto::ResXml {
331        line_number,
332        comment,
333    }
334}
335
336// ───────────────────────── proto → Axml ──────────────────────────────────────
337
338fn proto_to_axml(p: proto::Axml) -> Result<Axml, AxmlError> {
339    let (file_type, file_header_size, total_size) = p
340        .header_xml
341        .map(|h| (h.r#type as u16, h.header_size as u16, h.size))
342        .unwrap_or((crate::axml::RES_XML_TYPE, 8, 0));
343
344    let sb_proto = p.stringblocks.unwrap_or_default();
345    let string_pool = proto_to_string_pool(sb_proto.clone());
346
347    let resource_map = p.resourcemap.map(proto_to_resource_map);
348
349    let elements = p
350        .resourcexml
351        .map(proto_to_elements)
352        .transpose()?
353        .unwrap_or_default();
354
355    let mut axml = Axml {
356        proto: proto::Axml::default(),
357        stringblocks: StringBlocks::from_pool_and_proto(string_pool, sb_proto),
358        resource_map,
359        elements,
360        file_type,
361        file_header_size,
362        total_size,
363    };
364    axml.update_proto();
365    Ok(axml)
366}
367
368pub(crate) fn proto_to_string_pool(sb: proto::StringBlocks) -> StringPool {
369    let (is_utf8, flags) = sb
370        .hnd
371        .as_ref()
372        .map(|h| {
373            let f = h.flag;
374            ((f & crate::string_pool::UTF8_FLAG) != 0, f)
375        })
376        .unwrap_or((false, 0));
377
378    let strings: Vec<Vec<u8>> = sb.stringblocks.into_iter().map(|s| s.data).collect();
379    let string_offsets = sb.stringoffsets;
380    let style_offsets = sb.styleoffsets;
381
382    // Rebuild the reverse index so find() / add() stay O(1).
383    let index = strings
384        .iter()
385        .enumerate()
386        .map(|(i, data)| (data.clone(), i as u32))
387        .collect();
388
389    StringPool {
390        raw: None,
391        dirty: true,
392        is_utf8,
393        flags,
394        strings,
395        index,
396        string_offsets,
397        string_data_start: 0,
398        style_offsets,
399    }
400}
401
402fn proto_to_resource_map(rm: proto::ResourceMap) -> ResourceMap {
403    let header_size = rm
404        .header
405        .as_ref()
406        .map(|h| h.header_size as u16)
407        .unwrap_or(8);
408    let chunk_size = rm.header.as_ref().map(|h| h.size).unwrap_or(0);
409    ResourceMap {
410        ids: rm.res,
411        header_size,
412        chunk_size,
413    }
414}
415
416fn proto_to_elements(rxml: proto::ResourceXml) -> Result<Vec<XmlElement>, AxmlError> {
417    rxml.elts
418        .into_iter()
419        .filter_map(proto_to_xml_element)
420        .collect::<Result<Vec<_>, _>>()
421}
422
423pub(crate) fn proto_to_xml_element(
424    elt: proto::XmlElement,
425) -> Option<Result<XmlElement, AxmlError>> {
426    let header_size = elt
427        .header
428        .as_ref()
429        .map(|h| h.header_size as u16)
430        .unwrap_or(16);
431    let chunk_size = elt.header.as_ref().map(|h| h.size).unwrap_or(0);
432
433    if let Some(ns) = elt.start_ns {
434        let (ln, cm) = line_comment(&ns.generic);
435        return Some(Ok(XmlElement::StartNamespace {
436            header_size,
437            chunk_size,
438            line_number: ln,
439            comment: cm,
440            prefix: ns.prefix,
441            uri: ns.uri,
442        }));
443    }
444    if let Some(ns) = elt.end_ns {
445        let (ln, cm) = line_comment(&ns.generic);
446        return Some(Ok(XmlElement::EndNamespace {
447            header_size,
448            chunk_size,
449            line_number: ln,
450            comment: cm,
451            prefix: ns.prefix,
452            uri: ns.uri,
453        }));
454    }
455    if let Some(se) = elt.start_elt {
456        let (ln, cm) = line_comment(&se.generic);
457        let attrs = se
458            .attributes
459            .into_iter()
460            .map(|a| Attribute {
461                namespace_uri: a.namespace_uri,
462                name: a.name,
463                value: a.value,
464                type_: a.r#type,
465                data: a.data,
466                padding: a.padding,
467            })
468            .collect();
469        return Some(Ok(XmlElement::StartElement {
470            header_size,
471            chunk_size,
472            line_number: ln,
473            comment: cm,
474            namespace_uri: se.namespace_uri,
475            name: se.name,
476            at_start: se.at_start as i16,
477            at_size: se.at_size as i16,
478            style_attribute: se.style_attribute as i16,
479            class_attribute: se.class_attribute as i16,
480            attributes: attrs,
481        }));
482    }
483    if let Some(ee) = elt.end_elt {
484        let (ln, cm) = line_comment(&ee.generic);
485        return Some(Ok(XmlElement::EndElement {
486            header_size,
487            chunk_size,
488            line_number: ln,
489            comment: cm,
490            namespace_uri: ee.namespace_uri,
491            name: ee.name,
492        }));
493    }
494    if let Some(cd) = elt.cdata {
495        let (ln, cm) = line_comment(&cd.generic);
496        let (res_size, res_res0, res_data_type, res_data) = cd
497            .res
498            .map(|r| (r.size as u16, r.res0 as u8, r.data_type as u8, r.data))
499            .unwrap_or((0, 0, 0, 0));
500        return Some(Ok(XmlElement::CData {
501            header_size,
502            chunk_size,
503            line_number: ln,
504            comment: cm,
505            name: cd.name,
506            res_size,
507            res_res0,
508            res_data_type,
509            res_data,
510        }));
511    }
512    None
513}
514
515#[inline]
516fn line_comment(g: &Option<proto::ResXml>) -> (u32, u32) {
517    g.as_ref()
518        .map(|r| (r.line_number, r.comment))
519        .unwrap_or((0, 0xffff_ffff))
520}
521
522// ─────────────────────────────── Arsc ────────────────────────────────────────
523
524use crate::arsc::{
525    Arsc, ArscEntry, ArscPackage, ArscResChunk, ArscTypeSpec, ArscTypeType, ArscValue,
526};
527
528impl Arsc {
529    /// Serialize `self.proto` as binary protobuf bytes.
530    pub fn to_proto_bytes(&self) -> Vec<u8> {
531        self.proto.encode_to_vec()
532    }
533
534    /// Deserialize an [`Arsc`] from binary protobuf bytes produced by [`to_proto_bytes`].
535    pub fn from_proto_bytes(data: &[u8]) -> Result<Self, AxmlError> {
536        let p = proto::Arsc::decode(data)?;
537        proto_to_arsc(p)
538    }
539
540    /// Reconstruct an [`Arsc`] from a prost-generated [`proto::Arsc`] message.
541    pub fn from_proto(p: proto::Arsc) -> Result<Self, AxmlError> {
542        proto_to_arsc(p)
543    }
544
545    /// Convert to the prost-generated [`proto::Arsc`] message for in-process manipulation.
546    pub fn to_proto(&self) -> proto::Arsc {
547        self.proto.clone()
548    }
549
550    /// Recompute `self.proto` and `self.stringblocks.proto` from the current
551    /// in-memory state.  Called automatically at the end of `from_axml()`.
552    /// Call manually if you modify `stringblocks.inner` or `packages` directly.
553    pub fn update_proto(&mut self) {
554        let sb_proto = string_pool_to_proto(&self.stringblocks.inner);
555        self.stringblocks.proto = sb_proto.clone();
556
557        let restablespackage: Vec<proto::AxmlResTablePackage> = self
558            .packages
559            .iter_mut()
560            .map(|pkg| {
561                let p = arsc_package_to_proto(pkg);
562                pkg.proto = p.clone();
563                p
564            })
565            .collect();
566
567        self.proto = proto::Arsc {
568            header_res: Some(proto::AxmlHeaderRestable {
569                hnd: Some(proto::AxmlHeader {
570                    r#type: proto::ResType::ResTableType as i32,
571                    header_size: 12,
572                    size: self.total_size,
573                }),
574                package_count: self.package_count,
575            }),
576            stringblocks: Some(sb_proto),
577            restablespackage,
578        };
579    }
580}
581
582fn arsc_package_to_proto(pkg: &ArscPackage) -> proto::AxmlResTablePackage {
583    let type_sp = string_pool_to_proto(&pkg.type_strings);
584    let key_sp = string_pool_to_proto(&pkg.key_strings);
585
586    let restypes: Vec<proto::ArscResType> = pkg.chunks.iter().map(arsc_chunk_to_proto).collect();
587
588    proto::AxmlResTablePackage {
589        hnd: Some(proto::AxmlHeader {
590            r#type: crate::arsc::RES_TABLE_PACKAGE_TYPE as i32,
591            header_size: 8,
592            size: pkg.chunk_size,
593        }),
594        id: pkg.id,
595        name: pkg.name.clone(),
596        type_strings: 0,
597        last_public_type: 0,
598        key_strings: 0,
599        last_public_key: 0,
600        padding: vec![],
601        type_sp_string: Some(type_sp),
602        key_sp_string: Some(key_sp),
603        restypes,
604    }
605}
606
607fn arsc_chunk_to_proto(chunk: &ArscResChunk) -> proto::ArscResType {
608    match chunk {
609        ArscResChunk::Spec(spec) => proto::ArscResType {
610            hnd: Some(proto::AxmlHeader {
611                r#type: crate::arsc::RES_TABLE_TYPE_SPEC_TYPE as i32,
612                header_size: 8,
613                size: (8 + 8 + spec.entries.len() * 4) as u32,
614            }),
615            typespec: Some(proto::ArscResTypeSpec {
616                id: spec.id as u32,
617                res0: 0,
618                res1: 0,
619                entry_count: spec.entries.len() as u32,
620                entries: spec.entries.clone(),
621            }),
622            typetype: None,
623        },
624        ArscResChunk::Type(tt) => {
625            let tables: Vec<proto::ArscResTableEntry> =
626                tt.tables.iter().map(arsc_entry_to_proto).collect();
627            proto::ArscResType {
628                hnd: Some(proto::AxmlHeader {
629                    r#type: crate::arsc::RES_TABLE_TYPE_TYPE as i32,
630                    header_size: 8,
631                    size: 0,
632                }),
633                typespec: None,
634                typetype: Some(proto::ArscResTypeType {
635                    id: tt.id as u32,
636                    flags: 0,
637                    reserved: 0,
638                    entry_count: tt.tables.len() as u32,
639                    entry_start: 0,
640                    config: None,
641                    entries: vec![],
642                    tables,
643                }),
644            }
645        }
646    }
647}
648
649fn arsc_entry_to_proto(entry_opt: &Option<ArscEntry>) -> proto::ArscResTableEntry {
650    match entry_opt {
651        None => proto::ArscResTableEntry {
652            size: 0,
653            flags: 0,
654            index: 0,
655            present: false,
656            item: None,
657            key: None,
658        },
659        Some(entry) => {
660            let key = entry.value.as_ref().map(|v| proto::ArscResStringPoolRef {
661                size: v.size as u32,
662                res0: v.res0 as u32,
663                data_type: v.data_type as u32,
664                data: v.data,
665            });
666            proto::ArscResTableEntry {
667                size: 8,
668                flags: 0,
669                index: entry.key_index,
670                present: true,
671                item: None,
672                key,
673            }
674        }
675    }
676}
677
678// ─────────────────────── proto → Arsc ────────────────────────────────────────
679
680pub(crate) fn proto_to_arsc(p: proto::Arsc) -> Result<Arsc, AxmlError> {
681    let (total_size, package_count) = p
682        .header_res
683        .as_ref()
684        .map(|h| {
685            let sz = h.hnd.as_ref().map(|hnd| hnd.size).unwrap_or(0);
686            (sz, h.package_count)
687        })
688        .unwrap_or((0, 0));
689
690    let sb_proto = p.stringblocks.unwrap_or_default();
691    let string_pool = proto_to_string_pool(sb_proto.clone());
692    let stringblocks = StringBlocks::from_pool_and_proto(string_pool, sb_proto);
693
694    let packages: Vec<ArscPackage> = p
695        .restablespackage
696        .into_iter()
697        .map(proto_to_arsc_package)
698        .collect();
699
700    let mut arsc = Arsc {
701        proto: proto::Arsc::default(),
702        stringblocks,
703        packages,
704        total_size,
705        package_count,
706    };
707    arsc.update_proto();
708    Ok(arsc)
709}
710
711pub(crate) fn proto_to_arsc_package(p: proto::AxmlResTablePackage) -> ArscPackage {
712    let type_strings = p
713        .type_sp_string
714        .map(proto_to_string_pool)
715        .unwrap_or_default();
716    let key_strings = p
717        .key_sp_string
718        .map(proto_to_string_pool)
719        .unwrap_or_default();
720
721    let chunks: Vec<ArscResChunk> = p
722        .restypes
723        .into_iter()
724        .filter_map(proto_to_arsc_chunk)
725        .collect();
726
727    let chunk_size = p.hnd.as_ref().map(|h| h.size).unwrap_or(0);
728
729    ArscPackage {
730        proto: proto::AxmlResTablePackage::default(),
731        id: p.id,
732        name: p.name,
733        type_strings,
734        key_strings,
735        chunks,
736        chunk_size,
737        last_public_type: 0,
738        last_public_key: 0,
739    }
740}
741
742pub(crate) fn proto_to_arsc_chunk(p: proto::ArscResType) -> Option<ArscResChunk> {
743    if let Some(spec) = p.typespec {
744        return Some(ArscResChunk::Spec(ArscTypeSpec {
745            id: spec.id as u8,
746            res0: 0,
747            res1: 0,
748            entries: spec.entries,
749        }));
750    }
751    if let Some(tt) = p.typetype {
752        let tables: Vec<Option<ArscEntry>> =
753            tt.tables.into_iter().map(proto_to_arsc_entry).collect();
754        let config_raw = {
755            let mut cfg = vec![0u8; 32];
756            cfg[0] = 32;
757            cfg
758        };
759        return Some(ArscResChunk::Type(ArscTypeType {
760            id: tt.id as u8,
761            flags: 0,
762            reserved: 0,
763            language: String::new(),
764            region: String::new(),
765            config_raw,
766            tables,
767        }));
768    }
769    None
770}
771
772fn proto_to_arsc_entry(p: proto::ArscResTableEntry) -> Option<ArscEntry> {
773    if !p.present {
774        return None;
775    }
776    let value = p.key.map(|k| ArscValue {
777        size: k.size as u16,
778        res0: k.res0 as u8,
779        data_type: k.data_type as u8,
780        data: k.data,
781    });
782    let mut raw_tail = Vec::new();
783    let size = if let Some(ref v) = value {
784        raw_tail.extend_from_slice(&v.size.to_le_bytes());
785        raw_tail.push(v.res0);
786        raw_tail.push(v.data_type);
787        raw_tail.extend_from_slice(&v.data.to_le_bytes());
788        16 // ResTable_entry (8) + Res_value (8)
789    } else {
790        8 // Just the ResTable_entry header
791    };
792    Some(ArscEntry {
793        size: size as u16,
794        flags: 0,
795        key_index: p.index,
796        value,
797        raw_tail,
798    })
799}