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
191
192
193
194
195
196
197
198
199
200
201
202
use std::{
    fs::File,
    path::Path,
    sync::Mutex,
    net::ToSocketAddrs,
    io::{Seek, SeekFrom, BufReader},
    collections::{HashMap, HashSet},
};

use binread::{
    BinRead,
    FilePtr64,
    BinResult,
    BinReaderExt,
    io::Cursor,
};

use crate::{Hash40, FileNode, FileSystem, CompressedFileSystem};
use crate::hash_labels::HashLabels;
use crate::filesystem::HashToIndex;

/// A trait representing seekable readers
pub trait SeekRead: std::io::Read + std::io::Seek {}
impl<R: std::io::Read + std::io::Seek> SeekRead for R {}

/// A struct representing the data.arc file
#[derive(BinRead)]
#[br(magic = 0xABCDEF9876543210_u64)]
pub struct ArcFile {
    pub stream_section_offset: u64,
    pub file_section_offset: u64,
    pub shared_section_offset: u64,

    #[br(parse_with = FilePtr64::parse)]
    #[br(map = |x: CompressedFileSystem| x.0)]
    pub file_system: FileSystem,
    pub patch_section: u64,

    #[br(calc = Mutex::new(Box::new(Cursor::new([])) as _))]
    pub reader: Mutex<Box<dyn SeekRead>>,

    #[cfg(feature = "dir-listing")]
    #[br(calc = generate_dir_listing(&file_system))]
    pub dirs: HashMap<Hash40, Vec<FileNode>>,
}

#[cfg(feature = "dir-listing")]
fn parents_of_dir(dir: Hash40, labels: &mut HashLabels) -> Option<Vec<(Hash40, FileNode)>> {
    let label = dir.label(&labels)?.to_owned();
    let mut label = &label[..];
    let mut hashes = Vec::new();
    let mut last_hash = dir;

    while let Some(len) = label.trim_end_matches('/').rfind('/') {
        label = &label[..len];

        let hash = labels.add_label(label);
        hashes.push((hash, FileNode::Dir(last_hash)));
        last_hash = hash;
    }

    hashes.push((crate::hash40::hash40("/"), FileNode::Dir(last_hash)));

    Some(hashes)
}

#[cfg(feature = "dir-listing")]
fn dir_listing_flat<'a>(fs: &'a FileSystem, labels: &'a mut HashLabels) -> impl Iterator<Item = (Hash40, FileNode)> + 'a {
    let dirs: HashSet<_> = fs.file_paths.iter().map(|path| path.parent.hash40()).collect();

    let mut stream_dirs = Vec::new();
    for path_hash in fs.stream_hash_to_entries.iter().map(HashToIndex::hash40) {
        if let Some(label) = path_hash.label(&labels).and_then(|label| label.rfind('/').map(|pos| label[..pos].to_owned())) {
            let mut label = &label[..];
            let mut last_hash = crate::hash40::hash40(label);
            labels.add_label(label);

            while let Some(len) = label.trim_end_matches('/').rfind('/') {
                label = &label[..len];

                let hash = labels.add_label(label);
                stream_dirs.push((hash, FileNode::Dir(last_hash)));
                last_hash = hash;
            }

            stream_dirs.push((crate::hash40::hash40("/"), FileNode::Dir(last_hash)));

            labels.add_label(label);
        }
    }

    let stream_paths: Vec<(Hash40, &str)> = fs.stream_hash_to_entries.iter().filter_map(|entry| entry.hash40().label(labels).map(|path| (entry.hash40(), path))).collect();
    let stream_files: Vec<(Hash40, FileNode)> = stream_paths.iter().flat_map(|(path_hash, path)| {
        path.rfind('/')
            .map(|pos| {
                let dir = crate::hash40::hash40(&path[..pos]);

                (dir, FileNode::File(*path_hash))
            })
    }).collect();

    // Generate parents for directories
    let dirs = dirs.into_iter()
        .filter_map(move |dir| parents_of_dir(dir, labels).map(|x| x.into_iter()))
        .flatten();

    // Generate parents for files
    fs.file_paths.iter()
        .map(|path| (path.parent.hash40(), FileNode::File(path.path.hash40())))
        .chain(dirs)
        .chain(stream_files.into_iter())
        .chain(stream_dirs.into_iter())
}

#[cfg(feature = "dir-listing")]
fn generate_dir_listing(fs: &FileSystem) -> HashMap<Hash40, Vec<FileNode>> {
    let mut dirs = HashMap::new();

    let mut labels = crate::hash_labels::GLOBAL_LABELS.write();
    for (parent, child) in dir_listing_flat(fs, &mut labels) {
        let listing = dirs.entry(parent).or_insert_with(Vec::new);
        match listing.binary_search(&child) {
            Ok(_) => (),
            Err(insert_point) => listing.insert(insert_point, child)
        }
    }

    dirs
}

impl ArcFile {
    pub fn open<P: AsRef<Path>>(path: P) -> BinResult<Self> {
        Self::from_reader(BufReader::new(File::open(path)?))
    }

    #[cfg(feature = "network")]
    pub fn open_over_network<Addr: ToSocketAddrs>(ip: Addr) -> BinResult<Self> {
        let mut reader = BufReader::new(network_reader::NetworkReader::new(ip)?);

        reader.seek(SeekFrom::Start(0))?;

        Self::from_reader(reader)
    }

    pub fn from_reader<R: SeekRead + 'static>(mut reader: R) -> BinResult<Self> {
        let arc: Self = reader.read_le()?;

        *arc.reader.lock().unwrap() = Box::new(reader);

        Ok(arc)
    }

    #[cfg(feature = "dir-listing")]
    pub fn get_dir_listing<Hash: Into<Hash40>>(&self, hash: Hash) -> Option<&[FileNode]> {
        self.dirs.get(&hash.into()).map(AsRef::as_ref)
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    fn print_tree_hash(arc: &ArcFile, hash: Hash40, depth: usize) {
        for file in arc.get_dir_listing(hash).unwrap() {
            (0..depth).for_each(|_| print!("    "));
            match file {
                FileNode::File(file) => {
                    println!("L {}", file.global_label().unwrap_or_else(|| format!("{:#x}", file.as_u64())));
                }
                FileNode::Dir(dir) => {
                    println!("L {}", dir.global_label().unwrap_or_else(|| format!("{:#x}", dir.as_u64())));
                    //print_tree_hash(arc, *dir, depth + 1);
                }
            }
        }
    }

    fn print_tree(arc: &ArcFile, dir: &str) {
        println!("{}:", dir);
        print_tree_hash(arc, dir.into(), 1);
    }

    #[test]
    fn test_listing() {
        Hash40::set_global_labels_file("/home/jam/Downloads/hashes.txt");
        let arc = ArcFile::open("/home/jam/re/ult/900/data.arc").unwrap();
        //let arc = ArcFile::open_over_network(("192.168.86.32", 43022)).unwrap();

        print_tree(&arc, "/");
        //dbg!(arc.get_dir_listing("fighter/mario/model/body/c00/"));
    }

    #[test]
    fn test_stream_listing() {
        Hash40::set_global_labels_file("/home/jam/Downloads/hashes.txt");
        let arc = ArcFile::open("/home/jam/re/ult/900/data.arc").unwrap();
        //let arc = ArcFile::open_over_network(("192.168.86.32", 43022)).unwrap();

        print_tree(&arc, "stream:");
        //dbg!(arc.get_dir_listing("fighter/mario/model/body/c00/"));
    }
}