diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f0e3bca --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/target +**/*.rs.bk \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..318dd19 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,16 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "stellaris_modding_tools" +version = "0.0.1" +dependencies = [ + "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[metadata] +"checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..d618e14 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "stellaris_modding_tools" +version = "0.0.1" +authors = ["Dorian Klimowicz "] +edition = "2018" + +[dependencies] +lazy_static = "1.4.0" \ No newline at end of file diff --git a/src/Cfs.rs b/src/Cfs.rs new file mode 100644 index 0000000..d115b08 --- /dev/null +++ b/src/Cfs.rs @@ -0,0 +1,656 @@ +use std::fs; +use std::error::Error; + +pub struct Folder { + name: String, + subfolders: Option>, +} + +// Cannot be done on the stack until this issue (https://github.com/rust-lang/rust/issues/44580) is resolved +// Because Rust still hasn't implemented const generics in future something like this +// struct RectangularArray { +// array: [[T; WIDTH]; HEIGHT], +// } +// Should be possible, until then it HAS to be in Vectors even though we know size of an array +// at compile time +// If someone is able to remedy this, so it can live on stack, I'd glady incorporate this here. +// Well now I know the size of it, but it'll change when I add functionalty, won't it? +pub fn populate_folder_structure<'main> (folder_structure: &mut std::vec::Vec){ + folder_structure.push( + Folder{name: "common".to_owned(), subfolders: + Some( + vec![ + Folder {name: "agendas".to_owned(), subfolders: None}, + Folder {name: "ai_budget".to_owned(), subfolders: None}, + Folder {name: "ambient_objects".to_owned(), subfolders: None}, + Folder {name: "anomalies".to_owned(), subfolders: None}, + Folder {name: "armies".to_owned(), subfolders: None}, + Folder {name: "ascension_perks".to_owned(), subfolders: None}, + Folder {name: "asteroid_belts".to_owned(), subfolders: None}, + Folder {name: "attitudes".to_owned(), subfolders: None}, + Folder {name: "bombardment_stances".to_owned(), subfolders: None}, + Folder {name: "buildings".to_owned(), subfolders: None}, + Folder {name: "button_effects".to_owned(), subfolders: None}, + Folder {name: "bypass".to_owned(), subfolders: None}, + Folder {name: "casus_belli".to_owned(), subfolders: None}, + Folder {name: "colony_types".to_owned(), subfolders: None}, + Folder {name: "colors".to_owned(), subfolders: None}, + Folder {name: "component_sets".to_owned(), subfolders: None}, + Folder {name: "component_tags".to_owned(), subfolders: None}, + Folder {name: "component_templates".to_owned(), subfolders: None}, + Folder {name: "country_customization".to_owned(), subfolders: None}, + Folder {name: "country_types".to_owned(), subfolders: None}, + Folder {name: "decisions".to_owned(), subfolders: None}, + Folder {name: "defines".to_owned(), subfolders: None}, + Folder {name: "deposits".to_owned(), subfolders: None}, + Folder {name: "deposit_categories".to_owned(), subfolders: None}, + Folder {name: "diplomacy_economy".to_owned(), subfolders: None}, + Folder {name: "diplomatic_actions".to_owned(), subfolders: None}, + Folder {name: "diplo_phrases".to_owned(), subfolders: None}, + Folder {name: "districts".to_owned(), subfolders: None}, + Folder {name: "economic_categories".to_owned(), subfolders: None}, + Folder {name: "edicts".to_owned(), subfolders: None}, + Folder {name: "ethics".to_owned(), subfolders: None}, + Folder {name: "event_chains".to_owned(), subfolders: None}, + Folder {name: "fallen_empires".to_owned(), subfolders: None}, + Folder {name: "game_rules".to_owned(), subfolders: None}, + Folder {name: "global_ship_designs".to_owned(), subfolders: None}, + Folder {name: "graphical_culture".to_owned(), subfolders: None}, + Folder {name: "leader_classes".to_owned(), subfolders: None}, + Folder {name: "mandates".to_owned(), subfolders: None}, + Folder {name: "map_modes".to_owned(), subfolders: None}, + Folder {name: "megastructures".to_owned(), subfolders: None}, + Folder {name: "name_lists".to_owned(), subfolders: None}, + Folder {name: "notification_modifiers".to_owned(), subfolders: None}, + Folder {name: "observation_station_missions".to_owned(), subfolders: None}, + Folder {name: "on_actions".to_owned(), subfolders: None}, + Folder {name: "opinion_modifiers".to_owned(), subfolders: None}, + Folder {name: "personalities".to_owned(), subfolders: None}, + Folder {name: "planet_classes".to_owned(), subfolders: None}, + Folder {name: "planet_modifiers".to_owned(), subfolders: None}, + Folder {name: "policies".to_owned(), subfolders: None}, + Folder {name: "pop_categories".to_owned(), subfolders: None}, + Folder {name: "pop_faction_types".to_owned(), subfolders: None}, + Folder {name: "pop_jobs".to_owned(), subfolders: None}, + Folder {name: "precursor_civilizations".to_owned(), subfolders: None}, + Folder {name: "scripted_effects".to_owned(), subfolders: None}, + Folder {name: "scripted_loc".to_owned(), subfolders: None}, + Folder {name: "scripted_triggers".to_owned(), subfolders: None}, + Folder {name: "scripted_variables".to_owned(), subfolders: None}, + Folder {name: "section_templates".to_owned(), subfolders: None}, + Folder {name: "sector_focuses".to_owned(), subfolders: None}, + Folder {name: "sector_types".to_owned(), subfolders: None}, + Folder {name: "ship_behaviors".to_owned(), subfolders: None}, + Folder {name: "ship_sizes".to_owned(), subfolders: None}, + Folder {name: "solar_system_initializers".to_owned(), subfolders: None}, + Folder {name: "special_projects".to_owned(), subfolders: None}, + Folder {name: "species_archetypes".to_owned(), subfolders: None}, + Folder {name: "species_classes".to_owned(), subfolders: None}, + Folder {name: "species_names".to_owned(), subfolders: None}, + Folder {name: "species_rights".to_owned(), subfolders: None}, + Folder {name: "starbase_buildings".to_owned(), subfolders: None}, + Folder {name: "starbase_levels".to_owned(), subfolders: None}, + Folder {name: "starbase_modules".to_owned(), subfolders: None}, + Folder {name: "starbase_types".to_owned(), subfolders: None}, + Folder {name: "start_screen_messages".to_owned(), subfolders: None}, + Folder {name: "star_classes".to_owned(), subfolders: None}, + Folder {name: "static_modifiers".to_owned(), subfolders: None}, + Folder {name: "strategic_resources".to_owned(), subfolders: None}, + Folder {name: "subjects".to_owned(), subfolders: None}, + Folder {name: "system_types".to_owned(), subfolders: None}, + Folder {name: "terraform".to_owned(), subfolders: None}, + Folder {name: "trade_conversions".to_owned(), subfolders: None}, + Folder {name: "traditions".to_owned(), subfolders: None}, + Folder {name: "tradition_categories".to_owned(), subfolders: None}, + Folder {name: "traits".to_owned(), subfolders: None}, + Folder {name: "war_goals".to_owned(), subfolders: None}, + Folder {name: "technology".to_owned(), subfolders: + Some( + vec![ + Folder {name: "category".to_owned(), subfolders: None}, + Folder {name: "tier".to_owned(), subfolders: None}, + ] + ) + }, + Folder {name: "random_names".to_owned(), subfolders: + Some( + vec![ + Folder {name: "base".to_owned(), subfolders: None}, + ] + ) + }, + Folder {name: "governments".to_owned(), subfolders: + Some( + vec![ + Folder {name: "authorities".to_owned(), subfolders: None}, + Folder {name: "civics".to_owned(), subfolders: None}, + ] + ) + }, + ] + ) + } + ); + folder_structure.push( + Folder {name: "flags".to_owned(), subfolders: Some( + vec![ + Folder {name: "backgrounds".to_owned(), subfolders: None}, + Folder {name: "blocky".to_owned(), subfolders: Some( + vec![ + Folder {name: "map".to_owned(), subfolders: None}, + Folder {name: "small".to_owned(), subfolders: None}, + ] + )}, + Folder {name: "corporate".to_owned(), subfolders: Some( + vec![ + Folder {name: "map".to_owned(), subfolders: None}, + Folder {name: "small".to_owned(), subfolders: None}, + ] + )}, + Folder {name: "domination".to_owned(), subfolders: Some( + vec![ + Folder {name: "map".to_owned(), subfolders: None}, + Folder {name: "small".to_owned(), subfolders: None}, + ] + )}, + Folder {name: "enclaves".to_owned(), subfolders: Some( + vec![ + Folder {name: "map".to_owned(), subfolders: None}, + Folder {name: "small".to_owned(), subfolders: None}, + ] + )}, + Folder {name: "human".to_owned(), subfolders: Some( + vec![ + Folder {name: "map".to_owned(), subfolders: None}, + Folder {name: "small".to_owned(), subfolders: None}, + ] + )}, + Folder {name: "ornate".to_owned(), subfolders: Some( + vec![ + Folder {name: "map".to_owned(), subfolders: None}, + Folder {name: "small".to_owned(), subfolders: None}, + ] + )}, + Folder {name: "paradox".to_owned(), subfolders: Some( + vec![ + Folder {name: "map".to_owned(), subfolders: None}, + Folder {name: "small".to_owned(), subfolders: None}, + ] + )}, + Folder {name: "pirate".to_owned(), subfolders: Some( + vec![ + Folder {name: "map".to_owned(), subfolders: None}, + Folder {name: "small".to_owned(), subfolders: None}, + ] + )}, + Folder {name: "pointy".to_owned(), subfolders: Some( + vec![ + Folder {name: "map".to_owned(), subfolders: None}, + Folder {name: "small".to_owned(), subfolders: None}, + ] + )}, + Folder {name: "special".to_owned(), subfolders: Some( + vec![ + Folder {name: "map".to_owned(), subfolders: None}, + Folder {name: "small".to_owned(), subfolders: None}, + ] + )}, + Folder {name: "spherical".to_owned(), subfolders: Some( + vec![ + Folder {name: "map".to_owned(), subfolders: None}, + Folder {name: "small".to_owned(), subfolders: None}, + ] + )}, + Folder {name: "zoological".to_owned(), subfolders: Some( + vec![ + Folder {name: "map".to_owned(), subfolders: None}, + Folder {name: "small".to_owned(), subfolders: None}, + ] + )}, + ] + )} + ); + folder_structure.push( + Folder {name: "gfx".to_owned(), subfolders: Some( + vec![ + Folder {name: "advisorwindow".to_owned(), subfolders: None}, + Folder {name: "arrows".to_owned(), subfolders: None}, + Folder {name: "cursors".to_owned(), subfolders: None}, + Folder {name: "event_pictures".to_owned(), subfolders: None}, + Folder {name: "fonts".to_owned(), subfolders: Some( + vec![ + Folder {name: "arimo".to_owned(), subfolders: None}, + ] + )}, + Folder {name: "FX".to_owned(), subfolders: None}, + Folder {name: "interface".to_owned(), subfolders: Some( + vec![ + Folder {name: "anomaly".to_owned(), subfolders: None}, + Folder {name: "archaeology".to_owned(), subfolders: Some( + vec![ + Folder {name: "runes".to_owned(), subfolders: None}, + ] + )}, + Folder {name: "buttons".to_owned(), subfolders: Some( + vec![ + Folder {name: "gamepad".to_owned(), subfolders: None}, + ] + )}, + Folder {name: "diplomacy".to_owned(), subfolders: None}, + Folder {name: "elections".to_owned(), subfolders: None}, + Folder {name: "event_window".to_owned(), subfolders: None}, + Folder {name: "flags".to_owned(), subfolders: None}, + Folder {name: "fleet_view".to_owned(), subfolders: None}, + Folder {name: "frontend".to_owned(), subfolders: None}, + Folder {name: "government_mod_window".to_owned(), subfolders: None}, + Folder {name: "icons".to_owned(), subfolders: Some( + vec![ + Folder {name: "achievements".to_owned(), subfolders: None}, + Folder {name: "advisor_vo".to_owned(), subfolders: None}, + Folder {name: "army_attachments".to_owned(), subfolders: None}, + Folder {name: "ascension_perks".to_owned(), subfolders: None}, + Folder {name: "buildings".to_owned(), subfolders: Some( + vec![ + Folder {name: "old_production".to_owned(), subfolders: None}, + Folder {name: "unused".to_owned(), subfolders: None}, + ] + )}, + Folder {name: "combat_stats".to_owned(), subfolders: None}, + Folder {name: "controller".to_owned(), subfolders: None}, + Folder {name: "decisions".to_owned(), subfolders: None}, + Folder {name: "deposits".to_owned(), subfolders: Some( + vec![ + Folder {name: "old".to_owned(), subfolders: None}, + Folder {name: "unused".to_owned(), subfolders: None}, + ] + )}, + Folder {name: "diplomacy".to_owned(), subfolders: None}, + Folder {name: "districts".to_owned(), subfolders: Some( + vec![ + Folder {name: "grid_box".to_owned(), subfolders: None}, + ] + )}, + Folder {name: "dlc".to_owned(), subfolders: None}, + Folder {name: "ethics".to_owned(), subfolders: None}, + Folder {name: "faction_icons".to_owned(), subfolders: None}, + Folder {name: "governments".to_owned(), subfolders: Some( + vec![ + Folder {name: "authorities".to_owned(), subfolders: None}, + Folder {name: "civics".to_owned(), subfolders: None}, + ] + )}, + Folder {name: "jobs".to_owned(), subfolders: None}, + Folder {name: "message".to_owned(), subfolders: None}, + Folder {name: "modifiers".to_owned(), subfolders: None}, + Folder {name: "modules".to_owned(), subfolders: None}, + Folder {name: "planet_backgrounds".to_owned(), subfolders: Some( + vec![ + Folder {name: "old".to_owned(), subfolders: None}, + ] + )}, + Folder {name: "planet_modifiers".to_owned(), subfolders: None}, + Folder {name: "pop_categories".to_owned(), subfolders: None}, + Folder {name: "relics".to_owned(), subfolders: None}, + Folder {name: "resources".to_owned(), subfolders: Some( + vec![ + Folder {name: "old_unused".to_owned(), subfolders: None}, + ] + )}, + Folder {name: "ship_parts".to_owned(), subfolders: Some( + vec![ + Folder {name: "computers".to_owned(), subfolders: None}, + ] + )}, + Folder {name: "ship_stats".to_owned(), subfolders: None}, + Folder {name: "situation_log".to_owned(), subfolders: None}, + Folder {name: "technologies".to_owned(), subfolders: Some( + vec![ + Folder {name: "old_tech_icons".to_owned(), subfolders: None}, + Folder {name: "tech_templates".to_owned(), subfolders: None}, + ] + )}, + Folder {name: "text_icons".to_owned(), subfolders: None}, + Folder {name: "trade route icons".to_owned(), subfolders: None}, + Folder {name: "trade_icons".to_owned(), subfolders: None}, + Folder {name: "traditions".to_owned(), subfolders: Some( + vec![ + Folder {name: "unused".to_owned(), subfolders: None}, + ] + )}, + Folder {name: "traits".to_owned(), subfolders: Some( + vec![ + Folder {name: "leader_traits".to_owned(), subfolders: None}, + ] + )}, + ] + )}, + Folder {name: "main".to_owned(), subfolders: Some( + vec![ + Folder {name: "control_group".to_owned(), subfolders: None}, + ] + )}, + Folder {name: "market".to_owned(), subfolders: None}, + Folder {name: "masks".to_owned(), subfolders: None}, + Folder {name: "multiplayer".to_owned(), subfolders: None}, + Folder {name: "musicplayer".to_owned(), subfolders: None}, + Folder {name: "outliner".to_owned(), subfolders: None}, + Folder {name: "planetview".to_owned(), subfolders: None}, + Folder {name: "progressbars".to_owned(), subfolders: None}, + Folder {name: "relics".to_owned(), subfolders: None}, + Folder {name: "ship_designer".to_owned(), subfolders: None}, + Folder {name: "situation_log".to_owned(), subfolders: None}, + Folder {name: "sliders".to_owned(), subfolders: None}, + Folder {name: "system".to_owned(), subfolders: None}, + Folder {name: "tech_view".to_owned(), subfolders: None}, + Folder {name: "tiles".to_owned(), subfolders: None}, + Folder {name: "topbar".to_owned(), subfolders: None}, + Folder {name: "traditions".to_owned(), subfolders: None}, + Folder {name: "waroverview".to_owned(), subfolders: None}, + ] + )}, + Folder {name: "keyicons".to_owned(), subfolders: None}, + Folder {name: "lights".to_owned(), subfolders: None}, + Folder {name: "loadingscreens".to_owned(), subfolders: None}, + Folder {name: "map".to_owned(), subfolders: Some( + vec![ + Folder {name: "star_classes".to_owned(), subfolders: None}, + ] + )}, + Folder {name: "models".to_owned(), subfolders: Some( + vec![ + Folder {name: "add_ons".to_owned(), subfolders: None}, + Folder {name: "combat_items".to_owned(), subfolders: None}, + Folder {name: "galaxy_map".to_owned(), subfolders: None}, + Folder {name: "planets".to_owned(), subfolders: Some( + vec![ + Folder {name: "apocalypse_planet_effects".to_owned(), subfolders: None}, + Folder {name: "distant_stars_planets".to_owned(), subfolders: None}, + ] + )}, + Folder {name: "portraits".to_owned(), subfolders: Some( + vec![ + Folder {name: "advisor".to_owned(), subfolders: None}, + Folder {name: "AI".to_owned(), subfolders: None}, + Folder {name: "ancient_relics".to_owned(), subfolders: None}, + Folder {name: "arthropoid".to_owned(), subfolders: None}, + Folder {name: "avian".to_owned(), subfolders: None}, + Folder {name: "distant_stars".to_owned(), subfolders: None}, + Folder {name: "extradimensional".to_owned(), subfolders: None}, + Folder {name: "fungoid".to_owned(), subfolders: None}, + Folder {name: "human".to_owned(), subfolders: Some( + vec![ + Folder {name: "human_female_hair_update".to_owned(), subfolders: None}, + Folder {name: "human_male_hair_update".to_owned(), subfolders: None}, + ] + )}, + Folder {name: "humanoid".to_owned(), subfolders: Some( + vec![ + Folder {name: "humanoid_02_hair_update".to_owned(), subfolders: None}, + Folder {name: "humanoid_05_female_hair_update".to_owned(), subfolders: None}, + Folder {name: "humanoid_05_male_hair_update".to_owned(), subfolders: None}, + Folder {name: "humanoid_05_female_hair_update".to_owned(), subfolders: None}, + Folder {name: "humanoid_hp_02_female_hair_update".to_owned(), subfolders: None}, + Folder {name: "humanoid_hp_02_male_beard_update".to_owned(), subfolders: None}, + Folder {name: "humanoid_hp_11_female_hair_update".to_owned(), subfolders: None}, + Folder {name: "humanoid_hp_11_male_hair_update".to_owned(), subfolders: None}, + Folder {name: "humanoid_hp_12_female_hair_update".to_owned(), subfolders: None}, + Folder {name: "humanoid_hp_12_male_hair_update".to_owned(), subfolders: None}, + ] + )}, + Folder {name: "mammalian".to_owned(), subfolders: None}, + Folder {name: "molluscoid".to_owned(), subfolders: None}, + Folder {name: "plantoid".to_owned(), subfolders: None}, + Folder {name: "portrait_items".to_owned(), subfolders: None}, + Folder {name: "reptilian".to_owned(), subfolders: None}, + Folder {name: "shroud".to_owned(), subfolders: None}, + Folder {name: "swarm".to_owned(), subfolders: None}, + Folder {name: "synthetic_dawn".to_owned(), subfolders: None}, + ] + )}, + Folder {name: "ships".to_owned(), subfolders: Some( + vec![ + Folder {name: "ai_01".to_owned(), subfolders: None}, + Folder {name: "arthropoid_01".to_owned(), subfolders: None}, + Folder {name: "avian_01".to_owned(), subfolders: None}, + Folder {name: "caravaneer_01".to_owned(), subfolders: None}, + Folder {name: "colossus".to_owned(), subfolders: Some( + vec![ + Folder {name: "arthropoid_01".to_owned(), subfolders: None}, + Folder {name: "avian_01".to_owned(), subfolders: None}, + Folder {name: "fallen_empire_01".to_owned(), subfolders: None}, + Folder {name: "fungoid_01".to_owned(), subfolders: None}, + Folder {name: "humanoid_01".to_owned(), subfolders: None}, + Folder {name: "mammalian_01".to_owned(), subfolders: None}, + Folder {name: "molluscoid_01".to_owned(), subfolders: None}, + Folder {name: "plantoid_01".to_owned(), subfolders: None}, + Folder {name: "reptilian_01".to_owned(), subfolders: None}, + ] + )}, + Folder {name: "extra_dimensional_01".to_owned(), subfolders: None}, + Folder {name: "fallen_empire_01".to_owned(), subfolders: None}, + Folder {name: "fallen_machine_empire_01".to_owned(), subfolders: None}, + Folder {name: "fungoid_01".to_owned(), subfolders: None}, + Folder {name: "gatebuilder_01".to_owned(), subfolders: None}, + Folder {name: "guardians".to_owned(), subfolders: Some( + vec![ + Folder {name: "ancient_station".to_owned(), subfolders: None}, + Folder {name: "dimensional_horror".to_owned(), subfolders: None}, + Folder {name: "dreadnought".to_owned(), subfolders: None}, + Folder {name: "drone_homebase".to_owned(), subfolders: None}, + Folder {name: "elder_tiyanki".to_owned(), subfolders: None}, + Folder {name: "hive_asteroids".to_owned(), subfolders: None}, + Folder {name: "pirate_galleon".to_owned(), subfolders: None}, + Folder {name: "scavenger_bot".to_owned(), subfolders: None}, + Folder {name: "space_dragon".to_owned(), subfolders: None}, + Folder {name: "stellarite".to_owned(), subfolders: None}, + Folder {name: "technosphere".to_owned(), subfolders: None}, + Folder {name: "voidspawn".to_owned(), subfolders: None}, + Folder {name: "wraith".to_owned(), subfolders: None}, + ] + )}, + Folder {name: "humanoid_01".to_owned(), subfolders: None}, + Folder {name: "mammalian_01".to_owned(), subfolders: None}, + Folder {name: "megastructures".to_owned(), subfolders: Some( + vec![ + Folder {name: "construction_platform".to_owned(), subfolders: None}, + Folder {name: "dyson_sphere".to_owned(), subfolders: None}, + Folder {name: "gateway".to_owned(), subfolders: None}, + Folder {name: "interstellar_assembly".to_owned(), subfolders: None}, + Folder {name: "matter_decompressor".to_owned(), subfolders: None}, + Folder {name: "mega_art_institution".to_owned(), subfolders: None}, + Folder {name: "spy_orb".to_owned(), subfolders: None}, + Folder {name: "strategic_coordination_center".to_owned(), subfolders: None}, + Folder {name: "think_tank".to_owned(), subfolders: None}, + ] + )}, + Folder {name: "molluscoid_01".to_owned(), subfolders: None}, + Folder {name: "other".to_owned(), subfolders: None}, + Folder {name: "pirate_01".to_owned(), subfolders: None}, + Folder {name: "plantoid_01".to_owned(), subfolders: None}, + Folder {name: "reptilian_01".to_owned(), subfolders: None}, + Folder {name: "shroud_01".to_owned(), subfolders: None}, + Folder {name: "starbases".to_owned(), subfolders: None}, + Folder {name: "swarm_01".to_owned(), subfolders: None}, + Folder {name: "titans".to_owned(), subfolders: Some( + vec![ + Folder {name: "arthropoid_01".to_owned(), subfolders: None}, + Folder {name: "avian_01".to_owned(), subfolders: None}, + Folder {name: "fungoid_01".to_owned(), subfolders: None}, + Folder {name: "humanoid_01".to_owned(), subfolders: None}, + Folder {name: "mammalian_01".to_owned(), subfolders: None}, + Folder {name: "molluscoid_01".to_owned(), subfolders: None}, + Folder {name: "plantoid_01".to_owned(), subfolders: None}, + Folder {name: "reptilian_01".to_owned(), subfolders: None}, + ] + )}, + ] + )}, + Folder {name: "system_map".to_owned(), subfolders: None}, + Folder {name: "ui".to_owned(), subfolders: None}, + ] + )}, + Folder {name: "particles".to_owned(), subfolders: Some( + vec![ + Folder {name: "combat".to_owned(), subfolders: None}, + Folder {name: "misc".to_owned(), subfolders: None}, + Folder {name: "ships".to_owned(), subfolders: None}, + Folder {name: "stars_and_planets".to_owned(), subfolders: None}, + ] + )}, + Folder {name: "pingmap".to_owned(), subfolders: None}, + Folder {name: "portraits".to_owned(), subfolders: Some( + vec![ + Folder {name: "asset_selectors".to_owned(), subfolders: None}, + Folder {name: "city_sets".to_owned(), subfolders: None}, + Folder {name: "environments".to_owned(), subfolders: None}, + Folder {name: "leaders".to_owned(), subfolders: None}, + Folder {name: "misc".to_owned(), subfolders: None}, + Folder {name: "portraits".to_owned(), subfolders: None}, + ] + )}, + Folder {name: "projectiles".to_owned(), subfolders: Some( + vec![ + Folder {name: "planet_destruction".to_owned(), subfolders: None}, + ] + )}, + Folder {name: "shipview".to_owned(), subfolders: None}, + Folder {name: "ugc".to_owned(), subfolders: None}, + Folder {name: "worldgfx".to_owned(), subfolders: None}, + ] + )} + ); + folder_structure.push( + Folder {name: "interface".to_owned(), subfolders: Some( + vec![ + Folder {name: "game_setup".to_owned(), subfolders: None}, + Folder {name: "pdx_online".to_owned(), subfolders: None}, + Folder {name: "portraits".to_owned(), subfolders: None}, + Folder {name: "resource_groups".to_owned(), subfolders: None}, + ] + )} + ); + folder_structure.push( + Folder {name: "prescripted_countries".to_owned(), subfolders: None}, + ); + folder_structure.push( + Folder {name: "sound".to_owned(), subfolders: Some( + vec![ + Folder {name: "advisor_voice_types".to_owned(), subfolders: None}, + Folder {name: "ambient".to_owned(), subfolders: None}, + Folder {name: "ancient_relics".to_owned(), subfolders: None}, + Folder {name: "apocalypse".to_owned(), subfolders: None}, + Folder {name: "audiofiles".to_owned(), subfolders: None}, + Folder {name: "distant_stars".to_owned(), subfolders: None}, + Folder {name: "event".to_owned(), subfolders: None}, + Folder {name: "guardians".to_owned(), subfolders: None}, + Folder {name: "gui".to_owned(), subfolders: None}, + Folder {name: "humanoids".to_owned(), subfolders: None}, + Folder {name: "megacorp".to_owned(), subfolders: None}, + Folder {name: "other_entities".to_owned(), subfolders: None}, + Folder {name: "placeholders".to_owned(), subfolders: None}, + Folder {name: "portrait".to_owned(), subfolders: None}, + Folder {name: "ship_designer".to_owned(), subfolders: None}, + Folder {name: "synthetic_dawn".to_owned(), subfolders: None}, + Folder {name: "ui".to_owned(), subfolders: None}, + Folder {name: "units".to_owned(), subfolders: None}, + Folder {name: "utopia".to_owned(), subfolders: None}, + Folder {name: "vo".to_owned(), subfolders: Some( + vec![ + Folder {name: "bajen".to_owned(), subfolders: None}, + Folder {name: "ethics".to_owned(), subfolders: Some( + vec![ + Folder {name: "authoritarian".to_owned(), subfolders: None}, + Folder {name: "egalitarian".to_owned(), subfolders: None}, + Folder {name: "hivemind".to_owned(), subfolders: None}, + Folder {name: "machine_empire".to_owned(), subfolders: None}, + Folder {name: "materialist".to_owned(), subfolders: None}, + Folder {name: "militarist".to_owned(), subfolders: None}, + Folder {name: "pacifist".to_owned(), subfolders: None}, + Folder {name: "spiritualist".to_owned(), subfolders: None}, + Folder {name: "xenophile".to_owned(), subfolders: None}, + Folder {name: "xenophobe".to_owned(), subfolders: None}, + ] + )}, + Folder {name: "event".to_owned(), subfolders: None}, + Folder {name: "generic".to_owned(), subfolders: None}, + Folder {name: "notifications".to_owned(), subfolders: None}, + Folder {name: "phenotypes".to_owned(), subfolders: Some( + vec![ + Folder {name: "arthopoid".to_owned(), subfolders: None}, + Folder {name: "avian".to_owned(), subfolders: None}, + Folder {name: "fungoid".to_owned(), subfolders: None}, + Folder {name: "human".to_owned(), subfolders: None}, + Folder {name: "humanoid".to_owned(), subfolders: None}, + Folder {name: "mammalian".to_owned(), subfolders: None}, + Folder {name: "molluscoid".to_owned(), subfolders: None}, + Folder {name: "reptilian".to_owned(), subfolders: None}, + ] + )}, + Folder {name: "respons".to_owned(), subfolders: None}, + Folder {name: "tutorial".to_owned(), subfolders: None}, + ] + )}, + Folder {name: "weapons".to_owned(), subfolders: None}, + ] + )}, + ); + folder_structure.push( + Folder {name: "localisation_synced".to_owned(), subfolders: None} + ); + folder_structure.push( + Folder {name: "localisation".to_owned(), subfolders: Some(vec![ + Folder {name: "braz_por".to_owned(), subfolders: None}, + Folder {name: "english".to_owned(), subfolders: None}, + Folder {name: "french".to_owned(), subfolders: None}, + Folder {name: "german".to_owned(), subfolders: None}, + Folder {name: "polish".to_owned(), subfolders: None}, + Folder {name: "russian".to_owned(), subfolders: None}, + Folder {name: "simp_chinese".to_owned(), subfolders: None}, + Folder {name: "spanish".to_owned(), subfolders: None}, + ])} + ); +} + +pub fn make_subfolders(parent_folder_name: &String, subfolder: &std::vec::Vec, errors: &mut std::vec::Vec){ + for next_level_folder in subfolder.iter() { + let next_level_name = format!("{}\\{}", &parent_folder_name, next_level_folder.name); + match fs::create_dir(&next_level_name) { + Ok(()) => (), + Err(err) => folder_error_handling(err, &next_level_name, errors), + }; + match &next_level_folder.subfolders { + None => continue, + Some(subfolder) => make_subfolders(&next_level_name, subfolder,errors), + } + } +} + +pub fn make_folder_structure(folder_structure: &std::vec::Vec, root_dir: &String, errors: &mut std::vec::Vec) { + match fs::create_dir_all(&root_dir) { + Ok(()) => (), + Err(err) => folder_error_handling(err, &"Destination to root".to_owned(), errors) + } + for root_folder in folder_structure.iter() { + let root_folder_name = format!("{}\\{}", root_dir, root_folder.name); + match fs::create_dir(&root_folder_name) { + Ok(()) => (), + Err(err) => folder_error_handling(err, &"Destination to root".to_owned(), errors) + } + match &root_folder.subfolders { + None => continue, + Some(first_folder) => make_subfolders(&root_folder_name, &first_folder, errors), + } + } +} + +fn folder_error_handling (file_error: std::io::Error, maybe_usefull: &String, errors: &mut std::vec::Vec){ + if let Some(raw_os_err) = file_error.raw_os_error() { + if raw_os_err == 80 || raw_os_err == 183 { + return + } + return errors.push(format!("{} OS gave such error {} - {}", maybe_usefull, raw_os_err, file_error.description())) + } else { + return errors.push(format!("The error while creating folder {} came not from OS, unable to help.", maybe_usefull)) + } + // panic!("The error while creating folder was not from OS which is super weird(Maybe usefull info: {}): {:?}", maybe_usefull, file_error) +} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..fca8572 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,478 @@ +#![allow(non_snake_case)] + +use std::fs; +use std::io::{self, BufRead, /*BufReader*/}; +use std::path; +use std::collections::HashMap; +use std::ffi; +mod Cfs; + +// https://users.rust-lang.org/t/trim-string-in-place/15809 +trait TrimInPlace { fn trim_in_place (self: &'_ mut Self); } +impl TrimInPlace for String { + fn trim_in_place (self: &'_ mut Self) { + let (start, len): (*const u8, usize) = { + let self_trimmed: &str = self.trim(); + (self_trimmed.as_ptr(), self_trimmed.len()) + }; + unsafe { + core::ptr::copy( + start, + self.as_bytes_mut().as_mut_ptr(), // no str::as_mut_ptr() in std ... + len, + ); + } + self.truncate(len); // no String::set_len() in std ... + } +} +pub struct Translation { + origin: String, + translation: String, + line: u32, + version: u16, +} + +static ALLOWED_LANGUAGES: [&str; 8] = [ + "l_english", + "l_braz_por", + "l_german", + "l_french", + "l_spanish", + "l_polish", + "l_russian", + "l_simp_chinese", +]; + +struct Argument { + argument: &'static str, + description: &'static str, + possibilities: &'static str, + requirements: &'static str, + example: &'static str, +} + +static ALLOWED_ARGUMENTS: [Argument; 5] = [ + Argument { + argument: "-h", + description: "Prints whole documentation (for specific information use -h [argument]), possible arguments.\ + \n overall treat this as an API documentation. If this argument (or help, /h etc)\ + \n is found anywhere in the list every other argument is disregarded.", + possibilities: "chainable: Yes with every argument; Can be used separately: Yes", + requirements: "requirements: nothing, or a supported argument", + example: "example: -h \ + \n example: -h -a", + }, + Argument { + argument: "-g", + description: "generate the folder structure that Stellaris the game utilizes", + possibilities: "chainable: NO; Can be used separately: Yes", + requirements: "requirements: root folder has to point to a valid drive or directory \ + \n It does not have to point to an existing folder or folder structure", + example: "example: folder C:\\testing exists but C:\\testing\\stellaris doesn't\ + \n stellaris_localisation_tool -g \"C:\\testing\\stellaris\\newproject\"\ + \n creates EVERY folder and subfolder mimicking Stellaris main folder structure.", + }, + Argument { + argument: "-a", + description: "Scan every .yml file in folder and its subfolders for parsing errors and version checking", + possibilities: "chainable: Yes with -o; Can be used separately: Yes", + requirements: "requirements: root folder has to point to a valid drive and existing directory", + example: "example: folder C:\\testing\\localisation exists\ + \n stellaris_localisation_tool -a \"C:\\testing\\localisation\"", + }, + Argument { + argument: "-o", + description: "Overwrites default extension form .yml to whatever you like", + possibilities: "chainable: Yes with -a; Can be used separately: NO", + requirements: "requirements: -a argument has to be supplied, and it has to be before or after -a argument", + example: "example: folder C:\\testing\\localisation exists\ + \n stellaris_localisation_tool -a \"C:\\testing\\localisation\" -o \"\"\ + \n (only files with no extensions will be parsed. e.g. file but not file.e)", + }, + Argument { + argument: "-s", + description: "Scan single file at given path for parsing errors and version checking", + possibilities: "chainable: NO; Can be used separately: Yes", + requirements: "requirements: file has to exist, file extension has to supplied (.yml, .txt or nothing)", + example: "example: file C:\\testing\\localisation\\test.yml exists\ + \n stellaris_localisation_tool -s \"C:\\testing\\localisation\\test.yml\"\ + \n\n example: file C:\\testing\\localisation\\test exists\ + \n stellaris_localisation_tool -s \"C:\\testing\\localisation\\test\"", + }, +]; + +struct KeyVersion { + version: u16, + language: u16, +} + +fn real_main () -> i32 { + let args : Vec = std::env::args().collect(); + let mut help_found = false; + + println!("{:?}", args); + + for x in 0..args.len() { + if args[x] == "-h" + { + if args.len() > x+1 { + for y in 0..ALLOWED_ARGUMENTS.len() { + if args[x+1] == ALLOWED_ARGUMENTS[y].argument{ + print!(" {} {}\n\n {}\n\n {}\n\n {}", ALLOWED_ARGUMENTS[y].argument, ALLOWED_ARGUMENTS[y].description, ALLOWED_ARGUMENTS[y].possibilities, ALLOWED_ARGUMENTS[y].requirements, ALLOWED_ARGUMENTS[y].example); + return 0; + } + } + println!("Supplied argument isn't supported.", ); + return 0; + } + help_found = true; + } + else if args[x] == "/h" || args[x] == "\\h" || args[x] == "help" { + help_found = true; + break; + } + } + if args.len() == 1 || help_found{ + print!(" Everything that is printed here is subject to change prior to 1.0\n\ + \n Also please remember that C:\\my awesome folder is not synonymus with\ + \n \"C:\\my awesome folder\" (notice the quotes) first one will be parsed as\ + \n [\"C:\\my\", \"awesome\", \"folder\"] while the second one as\ + \n [\"C:\\my awesome folder\"]. All paths have to be absolute\ + \n If you're new, try \"-h -h\"\n\ + \n chainable: Means you can chain arguments so that they work differently together\ + \n if you supply multiple arguments then program will 'consume' them until nothing is left\n\ + \n List of possible arguments\ + \n [nothing]\ + \n /h\ + \n \\h\ + \n help\n"); + for x in 0..ALLOWED_ARGUMENTS.len() { + print!(" {} {}\n\n {}\n\n {}\n\n {}\n-------------\n", ALLOWED_ARGUMENTS[x].argument, ALLOWED_ARGUMENTS[x].description, ALLOWED_ARGUMENTS[x].possibilities, ALLOWED_ARGUMENTS[x].requirements, ALLOWED_ARGUMENTS[x].example); + } + return 0; + } + let mut extension = ffi::OsStr::new("yml"); + let mut all_keys: HashMap = HashMap::new(); + let mut localisation_families: Vec> = std::vec::Vec::with_capacity(ALLOWED_LANGUAGES.len()); + for _ in 0..ALLOWED_LANGUAGES.len() { + localisation_families.push(HashMap::new()); + } + let mut folder_structure: std::vec::Vec = Vec::new(); + let mut errors: std::vec::Vec = Vec::new(); + let mut x = 1; + 'outer: while x < args.len() { + for y in 1..ALLOWED_ARGUMENTS.len() { + if args[x] == ALLOWED_ARGUMENTS[y].argument { + match y { + 1 => { // -g + if x+1 == args.len() { + println!("Didn't supply a path, disregarding"); + break 'outer; + } + let possible_path = args[x+1].replace("\"", "\\"); + let path = path::Path::new(&possible_path); + if !path.is_absolute() { + println!("Supplied path isn't absolute, disregarding"); + x=x+1; + continue 'outer; + } + if !(path.components().next().unwrap().as_ref() as &path::Path).exists() { + println!("Supplied path points to a drive that doesn't exist, disregarding"); + x=x+1; + continue 'outer; + } + Cfs::populate_folder_structure(&mut folder_structure); + 'main: loop { + Cfs::make_folder_structure(&folder_structure, &String::from(path.to_str().unwrap()), &mut errors); + if errors.len() > 0 { + println!("There were errors while trying to create folder structure:"); + for error in errors.iter() { + println!("{}",error); + } + loop { + println!("Would you like to try agian or continue(not recomended)?"); + println!("T- Try again"); + println!("C- Continue"); + let mut response = String::new(); + io::stdin().read_line(&mut response).expect("Failed to read user response"); + if response == "T".to_owned() || response == "t".to_owned() { + continue 'main; + } else if response == "C".to_owned() || response == "c".to_owned() { + break 'main; + } else { + println!("You didn't supply C or T, please try again."); + } + } + } else { + break; + } + } + x=x+2; + continue 'outer; + } + 2 => { // -a + if x+1 == args.len() { + println!("Didn't supply a path, disregarding"); + break 'outer; + } + let possible_path = args[x+1].replace("\"", "\\"); + let path = path::Path::new(&possible_path); + if !path.is_absolute() { + println!("Supplied path isn't absolute, disregarding"); + x=x+1; + continue 'outer; + } + if !path.is_dir() { + println!("Supplied path doesn't point to a directory, disregarding"); + x=x+1; + continue 'outer; + } + if args.len() > x+3 { + if args[x+2] == "-o" { + extension = ffi::OsStr::new(&args[x+3]); + } + x+=x+2; + } + scrape_localisations(path, &mut localisation_families, &mut all_keys, extension); + x+=2; + continue 'outer; + } + 3 => { // -o + if x+1 == args.len() { + println!("Didn't supply a path, disregarding"); + break 'outer; + } else { + extension = ffi::OsStr::new(&args[x+1]); + } + x=x+2; + continue 'outer; + } + 4 => { // -s + if x+1 == args.len() { + println!("Didn't supply a path, disregarding"); + break 'outer; + } + let possible_path = args[x+1].replace("\"", "\\"); + let path = path::Path::new(&possible_path); + if !path.is_absolute() { + println!("Supplied path isn't absolute, disregarding"); + x=x+1; + continue 'outer; + } + if !path.is_file() { + println!("Supplied path doesn't point to a file that exists, disregarding"); + x=x+1; + continue 'outer; + } + parse_localisation(path, &mut localisation_families, &mut all_keys); + x+=2; + continue 'outer; + } + _ => { + print!("Logic error", ); + return 1; + } + } + } + } + println!("{} is unsupported", args[x]); + x+=1; + } + for (key, key_version) in all_keys.iter() { + for x in 0..localisation_families.len() { + if let Some(check) = localisation_families[x].get(&key.to_owned()) { + if key_version.version > check.version { + println!("Key: {} in a {} family on line {} in file {} has a version {}, and thus needs retranslation, because same key in a {} family in file {} on line {}, has a version {}", + key, + ALLOWED_LANGUAGES[x], + check.line, + check.origin, + check.version, + ALLOWED_LANGUAGES[key_version.language as usize], + localisation_families[key_version.language as usize].get(key).expect("This should not happen, logic error,").origin, + localisation_families[key_version.language as usize].get(key).expect("This should not happen, logic error").line, + key_version.version); + } + } + } + } + return 0; +} + +fn main() { // I would like to call it config but I can't :C + std::process::exit(match real_main() { + 0 => 0, + _ => { + eprintln!("Unhandled error code"); + 1 + } + }) +} +fn parse_localisation( + path: &path::Path, + localisation_families: &mut std::vec::Vec>, + all_keys: &mut HashMap, + ) { + println!("Now parsing: {:?}", path); + let mut currentlanguage = 9999; + let mut currenttranslation: Translation; + let file = fs::OpenOptions::new() + .read(true) + .open(path).expect("openinig translation failed"); + let file = io::BufReader::new(file); + let mut lines_iterator = file.lines(); + let mut line = lines_iterator.next().expect("File is empty").expect("There was an error reading a file"); + if line.len() < 3 { + panic!{"File too short"}; + } + if &line.as_bytes()[0..3] == [239, 187, 191] { + line.drain(..3); + } else { + panic!("Localisation without UTF8 BOM"); + } + let mut line_number: u32 = 1; + 'main: loop { + let mut key: String; + let mut value: String; + let line_copy = line.clone(); + + if let Some(collon) = line_copy.find(':') { + let mut possiblenumber: String = "".to_owned(); + let pair = line_copy.split_at(collon); + key = pair.0.to_owned(); + value = pair.1.to_owned(); + let mut not_found_start_of_key = true; + for char in key.chars() { + if !char.is_ascii() { + panic!("Key on line {} contains non-ascii characters", line_number); + } + if char == '#' && not_found_start_of_key { + line = match lines_iterator.next() { + Some(line) => { + line_number+=1; + line.expect("There was an error reading a file") + }, + None => break 'main, + }; + continue 'main; + } else if char != ' ' { + not_found_start_of_key = false; + } + } + key.trim_in_place(); + value.trim_in_place(); + + if value.len() == 1 { + let mut found = false; + for (i, language) in ALLOWED_LANGUAGES.iter().enumerate() { + if &key == language { + found = true; + currentlanguage = i; + break; + } + } + if !found { + panic!("Language not supported"); + } + } else { + if currentlanguage == 9999 { + panic!{"Key-value pair was found before language key, on line {}", line_number}; + } + else { + value.drain(0..1); + let mut begining: u32 = 0; + let hash_found = value.rfind('#').unwrap_or(0); + if let Some(_begining) = value.find('\"') { + begining = _begining as u32; + } + let mut end: u32 = 0; + if let Some(_end) = value.rfind('\"') { + end = _end as u32; + } + if let Some(at) = value.find(':') { + if ((at as u32) < begining || at as u32 > end) && at < hash_found { + panic!("colon found in wrong place at line: {}", line_number); + } + } + { + for (_, c) in value.char_indices().into_iter() { + if (c as u32 > 47) && ((c as u32) < 58) { + &possiblenumber.push(c); + } + else { + break; + } + } + value.drain(0..possiblenumber.len()); + value.trim_in_place(); + let mut first_quote = 0; + if let Some(at) = value.find('\"') + { + if at != 0 { + panic!("Non-space, non-numeric character was found before first quote, this line will fail to parse. line number: {}\n parsed line: {}", line_number, line); + } + first_quote = at; + } + if let Some(at) = value.rfind('\"') + { + if at == first_quote { + panic!("There is only one quote, this line will parse as nothing. Literally \"\". line number: {}\n parsed line: {}", line_number, line); + } + if hash_found > at { + println!("line {}", line_number); + let mut text_after_last_quote = value.get((at+1)..(hash_found-2)).expect("I don't know", ).to_owned(); + text_after_last_quote.trim_in_place(); + println!("{}, {}", text_after_last_quote, text_after_last_quote.len()); + if text_after_last_quote.len() > 0 { + panic!("Non-comment text was found after last quote, it will be disregarded(if it is a comment precede it with \"#\"). line number: {}\n parsed line: {}", line_number, line); + } + } + } + } + let possiblenumber: u16 = match possiblenumber.parse() { + Ok(val) => val, + Err(_) => 0, + }; + currenttranslation = Translation {origin: path.to_str().expect("Non UTF-8 os string").to_owned(), translation: value, version: possiblenumber, line: line_number}; + if let Some(key_version) = all_keys.get(&key.to_owned()) { + if key_version.version < possiblenumber { + all_keys.insert(key.to_owned(), KeyVersion{version: possiblenumber, language: currentlanguage as u16}); + } + } else { + all_keys.insert(key.to_owned(), KeyVersion{version: possiblenumber, language: currentlanguage as u16}); + } + localisation_families[currentlanguage].insert(key.to_owned(), currenttranslation); + } + } + } + else { + line.trim_in_place(); + if !line.len() == 0 { + panic!("Line X is formated wrongly"); + } + } + line = match lines_iterator.next() { + Some(line) => { + line_number+=1; + line.expect("There was an error reading a file") + }, + None => break, + } + } +} +fn scrape_localisations ( + dir: &path::Path, + localisation_families: &mut std::vec::Vec>, + all_keys: &mut HashMap, + extension: &ffi::OsStr) { + for entry in fs::read_dir(dir).expect("cokolwiek") { + let path = entry.expect("coś").path(); + if path.is_dir() { + scrape_localisations(&path, localisation_families, all_keys, extension) + } else if let Some(ext) = path.extension() { + if ext == extension { + parse_localisation(&path, localisation_families, all_keys) + } + } + } +} \ No newline at end of file diff --git a/stellaris_modding_tools.exe b/stellaris_modding_tools.exe new file mode 100644 index 0000000..f522325 Binary files /dev/null and b/stellaris_modding_tools.exe differ