1use std::fs;
2use std::path::{Path, PathBuf};
3
4use crate::arc;
5use crate::fighter::Fighter;
6use crate::wii_memory::WiiMemory;
7use crate::wiird_runner;
8
9use anyhow::{Error, bail};
10
11use fancy_slice::FancySlice;
12
13pub struct BrawlMod {
16 brawl_path: PathBuf,
17 mod_path: Option<PathBuf>,
18}
19
20impl BrawlMod {
21 pub fn new(brawl_path: &Path, mod_path: Option<&Path>) -> BrawlMod {
26 BrawlMod {
27 brawl_path: brawl_path.to_path_buf(),
28 mod_path: mod_path.map(|x| x.to_path_buf()),
29 }
30 }
31
32 pub fn load_fighters(&self, single_model: bool) -> Result<Vec<Fighter>, Error> {
36 let brawl_fighter_path = self.brawl_path.join("fighter");
37 let brawl_fighter_dir = match fs::read_dir(&brawl_fighter_path) {
38 Ok(dir) => dir,
39 Err(err) => bail!("Cannot read fighter directory in the brawl dump: {}", err),
40 };
41
42 let mut mod_fighter_dir = None;
43 if let Some(mod_path) = &self.mod_path {
44 let dir_reader = match fs::read_dir(mod_path) {
45 Ok(dir) => dir,
46 Err(err) => bail!("Cannot read brawl mod directory: {}", err),
47 };
48
49 for dir in dir_reader.flatten() {
50 let path = dir.path().join("pf/fighter");
51 if path.exists() {
52 mod_fighter_dir = Some(match fs::read_dir(path) {
53 Ok(dir) => dir,
54 Err(err) => {
55 bail!("Cannot read fighter directory in the brawl mod: {}", err)
56 }
57 });
58 break;
59 }
60 }
61 }
62 if self.mod_path.is_some() && mod_fighter_dir.is_none() {
63 bail!("Missing mod_name/pf/fighter directory");
64 }
65
66 let common_fighter_path = brawl_fighter_path.join("Fighter.pac");
67 let (common_fighter, wii_memory) =
68 if let Ok(mut file_data) = std::fs::read(common_fighter_path) {
69 let wii_memory = if self.mod_path.is_some() {
70 let codeset = self.load_wiird_codeset_raw()?;
71 let sakurai_ram_offset = 0x80F9FC20;
72 let sakurai_fighter_pac_offset = 0x80;
73 let fighter_pac_offset = sakurai_ram_offset - sakurai_fighter_pac_offset;
74
75 wiird_runner::process(&codeset, &mut file_data, fighter_pac_offset)
76 } else {
77 WiiMemory::new()
78 };
79
80 let data = FancySlice::new(&file_data);
81
82 (arc::arc(data, &wii_memory, false), wii_memory)
83 } else {
84 bail!("Missing Fighter.pac");
85 };
86
87 Ok(Fighter::load(
88 brawl_fighter_dir,
89 mod_fighter_dir,
90 &common_fighter,
91 &wii_memory,
92 single_model,
93 ))
94 }
95
96 pub fn load_wiird_codeset_raw(&self) -> Result<Vec<u8>, Error> {
97 struct GCTFile {
104 pub name: String,
105 pub data: Vec<u8>,
106 }
107
108 let mut gct_files: Vec<GCTFile> = vec![];
109 if let Some(mod_path) = &self.mod_path {
110 for entry in fs::read_dir(mod_path).unwrap().flatten() {
111 if entry.path().is_dir() {
112 let child_dir = entry.path();
113 for entry in fs::read_dir(child_dir).unwrap().flatten() {
114 let name = entry.file_name().into_string().unwrap();
115 if name.ends_with(".gct") || name.ends_with(".GCT") {
116 let codeset_path = entry.path();
117 if codeset_path.exists() {
118 match std::fs::read(&codeset_path) {
119 Ok(data) => {
120 if data.len() < 8 {
121 bail!(
122 "Not a WiiRD gct codeset file: File size is less than 8 bytes"
123 );
124 }
125 if let Some(matching_file) =
126 gct_files.iter().find(|x| x.name == name)
127 {
128 assert_eq!(matching_file.data, data);
129 } else {
130 gct_files.push(GCTFile { name, data });
131 }
132 }
133 Err(err) => bail!(
134 "Cannot read WiiRD codeset {:?}: {}",
135 codeset_path,
136 err
137 ),
138 };
139 }
140 }
141 }
142 }
143 }
144 } else {
145 bail!("Not a mod, vanilla brawl does not have a WiiRD codeset.");
146 }
147
148 gct_files.sort_by_key(|x| x.name.clone());
151
152 let mut result = vec![];
153 for gct_file in gct_files {
154 result.extend(&gct_file.data[8..]); }
156 Ok(result)
157 }
158
159 pub fn is_mod(&self) -> bool {
162 self.mod_path.is_some()
163 }
164}