Skip to main content

rage_rpf/
tree.rs

1use crate::archive::{RpfEntry, RpfEntryKind};
2
3/// A node in an RPF directory tree. Stores indices into the flat `RpfArchive.entries` slice.
4#[derive(Debug, Clone)]
5pub struct DirNode {
6    pub name  : String,
7    pub path  : String,
8    pub files : Vec<FileRef>,
9    pub subdirs: Vec<DirNode>,
10}
11
12/// A reference to a file entry within a `DirNode`, keyed by entry index.
13#[derive(Debug, Clone)]
14pub struct FileRef {
15    pub name        : String,
16    pub path        : String,
17    pub entry_index : usize,   // index into RpfArchive.entries
18    pub size        : u32,     // on-disk / compressed size
19    pub mem_size    : u32,     // uncompressed (binary) or resource page size (resource)
20    pub is_resource : bool,
21}
22
23/// Build a navigable directory tree from a flat `RpfArchive.entries` slice.
24pub fn build_directory_tree(entries: &[RpfEntry]) -> DirNode {
25    if entries.is_empty() {
26        return DirNode { name: String::new(), path: String::new(), files: vec![], subdirs: vec![] };
27    }
28
29    // If the first entry is a directory, it is the root.
30    let (root_name, start, count) = if let RpfEntryKind::Directory { entries_index, entries_count } = &entries[0].kind {
31        (entries[0].name.clone(), *entries_index as usize, *entries_count as usize)
32    } else {
33        // Flat archive (no directory wrapper) — put everything in a synthetic root.
34        return flat_root(entries);
35    };
36
37    let mut root = DirNode { name: root_name, path: String::new(), files: vec![], subdirs: vec![] };
38    populate(&mut root, entries, start, count, "");
39    root
40}
41
42/// Returns a flat list of all `FileRef`s in the tree (breadth-first).
43pub fn list_all_files(node: &DirNode) -> Vec<&FileRef> {
44    let mut out = Vec::new();
45    collect(node, &mut out);
46    out
47}
48
49// ─── Internals ────────────────────────────────────────────────────────────────
50
51fn populate(dir: &mut DirNode, entries: &[RpfEntry], start: usize, count: usize, parent_path: &str) {
52    let end = (start + count).min(entries.len());
53    for i in start..end {
54        let entry = &entries[i];
55        match &entry.kind {
56            RpfEntryKind::Directory { entries_index, entries_count } => {
57                let sub_path = child_path(parent_path, &entry.name_lower);
58                let mut sub = DirNode { name: entry.name.clone(), path: sub_path.clone(), files: vec![], subdirs: vec![] };
59                populate(&mut sub, entries, *entries_index as usize, *entries_count as usize, &sub_path);
60                dir.subdirs.push(sub);
61            }
62            RpfEntryKind::BinaryFile { file_size, uncompressed_size, .. } => {
63                let path = child_path(parent_path, &entry.name_lower);
64                dir.files.push(FileRef {
65                    name: entry.name.clone(), path, entry_index: i,
66                    size: *file_size, mem_size: *uncompressed_size, is_resource: false,
67                });
68            }
69            RpfEntryKind::ResourceFile { file_size, system_flags, .. } => {
70                let path = child_path(parent_path, &entry.name_lower);
71                let mem_size = crate::archive::resource_size_from_flags(*system_flags) as u32;
72                dir.files.push(FileRef {
73                    name: entry.name.clone(), path, entry_index: i,
74                    size: *file_size, mem_size, is_resource: true,
75                });
76            }
77        }
78    }
79}
80
81fn flat_root(entries: &[RpfEntry]) -> DirNode {
82    let mut root = DirNode { name: String::new(), path: String::new(), files: vec![], subdirs: vec![] };
83    for (i, entry) in entries.iter().enumerate() {
84        match &entry.kind {
85            RpfEntryKind::BinaryFile { file_size, uncompressed_size, .. } => {
86                root.files.push(FileRef {
87                    name: entry.name.clone(), path: entry.name_lower.clone(), entry_index: i,
88                    size: *file_size, mem_size: *uncompressed_size, is_resource: false,
89                });
90            }
91            RpfEntryKind::ResourceFile { file_size, system_flags, .. } => {
92                let mem_size = crate::archive::resource_size_from_flags(*system_flags) as u32;
93                root.files.push(FileRef {
94                    name: entry.name.clone(), path: entry.name_lower.clone(), entry_index: i,
95                    size: *file_size, mem_size, is_resource: true,
96                });
97            }
98            _ => {}
99        }
100    }
101    root
102}
103
104fn collect<'a>(node: &'a DirNode, out: &mut Vec<&'a FileRef>) {
105    out.extend(node.files.iter());
106    for sub in &node.subdirs { collect(sub, out); }
107}
108
109fn child_path(parent: &str, name: &str) -> String {
110    if parent.is_empty() { name.to_string() } else { format!("{}/{}", parent, name) }
111}