1use std::cmp::Ordering;
5use std::cmp::Ordering::{Greater, Less};
6use std::path::{Path, PathBuf};
7
8use crate::common::{Platform, get_platform_string, read_version};
9use crate::repository::RepositoryType::{Base, Expansion};
10
11#[derive(Debug, PartialEq, Eq, Copy, Clone)]
13#[repr(C)]
14pub enum RepositoryType {
15 Base,
17 Expansion {
19 number: i32,
21 },
22}
23
24#[derive(Debug, Clone, Eq)]
27pub struct Repository {
28 pub name: String,
30 pub platform: Platform,
32 pub repo_type: RepositoryType,
34 pub version: Option<String>,
36}
37
38impl PartialEq for Repository {
39 fn eq(&self, other: &Self) -> bool {
40 self.name == other.name
41 }
42}
43
44impl Ord for Repository {
45 fn cmp(&self, other: &Self) -> Ordering {
46 match self.repo_type {
48 Base => Less,
49 Expansion { number } => {
50 let super_number = number;
51 match other.repo_type {
52 Base => Greater,
53 Expansion { number } => super_number.cmp(&number),
54 }
55 }
56 }
57 }
58}
59
60impl PartialOrd for Repository {
61 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
62 Some(self.cmp(other))
63 }
64}
65
66#[derive(Debug, PartialEq, Eq, Copy, Clone)]
69pub enum Category {
70 Common = 0x00,
72 BackgroundCommon = 0x01,
74 Background = 0x02,
76 Cutscene = 0x03,
78 Character = 0x04,
80 Shader = 0x05,
82 UI = 0x06,
84 Sound = 0x07,
86 VFX = 0x08,
88 UIScript = 0x09,
90 EXD = 0x0A,
92 GameScript = 0x0B,
94 Music = 0x0C,
96 SqPackTest = 0x12,
98 Debug = 0x13,
100}
101
102pub fn string_to_category(string: &str) -> Option<Category> {
103 use crate::repository::Category::*;
104
105 match string {
106 "common" => Some(Common),
107 "bgcommon" => Some(BackgroundCommon),
108 "bg" => Some(Background),
109 "cut" => Some(Cutscene),
110 "chara" => Some(Character),
111 "shader" => Some(Shader),
112 "ui" => Some(UI),
113 "sound" => Some(Sound),
114 "vfx" => Some(VFX),
115 "ui_script" => Some(UIScript),
116 "exd" => Some(EXD),
117 "game_script" => Some(GameScript),
118 "music" => Some(Music),
119 "sqpack_test" => Some(SqPackTest),
120 "debug" => Some(Debug),
121 _ => None,
122 }
123}
124
125impl Repository {
126 pub fn from_existing_base(platform: Platform, dir: &str) -> Option<Repository> {
129 let path = Path::new(dir);
130 if path.metadata().is_err() {
131 return None;
132 }
133
134 let mut d = PathBuf::from(dir);
135 d.push("ffxivgame.ver");
136
137 let version = read_version(d.as_path());
138 Some(Repository {
139 name: "ffxiv".to_string(),
140 platform,
141 repo_type: Base,
142 version,
143 })
144 }
145
146 pub fn from_existing_expansion(platform: Platform, dir: &str) -> Option<Repository> {
149 let path = Path::new(dir);
150 if path.metadata().is_err() {
151 return None;
152 }
153
154 let name = String::from(path.file_stem()?.to_str()?);
155 let expansion_number = name[2..3].parse().ok()?;
156
157 let mut d = PathBuf::from(dir);
158 d.push(format!("{name}.ver"));
159
160 Some(Repository {
161 name,
162 platform,
163 repo_type: Expansion {
164 number: expansion_number,
165 },
166 version: read_version(d.as_path()),
167 })
168 }
169
170 pub fn index_filename(&self, chunk: u8, category: Category) -> String {
172 format!(
173 "{:02x}{:02}{:02}.{}.index",
174 category as i32,
175 self.expansion(),
176 chunk,
177 get_platform_string(&self.platform)
178 )
179 }
180
181 pub fn index2_filename(&self, chunk: u8, category: Category) -> String {
183 format!("{}2", self.index_filename(chunk, category))
184 }
185
186 pub fn dat_filename(&self, chunk: u8, category: Category, data_file_id: u32) -> String {
188 let expansion = self.expansion();
189 let platform = get_platform_string(&self.platform);
190
191 format!(
192 "{:02x}{expansion:02}{chunk:02}.{platform}.dat{data_file_id}",
193 category as u32
194 )
195 }
196
197 fn expansion(&self) -> i32 {
198 match self.repo_type {
199 Base => 0,
200 Expansion { number } => number,
201 }
202 }
203}
204
205#[cfg(test)]
206mod tests {
207 use crate::common::Platform;
208 use std::path::PathBuf;
209
210 use super::*;
211
212 #[test]
213 fn test_base() {
214 let mut d = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
215 d.push("resources/tests");
216 d.push("ffxiv");
217
218 let repository = Repository::from_existing_base(Platform::Win32, d.to_str().unwrap());
219 assert!(repository.is_some());
220 assert_eq!(repository.unwrap().version.unwrap(), "2012.01.01.0000.0000");
221 }
222
223 #[test]
224 fn test_expansion() {
225 let mut d = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
226 d.push("resources/tests");
227 d.push("ex1");
228
229 let repository = Repository::from_existing_expansion(Platform::Win32, d.to_str().unwrap());
230 assert!(repository.is_some());
231 assert_eq!(repository.unwrap().version.unwrap(), "2012.01.01.0000.0000");
232 }
233
234 #[test]
235 fn test_win32_filenames() {
236 let repo = Repository {
237 name: "ffxiv".to_string(),
238 platform: Platform::Win32,
239 repo_type: RepositoryType::Base,
240 version: None,
241 };
242
243 assert_eq!(
244 repo.index_filename(0, Category::Music),
245 "0c0000.win32.index"
246 );
247 assert_eq!(
248 repo.index2_filename(0, Category::Music),
249 "0c0000.win32.index2"
250 );
251 assert_eq!(
252 repo.dat_filename(0, Category::GameScript, 1),
253 "0b0000.win32.dat1"
254 );
255 }
256
257 #[test]
258 fn test_ps3_filenames() {
259 let repo = Repository {
260 name: "ffxiv".to_string(),
261 platform: Platform::PS3,
262 repo_type: RepositoryType::Base,
263 version: None,
264 };
265
266 assert_eq!(repo.index_filename(0, Category::Music), "0c0000.ps3.index");
267 assert_eq!(
268 repo.index2_filename(0, Category::Music),
269 "0c0000.ps3.index2"
270 );
271 assert_eq!(
272 repo.dat_filename(0, Category::GameScript, 1),
273 "0b0000.ps3.dat1"
274 );
275 }
276
277 #[test]
278 fn test_ps4_filenames() {
279 let repo = Repository {
280 name: "ffxiv".to_string(),
281 platform: Platform::PS4,
282 repo_type: RepositoryType::Base,
283 version: None,
284 };
285
286 assert_eq!(repo.index_filename(0, Category::Music), "0c0000.ps4.index");
287 assert_eq!(
288 repo.index2_filename(0, Category::Music),
289 "0c0000.ps4.index2"
290 );
291 assert_eq!(
292 repo.dat_filename(0, Category::GameScript, 1),
293 "0b0000.ps4.dat1"
294 );
295 }
296}