Skip to content

Instantly share code, notes, and snippets.

@madwareru
Last active June 6, 2024 23:39
Show Gist options
  • Select an option

  • Save madwareru/9754da985ad9a5a4895ff2eef533fefa to your computer and use it in GitHub Desktop.

Select an option

Save madwareru/9754da985ad9a5a4895ff2eef533fefa to your computer and use it in GitHub Desktop.
Used by myself to play the company of Songs Of Conquest without that much back hurt
[package]
name = "songs-of-conquest-json-explorator"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
json = "0.12.4"
getopts = "0.2.21"
use std::io::Write;
use getopts::Options;
use json::JsonValue;
use json::number::Number;
mod team {
use std::fmt::{Display, Formatter};
use json::JsonValue;
use crate::cast_json::CastJson;
pub struct TeamInfo {
pub id: i32,
pub name: String,
pub resources: Vec<ResourceInfo>
}
impl TeamInfo {
pub fn read_from(jv: &JsonValue) -> Option<Self> {
let object = jv.as_object()?;
let id = object.get("_teamID")?.as_i32()?;
let name = object.get("_name")?.as_str()?.to_string();
let resources = object
.get("_resources")?.as_object()?
.get("_resources")?.as_array_slice()?
.iter()
.filter_map(|jv| {
let quantity = jv.as_object()?.get("_amount")?.as_usize()?;
let resource_type = jv.as_object()?.get("Type")?.as_usize()?.into();
Some(ResourceInfo { resource_type, quantity })
}).collect();
Some(Self {
id,
name,
resources
})
}
}
#[derive(Copy, Clone)]
pub struct ResourceInfo {
pub resource_type: ResourceType,
pub quantity: usize
}
impl Display for ResourceInfo {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
self.resource_type.fmt(f)?;
f.write_str(": ")?;
self.quantity.fmt(f)
}
}
#[derive(Copy, Clone)]
pub enum ResourceType {
Gold,
Wood,
Stone,
AncientAmber,
GlimmerWeave,
CelestialOre,
Unknown
}
impl From<usize> for ResourceType {
fn from(id: usize) -> Self {
match id {
0 => Self::Gold,
1 => Self::Wood,
2 => Self::Stone,
3 => Self::AncientAmber,
4 => Self::GlimmerWeave,
5 => Self::CelestialOre,
_ => Self::Unknown,
}
}
}
impl Display for ResourceType {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
ResourceType::Gold => f.write_str("Gold"),
ResourceType::Wood => f.write_str("Wood"),
ResourceType::Stone => f.write_str("Stone"),
ResourceType::AncientAmber => f.write_str("AncientAmber"),
ResourceType::GlimmerWeave => f.write_str("GlimmerWeave"),
ResourceType::CelestialOre => f.write_str("CelestialOre"),
_ => f.write_str("???")
}
}
}
}
mod commander {
pub struct CommanderInfo {
pub id: i32,
pub name: String,
pub troops: Vec<TroopInfo>
}
pub struct TroopInfo {
pub faction_id: i32,
pub unit_id: i32,
pub upgrade_type: i32,
pub health: i32,
pub defense: i32,
pub movement: i32,
pub initiative: i32,
pub size: i32,
pub max_size: i32,
pub spell_damage_resistance: i32,
pub damage_multiplier: i32,
pub min_damage: i32,
pub max_damage: i32,
}
}
mod cast_json {
pub trait CastJson {
fn as_array_slice(&self) -> Option<&[json::JsonValue]>;
fn as_object(&self) -> Option<&json::object::Object>;
}
pub trait CastJsonMut {
fn as_array_slice_mut(&mut self) -> Option<&mut [json::JsonValue]>;
fn as_object_mut(&mut self) -> Option<&mut json::object::Object>;
}
impl CastJson for json::JsonValue {
fn as_array_slice(&self) -> Option<&[json::JsonValue]> {
match self {
json::JsonValue::Array(it) => Some(&it[..]),
_ => None
}
}
fn as_object(&self) -> Option<&json::object::Object> {
match self {
json::JsonValue::Object(it) => Some(&it),
_ => None
}
}
}
impl CastJsonMut for json::JsonValue {
fn as_array_slice_mut(&mut self) -> Option<&mut [json::JsonValue]> {
match self {
json::JsonValue::Array(it) => Some(&mut it[..]),
_ => None
}
}
fn as_object_mut(&mut self) -> Option<&mut json::object::Object> {
match self {
json::JsonValue::Object(it) => Some(it),
_ => None
}
}
}
}
use cast_json::*;
use team::TeamInfo;
use crate::commander::{CommanderInfo, TroopInfo};
struct Opts {
input : String,
sub_command: SubCommand
}
enum SubCommand {
ListTeams,
ListCommanders { team_id: usize },
MakeTeamPoor { team_id: usize },
MakeChicken { commander_id: usize },
MakeStronger { commander_id: usize }
}
fn parse_options() -> Result<Opts, String> {
let mut opts = Options::new();
opts.optflag(
"",
"list-teams",
"list teams in a save file"
);
opts.optopt(
"",
"list-commanders",
"list teams in a save file for a given team", "TEAM_ID"
);
opts.optopt(
"",
"make-poor",
"make the team in a save file very poor",
"TEAM_ID"
);
opts.optopt(
"",
"make-chicken",
"make the commander in a save file weak as a chicken",
"COMMANDER_ID"
);
opts.optopt(
"",
"make-stronger",
"make all armies of the commander full in size",
"COMMANDER_ID"
);
let args: Vec<String> = std::env::args().collect();
let actual_args = &args[1..];
let program = args[0].clone();
let options = {
if actual_args.is_empty() {
None
}
else {
let input = actual_args[0].clone();
let actual_args = &actual_args[1..];
let matches = match opts.parse(actual_args) {
Ok(m) => { Some(m) }
Err(f) => {
println!("Error occured: {}", f.to_string());
None
}
};
matches.and_then(|it| {
match () {
_ if it.opt_present("list-teams") => Some(Opts {
input,
sub_command: SubCommand::ListTeams
}),
_ if it.opt_present("list-commanders") => {
it
.opt_str("list-commanders")
.and_then(|it| it.parse::<usize>().ok())
.map(|team_id| Opts { input, sub_command: SubCommand::ListCommanders { team_id } })
},
_ if it.opt_present("make-poor") => {
it
.opt_str("make-poor")
.and_then(|it| it.parse::<usize>().ok())
.map(|team_id| Opts { input, sub_command: SubCommand::MakeTeamPoor { team_id } })
},
_ if it.opt_present("make-chicken") => {
it
.opt_str("make-chicken")
.and_then(|it| it.parse::<usize>().ok())
.map(|commander_id| Opts { input, sub_command: SubCommand::MakeChicken { commander_id } })
},
_ if it.opt_present("make-stronger") => {
it
.opt_str("make-stronger")
.and_then(|it| it.parse::<usize>().ok())
.map(|commander_id| Opts { input, sub_command: SubCommand::MakeStronger { commander_id } })
},
_ => None
}
})
}
};
options.ok_or_else(|| {
let brief = format!("Usage: {} FILE [options]", program);
opts.usage(&brief).to_string()
})
}
fn main() {
let options = parse_options();
match options {
Ok(opts) => {
let Opts { input, .. } = &opts;
if let Ok(file_contents) = std::fs::read_to_string(input) {
let mut save_file = json::parse(&file_contents).expect("Failed to parse save file");
let mut file = {
let file = save_file
.as_object_mut().expect("Incorrect save file. Object expected")
.get("File")
.and_then(|it| it.as_str())
.expect("Failed to find File entry");
json::parse(file).expect("Failed to parse File entry")
};
let changed = match opts {
Opts { sub_command: SubCommand::ListTeams, .. } => {
print_teams(&file).expect("Failed to print team info");
false
},
Opts { sub_command: SubCommand::MakeTeamPoor { team_id }, .. } => {
make_team_poor(&mut file, team_id)
.expect("Failed to make team poor");
true
},
Opts { sub_command: SubCommand::ListCommanders { team_id }, .. } => {
print_commanders(&file, team_id).expect("Failed to print commander info");
false
},
Opts { sub_command: SubCommand::MakeChicken { commander_id}, .. } => {
make_chicken(&mut file, commander_id)
.expect("Failed to make commander a chicken");
true
}
Opts { sub_command: SubCommand::MakeStronger { commander_id }, .. } => {
make_stronger(&mut file, commander_id)
.expect("Failed to make commander stronger");
true
}
};
if changed {
let file_entry = save_file
.as_object_mut().unwrap()
.get_mut("File")
.unwrap();
*file_entry = JsonValue::String(file.to_string());
let save_file_str = save_file.pretty(2);
std::fs::File::create(input).unwrap().write_all(save_file_str.as_bytes()).unwrap();
}
}
else {
println!("Failed to read {}", &input);
}
}
Err(usages) => {
println!("{}", usages)
}
}
}
fn make_chicken(adventure_data: &mut JsonValue, commander_id: usize) -> Option<()> {
let troops = adventure_data
.as_object_mut()?
.get_mut("_troopsSerializable")
.and_then(|it| it.as_array_slice_mut())?;
for troop in troops.iter_mut() {
let parent_id = troop.as_object()?.get("_parentId")?.as_i32()?;
if parent_id != commander_id as i32 {
continue;
}
let troop_stats = troop.as_object_mut()?.get_mut("_stats")?.as_object_mut()?;
*(troop_stats.get_mut("_size")?) = JsonValue::Number(1.into());
let troop_reference = troop.as_object_mut()?.get_mut("_reference")?.as_object_mut()?;
*(troop_reference.get_mut("Size")?) = JsonValue::Number(1.into());
}
Some(())
}
fn make_stronger(adventure_data: &mut JsonValue, commander_id: usize) -> Option<()> {
let troops = adventure_data
.as_object_mut()?
.get_mut("_troopsSerializable")
.and_then(|it| it.as_array_slice_mut())?;
for troop in troops.iter_mut() {
let parent_id = troop.as_object()?.get("_parentId")?.as_i32()?;
if parent_id != commander_id as i32 {
continue;
}
let troop_stats = troop.as_object_mut()?.get_mut("_stats")?.as_object_mut()?;
let max_size = troop_stats.get("_maxTroopSize")?.as_i32()?;
*(troop_stats.get_mut("_size")?) = JsonValue::Number(max_size.into());
let troop_reference = troop.as_object_mut()?.get_mut("_reference")?.as_object_mut()?;
*(troop_reference.get_mut("Size")?) = JsonValue::Number(max_size.into());
}
Some(())
}
fn make_team_poor(adventure_data: &mut JsonValue, target_team_id: usize) -> Option<()> {
let teams = adventure_data
.as_object_mut()?
.get_mut("_teamsSerializable")
.and_then(|it| it.as_array_slice_mut())?;
let enemy_team_object = teams
.iter_mut()
.find(|it| it
.as_object()
.and_then(|it| it.get("_teamID"))
.and_then(|it| it.as_usize())
.map(|it| it == target_team_id)
.unwrap_or(false)
)?.as_object_mut()?;
let resources = enemy_team_object
.get_mut("_resources")?.as_object_mut()?
.get_mut("_resources")?.as_array_slice_mut()?;
for resource in resources.iter_mut() {
match resource {
JsonValue::Object(ro) => {
if let Some(jv) = ro.get_mut("_amount") {
*jv = JsonValue::Number(Number::from(0))
}
}
_ => {}
}
}
Some(())
}
fn print_teams(adventure_data: &JsonValue) -> Option<()> {
let teams = adventure_data
.as_object()?
.get("_teamsSerializable")?
.as_array_slice()?;
for team_jvalue in teams.iter() {
let team_info = TeamInfo::read_from(team_jvalue)?;
println!("id: {}\nname: {}\nresources:", team_info.id, &team_info.name);
for res in team_info.resources.iter() {
println!(" - {}", res);
}
println!();
}
Some(())
}
fn print_commanders(adventure_data: &JsonValue, team_id: usize) -> Option<()> {
let commanders_value = adventure_data.as_object()?.get("_commandersSerializable")?;
let mut commanders = commanders_value.as_array_slice()?
.iter()
.filter_map(|commander_entry| {
let commander_team_id = commander_entry.as_object()?
.get("_teamId")?.as_usize()?;
if commander_team_id != team_id {
return None;
}
let id = commander_entry.as_object()?
.get("_id")?.as_i32()?;
let name = commander_entry.as_object()?
.get("_reference")?.as_object()?
.get("Name")?.as_str()?
.to_string();
Some(CommanderInfo {
name,
id,
troops: Vec::new()
})
}).collect::<Vec<_>>();
for troop_entry in adventure_data
.as_object()?
.get("_troopsSerializable")?
.as_array_slice()?
.iter() {
let troop_obj = troop_entry.as_object()?;
let parent_id = troop_obj.get("_parentId")?.as_i32()?;
let reference_obj = troop_obj.get("_reference")?.as_object()?;
let faction_id = reference_obj.get("FactionIndex")?.as_i32()?;
let unit_id = reference_obj.get("UnitIndex")?.as_i32()?;
let upgrade_type = reference_obj.get("UpgradeType")?.as_i32()?;
let stats_obj = troop_obj.get("_stats")?.as_object()?;
let health = stats_obj.get("_health")?.as_i32()?;
let defense = stats_obj.get("_defense")?.as_i32()?;
let movement = stats_obj.get("_movement")?.as_i32()?;
let initiative = stats_obj.get("_initiative")?.as_i32()?;
let size = stats_obj.get("_size")?.as_i32()?;
let max_size = stats_obj.get("_maxTroopSize")?.as_i32()?;
let spell_damage_resistance = stats_obj.get("_spellDamageResistance")?.as_i32()?;
let damage_multiplier = stats_obj.get("_damageMultiplier")?.as_i32()?;
let damage_obj = stats_obj.get("_damage")?.as_object()?;
let min_damage = damage_obj.get("min")?.as_i32()?;
let max_damage = damage_obj.get("max")?.as_i32()?;
let troop_info = TroopInfo {
faction_id,
unit_id,
upgrade_type,
health,
defense,
movement,
initiative,
size,
max_size,
spell_damage_resistance,
damage_multiplier,
min_damage,
max_damage
};
if let Some(commander) = commanders
.iter_mut()
.find(|it| it.id == parent_id) {
commander.troops.push(troop_info);
}
}
for commander in commanders.iter() {
println!("id: {}, name: {}", commander.id, &commander.name);
println!("troops:");
for troop in commander.troops.iter() {
println!(" + faction_id: {}, unit_id: {}", troop.faction_id, troop.unit_id);
println!(" - {} of {}, upgrade_type: {}", troop.size, troop.max_size, troop.upgrade_type);
println!(" - 🦶: {}, initiative: {}", troop.movement, troop.initiative);
println!(" - 🛡: {}, ♥: {}", troop.defense, troop.health);
println!(" - ⚔: {}-{}, damage_multiplier: {}", troop.min_damage, troop.max_damage, troop.damage_multiplier);
}
println!()
}
Some(())
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment