rpgmxp_tool/commands/extract_assets/
file_entry_iter.rs1mod util;
2
3pub use self::util::guess_game_kind_from_exe;
4use crate::GameKind;
5use anyhow::bail;
6use anyhow::ensure;
7use anyhow::Context;
8use camino::Utf8Path;
9use camino::Utf8PathBuf;
10use std::ffi::OsStr;
11use std::fs::File;
12use std::io::BufReader;
13use std::io::Read;
14use std::path::Path;
15use std::path::PathBuf;
16use walkdir::WalkDir;
17
18pub enum FileEntryIter {
20 WalkDir {
21 input_path: PathBuf,
22 iter: walkdir::IntoIter,
23 game_kind: GameKind,
24 },
25 Rgssad {
26 reader: rgssad::Reader<File>,
27 game_kind: GameKind,
28 },
29}
30
31impl FileEntryIter {
32 pub fn new<P>(path: P) -> anyhow::Result<Self>
36 where
37 P: AsRef<Path>,
38 {
39 let path = path.as_ref();
40
41 if !path.is_dir() {
42 return Self::new_rgssad_path(path);
44 }
45
46 let rgssad_path = path.join("Game.rgssad");
47 match File::open(&rgssad_path) {
48 Ok(file) => {
49 return Self::new_rgssad_file(file, GameKind::Xp);
50 }
51 Err(error) if error.kind() == std::io::ErrorKind::NotFound => {}
52 Err(error) => {
53 return Err(error)
54 .with_context(|| format!("failed to open \"{}\"", rgssad_path.display()));
55 }
56 };
57
58 let rgssad_path = path.join("Game.rgss2a");
59 match File::open(&rgssad_path) {
60 Ok(file) => {
61 return Self::new_rgssad_file(file, GameKind::Vx);
62 }
63 Err(error) if error.kind() == std::io::ErrorKind::NotFound => {}
64 Err(error) => {
65 return Err(error)
66 .with_context(|| format!("failed to open \"{}\"", rgssad_path.display()));
67 }
68 };
69
70 ensure!(
71 path.join("Data").exists(),
72 "Data directory is missing. Are you sure the input folder is correct?"
73 );
74 ensure!(
75 path.join("Graphics").exists(),
76 "Graphics directory is missing. Are you sure the input folder is correct?"
77 );
78
79 Self::new_walkdir_path(path)
80 }
81
82 pub fn new_walkdir_path<P>(path: P) -> anyhow::Result<Self>
84 where
85 P: AsRef<Path>,
86 {
87 let path = path.as_ref();
88
89 let game_kind = (|| {
90 let game_path = path.join("Game.exe");
91 let game_exe = std::fs::read(&game_path)
92 .with_context(|| format!("failed to read \"{}\"", game_path.display()))?;
93
94 if let Some(game_kind) =
95 guess_game_kind_from_exe(&game_exe).context("failed to guess game type")?
96 {
97 return Ok(game_kind);
98 }
99
100 bail!("failed to determine game type");
101 })()?;
102
103 let iter = WalkDir::new(path).into_iter();
104
105 Ok(FileEntryIter::WalkDir {
106 input_path: path.into(),
107 iter,
108 game_kind,
109 })
110 }
111
112 pub fn new_rgssad_path<P>(path: P) -> anyhow::Result<Self>
114 where
115 P: AsRef<Path>,
116 {
117 let path = path.as_ref();
118 let extension = path
119 .extension()
120 .context("missing extension")?
121 .to_str()
122 .context("extension is not unicode")?;
123 let game_kind: GameKind = extension.parse()?;
124 let file = File::open(path)
125 .with_context(|| format!("failed to open input file from \"{}\"", path.display()))?;
126 Self::new_rgssad_file(file, game_kind)
127 }
128
129 pub fn new_rgssad_file(file: File, game_kind: GameKind) -> anyhow::Result<Self> {
131 let mut reader = rgssad::Reader::new(file);
132 reader.read_header()?;
133
134 Ok(Self::Rgssad { reader, game_kind })
135 }
136
137 pub fn next_file_entry(&mut self) -> anyhow::Result<Option<FileEntry>> {
139 match self {
140 Self::WalkDir {
141 input_path, iter, ..
142 } => {
143 let entry = loop {
144 let entry = match iter.next() {
145 Some(Ok(entry)) => entry,
146 Some(Err(error)) => return Err(error).context("failed to read dir entry"),
147 None => return Ok(None),
148 };
149
150 if entry.depth() == 1
153 && ![OsStr::new("Data"), OsStr::new("Graphics")]
154 .contains(&entry.file_name())
155 {
156 if entry.file_type().is_dir() {
157 iter.skip_current_dir();
158 }
159 continue;
160 }
161
162 if entry.file_type().is_dir() {
164 continue;
165 }
166
167 break entry;
168 };
169 ensure!(!entry.path_is_symlink());
170
171 let file = File::open(entry.path())?;
172
173 let entry_path = entry.into_path();
174 let relative_path = entry_path.strip_prefix(input_path)?;
175 let relative_path = relative_path
176 .to_str()
177 .context("relative path is not utf8")?;
178
179 Ok(Some(FileEntry::WalkDir {
180 relative_path: relative_path.into(),
181 file: BufReader::new(file),
182 }))
183 }
184 Self::Rgssad { reader, .. } => {
185 let file = match reader.read_file()? {
186 Some(file) => file,
187 None => return Ok(None),
188 };
189
190 Ok(Some(FileEntry::Rgssad { file }))
191 }
192 }
193 }
194
195 pub fn game_kind(&self) -> GameKind {
197 match self {
198 Self::WalkDir { game_kind, .. } => *game_kind,
199 Self::Rgssad { game_kind, .. } => *game_kind,
200 }
201 }
202}
203
204pub enum FileEntry<'a> {
206 WalkDir {
207 relative_path: Utf8PathBuf,
208 file: BufReader<File>,
209 },
210 Rgssad {
211 file: rgssad::reader::File<'a, File>,
212 },
213}
214
215impl FileEntry<'_> {
216 pub fn relative_path(&self) -> &Utf8Path {
218 match self {
219 Self::WalkDir { relative_path, .. } => relative_path,
220 Self::Rgssad { file } => Utf8Path::new(file.name()),
221 }
222 }
223}
224
225impl Read for FileEntry<'_> {
226 fn read(&mut self, buffer: &mut [u8]) -> std::io::Result<usize> {
227 match self {
228 Self::WalkDir { file, .. } => file.read(buffer),
229 Self::Rgssad { file } => file.read(buffer),
230 }
231 }
232}