1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
use std::fs::File;
use std::io::{Read, Seek, SeekFrom};
use std::path::{Component, PathBuf};

use anyhow::Result;
use zerocopy::FromBytes;

use crate::toc::node::{DirectoryNode, Node, NodeKind};
use crate::toc::raw_toc_entry::{RawTocEntry, TOC_ENTRY_SIZE};

pub struct Toc {
    toc_path: PathBuf,
    directories: Vec<Node>,
    files: Vec<Node>,
    root: Option<Node>,
}

impl Toc {
    pub fn new(toc_path: PathBuf) -> Self {
        Self {
            toc_path,
            directories: Vec::new(),
            files: Vec::new(),
            root: None,
        }
    }

    pub fn directories(&self) -> Vec<Node> {
        self.directories.clone()
    }

    pub fn files(&self) -> Vec<Node> {
        self.files.clone()
    }

    pub fn root(&self) -> Option<Node> {
        self.root.clone()
    }

    pub fn is_loaded(&self) -> bool {
        self.root.is_some()
    }

    pub fn read_toc(&mut self) -> Result<()> {
        if self.is_loaded() {
            return Ok(()); // TOC already loaded
        }

        // Clear the directory and file vectors in case they were populated
        // from a previous read
        self.unread_toc();

        let mut toc_reader = File::open(&self.toc_path).unwrap();
        let entry_count = (toc_reader.metadata().unwrap().len() as usize - 8) / TOC_ENTRY_SIZE;
        toc_reader.seek(SeekFrom::Start(8)).unwrap();

        // Reserve space for the entries in the vectors to avoid unnecessary
        // reallocations
        self.files.reserve(entry_count);
        self.directories.reserve(entry_count);

        let mut file_count = 0;
        let mut dir_count = 1; // Hardcoded root directory

        let root = Node::root();
        self.directories.insert(0, root.clone());

        let mut buffer = vec![0u8; TOC_ENTRY_SIZE * entry_count];
        toc_reader.read_exact(&mut buffer).unwrap();

        let entries = RawTocEntry::slice_from(&buffer).unwrap();
        for entry in entries {
            // Entry timestamp of 0 means the entry has been replaced with a
            // newer version with the same name and path with a valid timestamp
            if entry.timestamp == 0 {
                continue;
            }

            // Entry name is a null-terminated string, so we need to find the
            // index of the null byte and truncate the string there
            let entry_name = {
                let null_index = entry.name.iter().position(|&x| x == 0).unwrap_or(64);

                // SAFETY: The entry name is guaranteed to be valid UTF-8
                let entry_name = unsafe { std::str::from_utf8_unchecked(&entry.name[..null_index]) };
                entry_name.to_string()
            };

            let parent_node = self
                .directories
                .get(entry.parent_dir_index as usize)
                .unwrap();

            // If the cache offset is -1, then the entry is a directory
            if entry.cache_offset == -1 {
                let dir_node = Node::directory(entry_name);

                parent_node.append(dir_node.clone());
                self.directories.insert(dir_count, dir_node);

                dir_count += 1;
            } else {
                let file_node = Node::file(
                    entry_name,
                    entry.cache_offset,
                    entry.timestamp,
                    entry.comp_len,
                    entry.len,
                );

                parent_node.append(file_node.clone());
                self.files.insert(file_count, file_node);

                file_count += 1;
            }
        }

        // Shrink the vectors to the actual size of the vectors to save memory
        self.directories.shrink_to_fit();
        self.files.shrink_to_fit();

        self.root = Some(root);

        Ok(()) // TOC read successfully
    }

    pub fn unread_toc(&mut self) {
        self.directories.clear();
        self.files.clear();
        self.root = None;
    }

    fn get_node(&self, path: PathBuf) -> Option<Node> {
        if !self.is_loaded() {
            return None;
        }

        if !path.has_root() {
            panic!("Path must be absolute");
        }

        let mut components = path.components();
        let mut current_node = self.root.clone().unwrap();

        // Skip root
        components.next();

        for component in components {
            match component {
                Component::Normal(name) => {
                    let name = name.to_str().unwrap();
                    current_node = match current_node.get_child(name) {
                        Some(child) => child,
                        _ => return None,
                    };
                }
                Component::ParentDir => {
                    current_node = match current_node.parent() {
                        Some(parent) => parent,
                        _ => return None,
                    };
                }
                Component::CurDir => continue,
                _ => return None,
            }
        }

        Some(current_node)
    }

    pub fn get_directory_node(&self, path: PathBuf) -> Option<Node> {
        match self.get_node(path) {
            Some(node) => match node.kind() {
                NodeKind::Directory => Some(node),
                _ => None,
            },
            _ => None,
        }
    }

    pub fn get_file_node(&self, path: PathBuf) -> Option<Node> {
        match self.get_node(path) {
            Some(node) => match node.kind() {
                NodeKind::File => Some(node),
                _ => None,
            },
            _ => None,
        }
    }
}