z_mirror/fmm

git clone https://git.raiguard.me/z_mirror/fmm

raiguardRead mod-settings.dat (causing Rust ICE)216c7c7

rust-ice
5.6 KiB203 linesraw
1#![feature(iter_intersperse)]
2
3use std::fs;
4use std::path::PathBuf;
5
6use anyhow::Result;
7use console::style;
8use reqwest::blocking::Client;
9use semver::Version;
10use structopt::StructOpt;
11
12mod config;
13mod dependency;
14mod directory;
15mod download;
16mod read;
17mod sync;
18mod types;
19
20use config::*;
21use directory::*;
22use types::*;
23
24#[derive(Clone, StructOpt)]
25#[structopt(name = "fmm", about = "Manage your Factorio mods.")]
26pub struct App {
27    /// The path to the configuration file
28    #[structopt(long)]
29    config: Option<PathBuf>,
30    /// Disables all mods in the directory
31    #[structopt(short = "o", long)]
32    disable_all: bool,
33    /// Disables the given mods. Mods are formatted as `Name`
34    #[structopt(short, long)]
35    disable: Vec<ModIdent>,
36    /// Enables all mods in the directory
37    #[structopt(short = "a", long)]
38    enable_all: bool,
39    /// Enables the mods in the given set
40    #[structopt(short = "E", long)]
41    enable_set: Option<String>,
42    /// Enables the given mods. Mods are formatted as `Name` or `Name@Version`
43    #[structopt(short, long)]
44    enable: Vec<ModIdent>,
45    /// The game directory to manipulate. Optional if a configuration file is in use
46    #[structopt(long)]
47    game_dir: Option<PathBuf>,
48    /// Lists all mods in the directory
49    #[structopt(short, long)]
50    list: bool,
51    /// The mods directory to manipulate. Optional if a configuration file is in use
52    #[structopt(long)]
53    mods_dir: Option<PathBuf>,
54    /// Removes the given mods from the mods directory. Mods are formatted as `Name` or `Name@Version`
55    #[structopt(short, long)]
56    remove: Vec<ModIdent>,
57    /// A path to a save file to sync with
58    #[structopt(short, long)]
59    sync: Option<PathBuf>,
60}
61
62// DESIGN NOTES:
63// - Get a list of all mods + versions in the folder _without_ reading the ZIP files (use filenames)
64// - Only read ZIPs if we need to get dependencies or other info
65// - Cache will only be used once we have advanced features that would benefit from it
66
67fn main() -> Result<()> {
68    let mut app = App::from_args();
69    let client = Client::new();
70
71    let config = Config::new(&app)?;
72
73    let mut directory = Directory::new(config.mods_dir.clone())?;
74
75    // List mods
76    if app.list {
77        let mut lines: Vec<String> = directory
78            .mods
79            .iter()
80            .flat_map(|(mod_name, mod_versions)| {
81                mod_versions
82                    .iter()
83                    .map(|version| format!("{} v{}", mod_name, version.version))
84                    .collect::<Vec<String>>()
85            })
86            .collect();
87
88        lines.sort();
89
90        for line in lines {
91            println!("{}", line)
92        }
93    }
94
95    // Combine enable commands
96    let mut combined_enable = vec![];
97    // Enable set
98    if let Some(set_name) = app.enable_set {
99        if let Some(sets) = config.sets.as_ref() {
100            if let Some(set) = sets.get(&set_name) {
101                combined_enable.append(&mut set.to_vec());
102            }
103        }
104    }
105    // Sync with save
106    if let Some(sync_path) = app.sync {
107        let save_file = sync::SaveFile::from(sync_path)?;
108
109        let mut mods = save_file.mods.to_vec();
110
111        if config.sync_latest_versions {
112            for mod_ident in mods.iter_mut() {
113                mod_ident.version_req = None;
114            }
115        }
116
117        combined_enable.append(&mut mods);
118    }
119    // Manually enable
120    combined_enable.append(&mut app.enable.to_vec());
121    app.enable = combined_enable;
122
123    // Remove specified mods
124    for mod_ident in app.remove {
125        if mod_ident.name != "base" {
126            directory.remove(&mod_ident);
127        }
128    }
129
130    // Disable all mods
131    if app.disable_all {
132        directory.disable_all();
133    }
134
135    // Disable specified mods
136    for mod_ident in app.disable {
137        directory.disable(&mod_ident);
138    }
139
140    // Enable all mods
141    if app.enable_all {
142        directory.enable_all();
143    }
144
145    // Enable and/or download specified mods
146    let mut cycle_orders: Vec<ManageOrder> = app
147        .enable
148        .iter()
149        .map(|mod_ident| ManageOrder::Enable(mod_ident.clone()))
150        .collect();
151
152    while !cycle_orders.is_empty() {
153        cycle_orders = cycle_orders
154            .iter_mut()
155            .filter(|order| order.get_name() != "base")
156            .filter_map(|order| match order {
157                ManageOrder::Download(mod_ident) => {
158                    if config.auto_download {
159                        if download::download_mod(mod_ident, &mut directory, &config, &client)
160                            .ok()?
161                        {
162                            Some(vec![ManageOrder::Enable(mod_ident.clone())])
163                        } else {
164                            None
165                        }
166                    } else {
167                        println!("{} {}", style("Did not download").red(), mod_ident.name);
168                        None
169                    }
170                }
171                ManageOrder::Enable(mod_ident) => directory.enable(mod_ident),
172            })
173            .flatten()
174            .collect();
175    }
176
177    // Write mod-list.json
178    fs::write(
179        &directory.mod_list_path,
180        serde_json::to_string_pretty(&ModListJson {
181            mods: directory.mod_list,
182        })?,
183    )?;
184
185    Ok(())
186}
187
188trait HasVersion {
189    fn get_version(&self) -> &Version;
190}
191
192fn get_mod_version<'a, T>(list: &'a [T], mod_ident: &ModIdent) -> Option<&'a T>
193where
194    T: HasVersion,
195{
196    if let Some(version_req) = &mod_ident.version_req {
197        list.iter()
198            .rev()
199            .find(|entry| version_req.matches(entry.get_version()))
200    } else {
201        list.last()
202    }
203}