z_mirror/fmm
git clone https://git.raiguard.me/z_mirror/fmm
216c7c7
rust-ice
1use anyhow:: Result ; 2use console:: style; 3use std:: collections:: HashMap ; 4use std:: ffi:: OsString ; 5use std:: fs; 6use std:: fs::{ DirEntry , File }; 7use std:: io::{ Cursor , Read , Seek , SeekFrom }; 8use std:: path:: PathBuf ; 9 10use semver::{ Version , VersionReq }; 11use zip:: ZipArchive ; 12 13use crate :: dependency:: ModDependencyType ; 14use crate :: get_mod_version; 15use crate :: read:: PropertyTree ; 16use crate :: types:: * ; 17 18pub struct Directory { 19pub mods : HashMap < String , Vec < ModEntry >>, 20pub mod_list : Vec < ModListJsonMod >, 21pub mod_list_path : PathBuf , 22pub startup_settings : PropertyTree , 23} 24 25impl Directory { 26pub fn new ( path : PathBuf ) ->Result < Self > { 27// Get all mods in the directory 28let mod_entries = fs:: read_dir ( & path) ? 29. filter_map ( |entry|{ 30let entry = entry. ok () ?; 31 32if let Some (( mod_name, version)) =parse_file_name ( & entry. file_name ()) { 33Some (( mod_name, ModEntry { entry, version})) 34} else { 35let info_json =read_info_json ( & entry) ?; 36Some (( 37 info_json. name , 38ModEntry { 39 entry, 40version : info_json. version , 41}, 42)) 43} 44}) 45. fold ( HashMap :: new (), |mut directory_mods, ( mod_name, version) |{ 46let versions = directory_mods. entry ( mod_name). or_insert_with ( Vec :: new); 47 48let index = versions 49. binary_search ( & version) 50. unwrap_or_else ( |index| index); 51 versions. insert ( index, version); 52 53 directory_mods 54}); 55 56// Parse mod-list.json 57let mut mlj_path = path. clone (); 58 mlj_path. push ( "mod-list.json" ); 59let enabled_versions = fs:: read_to_string ( & mlj_path) ?; 60let mod_list_json: ModListJson = serde_json:: from_str ( & enabled_versions) ?; 61 62// Parse mod-settings.dat 63let mut settings_path = path; 64 settings_path. push ( "mod-settings.dat" ); 65let mut settings_cursor =Cursor :: new ( fs:: read ( & mlj_path) ?); 66 settings_cursor. seek ( SeekFrom :: Current ( 8 )) ?; 67let startup_settings =PropertyTree :: load ( & mut settings_cursor) ?; 68 69println! ( "{:#?}" , startup_settings); 70 71Ok ( Self { 72mods : mod_entries, 73mod_list : mod_list_json. mods , 74mod_list_path : mlj_path, 75 startup_settings, 76}) 77} 78 79/// Adds the mod, but keeps it disabled 80pub fn add ( & mut self , ( mod_name, mod_entry): ( String , ModEntry )) { 81// Add or disable mod in mod-list.json 82if let Some ( mod_state) =self 83. mod_list 84. iter_mut () 85. find ( |mod_state| mod_name == mod_state. name ) 86{ 87 mod_state. enabled =false ; 88 mod_state. version =None ; 89} else { 90self . mod_list . push ( ModListJsonMod { 91name : mod_name. clone (), 92enabled : false , 93version : None , 94}); 95} 96 97let entries =self . mods . entry ( mod_name). or_default (); 98if let Err ( index) = entries. binary_search ( & mod_entry) { 99 entries. insert ( index, mod_entry); 100} 101} 102 103pub fn disable ( & mut self , mod_ident : & ModIdent ) { 104if mod_ident. name =="base" ||self . mods . contains_key ( & mod_ident. name ) { 105let mod_state =self 106. mod_list 107. iter_mut () 108. find ( |mod_state| mod_ident. name == mod_state. name ); 109 110println! ( "{} {}" , style ( "Disabled" ). yellow (). bold (), & mod_ident); 111 112if let Some ( mod_state) = mod_state{ 113 mod_state. enabled =false ; 114 mod_state. version =None ; 115} 116} else { 117println! ( "Could not find {}" , & mod_ident); 118} 119} 120 121pub fn disable_all ( & mut self ) { 122println! ( "{}" , style ( "Disabled all mods" ). yellow (). bold ()); 123 124for mod_datain self 125. mod_list 126. iter_mut () 127. filter ( |mod_state| mod_state. name !="base" ) 128{ 129 mod_data. enabled =false ; 130 mod_data. version =None ; 131} 132} 133 134pub fn enable ( & mut self , mod_ident : & ModIdent ) ->Option < Vec < ManageOrder >> { 135if let Some ( mod_entry) =self 136. mods 137. get ( & mod_ident. name ) 138. and_then ( |mod_entries|get_mod_version ( mod_entries, mod_ident)) 139{ 140let mod_state =self 141. mod_list 142. iter_mut () 143. find ( |mod_state| mod_ident. name == mod_state. name ); 144 145let enabled = mod_state. is_some () && mod_state. as_ref (). unwrap (). enabled ; 146 147if !enabled{ 148println! ( 149"{} {} v{}" , 150style ( "Enabled" ). green (). bold (), 151 mod_ident. name , 152 mod_entry. version 153); 154 155let version = mod_ident 156. version_req 157. as_ref () 158. map ( |_| mod_entry. version . clone ()); 159 160if let Some ( mod_state) = mod_state{ 161 mod_state. enabled =true ; 162 mod_state. version = version; 163} else { 164self . mod_list . push ( ModListJsonMod { 165name : mod_ident. name . to_string (), 166enabled : true , 167 version, 168}); 169} 170 171return Some ( 172read_info_json ( & mod_entry. entry ) 173. and_then ( |info_json| info_json. dependencies ) 174. unwrap_or_default () 175. iter () 176. filter ( |dependency|{ 177 dependency. name !="base" 178 &&matches! ( 179 dependency. dep_type , 180ModDependencyType :: NoLoadOrder |ModDependencyType :: Required 181) 182}) 183. map ( |dependency|ModIdent { 184name : dependency. name . clone (), 185version_req : dependency. version_req . clone (), 186}) 187. map ( |dependency_ident|{ 188self . mods 189. get ( & dependency_ident. name ) 190. and_then ( |dependency_entries|{ 191get_mod_version ( dependency_entries, & dependency_ident) 192}) 193. map ( |_|ManageOrder :: Enable ( dependency_ident. clone ())) 194. unwrap_or_else ( ||ManageOrder :: Download ( dependency_ident. clone ())) 195}) 196. collect (), 197); 198} 199} else { 200return Some ( vec! [ ManageOrder :: Download ( mod_ident. clone ())]); 201} 202 203None 204} 205 206pub fn enable_all ( & mut self ) { 207println! ( 208"{}" , 209style ( "Enabled latest versions of all mods" ). green (). bold () 210); 211for mod_datain self . mod_list . iter_mut () { 212 mod_data. enabled =true ; 213 mod_data. version =None ; 214} 215} 216 217pub fn remove ( & mut self , mod_ident : & ModIdent ) { 218let version_req = mod_ident 219. version_req 220. as_ref () 221. cloned () 222. unwrap_or_else ( VersionReq :: any); 223if let Some ( mod_versions) =self . mods . get ( & mod_ident. name ) { 224 mod_versions 225. iter () 226. filter ( |version| version_req. matches ( & version. version )) 227. for_each ( |version|{ 228let result = version. entry . metadata (). and_then ( |metadata|{ 229if metadata. is_dir () { 230 fs:: remove_dir_all ( version. entry . path ()) 231} else { 232 fs:: remove_file ( version. entry . path ()) 233} 234}); 235if result. is_ok () { 236println! ( 237"{} {} v{}" , 238style ( "Removed" ). red (). bold (), 239& mod_ident. name , 240 version. version 241); 242} else { 243println! ( "Could not remove {} v{}" , & mod_ident. name , version. version ); 244} 245}); 246self . mods . remove ( & mod_ident. name ); 247} 248 249if let Some (( index, _)) =self 250. mod_list 251. iter () 252. enumerate () 253. find ( |( _, mod_state) | mod_ident. name == mod_state. name ) 254{ 255self . mod_list . remove ( index); 256} 257} 258} 259 260fn parse_file_name ( file_name : & OsString ) ->Option <( String , Version )> { 261let ( name, version) = file_name 262. to_str () ? 263. trim_end_matches ( ".zip" ) 264. rsplit_once ( '_' ) ?; 265 266if let Ok ( version) =Version :: parse ( version) { 267Some (( name. to_string (), version)) 268} else { 269None 270} 271} 272 273fn read_info_json ( entry : & DirEntry ) ->Option < InfoJson > { 274let contents =match ModEntryStructure :: parse ( entry) ?{ 275ModEntryStructure :: Directory |ModEntryStructure :: Symlink =>{ 276let mut path = entry. path (); 277 path. push ( "info.json" ); 278 fs:: read_to_string ( path). ok () ? 279} 280ModEntryStructure :: Zip =>{ 281let mut archive =ZipArchive :: new ( File :: open ( entry. path ()). ok () ?). ok () ?; 282let filename = archive 283. file_names () 284. find ( |name| name. contains ( "info.json" )) 285. map ( ToString :: to_string) ?; 286let mut file = archive. by_name ( & filename). ok () ?; 287let mut contents =String :: new (); 288 file. read_to_string ( & mut contents). ok () ?; 289 contents 290} 291}; 292 293 serde_json:: from_str ::< InfoJson >( & contents). ok () 294}