Expose save slot info (like playtime) in GUI

This commit is contained in:
Joshua Goins 2025-04-20 01:02:00 -04:00
parent d1e9a8d57a
commit dc40526c33
5 changed files with 120 additions and 35 deletions

10
Cargo.lock generated
View file

@ -620,6 +620,15 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "chrono"
version = "0.4.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a7964611d71df112cb1730f2ee67324fcf4d0fc6606acbbe9bfe06df124637c"
dependencies = [
"num-traits",
]
[[package]] [[package]]
name = "clipboard-win" name = "clipboard-win"
version = "5.4.0" version = "5.4.0"
@ -1565,6 +1574,7 @@ name = "ireko"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"binrw", "binrw",
"chrono",
"eframe", "eframe",
"flate2", "flate2",
"paramacro", "paramacro",

View file

@ -8,3 +8,4 @@ flate2 = { version = "1.0", features = ["zlib-ng"], default-features = false }
binrw = { version = "0.14", features = ["std"], default-features = false } binrw = { version = "0.14", features = ["std"], default-features = false }
paramacro = { path = "paramacro" } paramacro = { path = "paramacro" }
eframe = { version= "0.31" } eframe = { version= "0.31" }
chrono = { version = "0.4", default-features = false }

View file

@ -3,7 +3,18 @@ use std::{env, io::Cursor};
use binrw::BinRead; use binrw::BinRead;
use eframe::egui; use eframe::egui;
use ireko::{ use ireko::{
property::{array_property::ArrayValue, map_property::{MabSubProperty, MapKeyProperty}}, save_object::{generic::Property, LocalProfileObject, PersistentObject}, structure::{DAAssembleIdDataStruct, DABuildDataStruct, DACustomizeAssetIdDataStruct, DAHumanoidColoringDataStruct, DAHumanoidFigureData, DAMachineColoringDataStruct, DAModuleColorStruct, DATriggerDataStruct, DATuningDataStruct, Guid, LinearColorStruct, PrimaryAssetIdStruct, Struct}, CompressedSaveFile CompressedSaveFile,
property::{
array_property::ArrayValue,
map_property::{MabSubProperty, MapKeyProperty},
},
save_object::{LocalProfileObject, PersistentObject, SlotObject, generic::Property},
structure::{
DAAssembleIdDataStruct, DABuildDataStruct, DACustomizeAssetIdDataStruct,
DAHumanoidColoringDataStruct, DAHumanoidFigureData, DAMachineColoringDataStruct,
DAModuleColorStruct, DATriggerDataStruct, DATuningDataStruct, Guid, LinearColorStruct,
PrimaryAssetIdStruct, Struct,
},
}; };
fn main() -> eframe::Result { fn main() -> eframe::Result {
@ -39,7 +50,7 @@ fn main() -> eframe::Result {
ModuleInventory, ModuleInventory,
PartsInventory, PartsInventory,
Build, Build,
Presets Presets,
}; };
let mut tab = Tab::General; let mut tab = Tab::General;
@ -67,7 +78,29 @@ fn main() -> eframe::Result {
panic!("Expected a stirng!"); panic!("Expected a stirng!");
}; };
if ui.button(&name.value).clicked() { let slot_path = format!("{save_dir}/{}/Slot.sav", id.value);
let mut slot_data = Cursor::new(std::fs::read(&slot_path).unwrap());
let slot = CompressedSaveFile::<SlotObject>::read_le(&mut slot_data).unwrap();
let playtime = chrono::NaiveTime::from_num_seconds_from_midnight_opt(
slot.value.objs.playtime.value.round() as u32,
0,
)
.unwrap();
let last_checkpoint = slot.value.objs.district_tag.value;
if ui
.button(&format!(
"{} {} {} {}",
name.value,
slot.value.objs.slot_info.timestamp.to_datetime(),
playtime,
last_checkpoint
))
.clicked()
{
let persistent_path = format!("{save_dir}/{}/Persistent.sav", id.value); let persistent_path = format!("{save_dir}/{}/Persistent.sav", id.value);
let mut persistent_data = let mut persistent_data =
@ -116,15 +149,20 @@ fn main() -> eframe::Result {
ui.checkbox(&mut persistent.demo_version.value, "Demo Version"); ui.checkbox(&mut persistent.demo_version.value, "Demo Version");
ui.horizontal(|ui| { ui.horizontal(|ui| {
ui.add(egui::DragValue::new(&mut persistent.money.value).suffix(" cell")); ui.add(
egui::DragValue::new(&mut persistent.money.value).suffix(" cell"),
);
ui.label("Money") ui.label("Money")
}); });
ui.horizontal(|ui| { ui.horizontal(|ui| {
ui.add(egui::DragValue::new(&mut persistent.current_item_slot.value).range(0..=5)); ui.add(
egui::DragValue::new(&mut persistent.current_item_slot.value)
.range(0..=5),
);
ui.label("Selected Item"); ui.label("Selected Item");
}); });
}, }
Tab::ItemWheel => { Tab::ItemWheel => {
for (i, item) in persistent.item_slots.entries.iter_mut().enumerate() { for (i, item) in persistent.item_slots.entries.iter_mut().enumerate() {
let ArrayValue::Struct { ref mut r#struct } = item.key else { let ArrayValue::Struct { ref mut r#struct } = item.key else {
@ -140,14 +178,16 @@ fn main() -> eframe::Result {
ui.label(format!("{i:02}")); ui.label(format!("{i:02}"));
}); });
} }
}, }
Tab::ItemInventory => { Tab::ItemInventory => {
for item in &mut persistent.normal_item_inventory.entries { for item in &mut persistent.normal_item_inventory.entries {
let MapKeyProperty::StructMaybe(ref key) = item.key else { let MapKeyProperty::StructMaybe(ref key) = item.key else {
panic!("Expecting a struct!"); panic!("Expecting a struct!");
}; };
let Property::Name(ref name) = **(key.extra_fields[0].key.as_ref().unwrap()) else { let Property::Name(ref name) =
**(key.extra_fields[0].key.as_ref().unwrap())
else {
panic!("Expecting a name property!"); panic!("Expecting a name property!");
}; };
@ -155,11 +195,15 @@ fn main() -> eframe::Result {
panic!("Expecting a struct!"); panic!("Expecting a struct!");
}; };
let Property::Int(ref carry_count) = **(value.fields[0].key.as_ref().unwrap()) else { let Property::Int(ref carry_count) =
**(value.fields[0].key.as_ref().unwrap())
else {
panic!("Expecting an int!"); panic!("Expecting an int!");
}; };
let Property::Int(ref store_count) = **(value.fields[1].key.as_ref().unwrap()) else { let Property::Int(ref store_count) =
**(value.fields[1].key.as_ref().unwrap())
else {
panic!("Expecting an int!"); panic!("Expecting an int!");
}; };
@ -169,7 +213,7 @@ fn main() -> eframe::Result {
ui.label(format!("store: {}", store_count.value)); ui.label(format!("store: {}", store_count.value));
}); });
} }
}, }
Tab::ModuleInventory => { Tab::ModuleInventory => {
ui.horizontal(|ui| { ui.horizontal(|ui| {
if ui.button("Mobility").clicked() { if ui.button("Mobility").clicked() {
@ -208,8 +252,12 @@ fn main() -> eframe::Result {
let matches = match module { let matches = match module {
ModuleType::Mobility => key.value == "EDAModuleItemType::Mobility", ModuleType::Mobility => key.value == "EDAModuleItemType::Mobility",
ModuleType::FrontWeapon => key.value == "EDAModuleItemType::FrontWeapon", ModuleType::FrontWeapon => {
ModuleType::RearWeapon => key.value == "EDAModuleItemType::RearWeapon", key.value == "EDAModuleItemType::FrontWeapon"
}
ModuleType::RearWeapon => {
key.value == "EDAModuleItemType::RearWeapon"
}
ModuleType::Hanger => key.value == "EDAModuleItemType::Hanger", ModuleType::Hanger => key.value == "EDAModuleItemType::Hanger",
ModuleType::Utility => key.value == "EDAModuleItemType::Utility", ModuleType::Utility => key.value == "EDAModuleItemType::Utility",
ModuleType::Thruster => key.value == "EDAModuleItemType::Thruster", ModuleType::Thruster => key.value == "EDAModuleItemType::Thruster",
@ -221,7 +269,9 @@ fn main() -> eframe::Result {
panic!("Expecting struct value!"); panic!("Expecting struct value!");
}; };
let Property::Map(ref mut map) = **value.fields[0].key.as_mut().unwrap() else { let Property::Map(ref mut map) =
**value.fields[0].key.as_mut().unwrap()
else {
panic!("Expecting map!"); panic!("Expecting map!");
}; };
@ -236,11 +286,14 @@ fn main() -> eframe::Result {
panic!("Expecting struct value!"); panic!("Expecting struct value!");
}; };
let Property::Struct(ref asset_id) = **value.fields[0].key.as_mut().unwrap() else { let Property::Struct(ref asset_id) =
**value.fields[0].key.as_mut().unwrap()
else {
panic!("Expecting struct!"); panic!("Expecting struct!");
}; };
let Struct::PrimaryAssetId(ref asset_id) = asset_id.r#struct else { let Struct::PrimaryAssetId(ref asset_id) = asset_id.r#struct
else {
panic!("Expecting primary asset id!"); panic!("Expecting primary asset id!");
}; };
@ -253,23 +306,24 @@ fn main() -> eframe::Result {
};*/ };*/
ui.horizontal(|ui| { ui.horizontal(|ui| {
ui.label(format!("{:#?} {} level", key, asset_id.primary_asset_name.value)); ui.label(format!(
"{:#?} {} level",
key, asset_id.primary_asset_name.value
));
}); });
} }
} }
} }
//println!("{:#?}", persistent.module_inventory); //println!("{:#?}", persistent.module_inventory);
}, }
Tab::PartsInventory => { Tab::PartsInventory => {
println!("{:#?}", persistent.parts_inventory); println!("{:#?}", persistent.parts_inventory);
}, }
Tab::Build => { Tab::Build => {
edit_build_data(ui, &mut persistent.current_build_data); edit_build_data(ui, &mut persistent.current_build_data);
}, }
Tab::Presets => { Tab::Presets => {}
},
} }
} }
}); });
@ -322,8 +376,14 @@ fn edit_customize_data(ui: &mut egui::Ui, customize: &mut DACustomizeAssetIdData
edit_humanoid_figure(ui, &mut customize.figure_data); edit_humanoid_figure(ui, &mut customize.figure_data);
ui.checkbox(&mut customize.inverse_face_mesh.value, "Inverse Face"); ui.checkbox(&mut customize.inverse_face_mesh.value, "Inverse Face");
ui.checkbox(&mut customize.inverse_front_hair_mesh.value, "Inverse Front Hair"); ui.checkbox(
ui.checkbox(&mut customize.inverse_back_hair_mesh.value, "Inverse Back Hair"); &mut customize.inverse_front_hair_mesh.value,
"Inverse Front Hair",
);
ui.checkbox(
&mut customize.inverse_back_hair_mesh.value,
"Inverse Back Hair",
);
} }
fn edit_humanoid_figure(ui: &mut egui::Ui, figure_data: &mut DAHumanoidFigureData) { fn edit_humanoid_figure(ui: &mut egui::Ui, figure_data: &mut DAHumanoidFigureData) {
@ -348,7 +408,10 @@ fn edit_humanoid_figure(ui: &mut egui::Ui, figure_data: &mut DAHumanoidFigureDat
}); });
ui.horizontal(|ui| { ui.horizontal(|ui| {
ui.add(egui::Slider::new(&mut figure_data.waist_up.value, 0.0..=1.0)); ui.add(egui::Slider::new(
&mut figure_data.waist_up.value,
0.0..=1.0,
));
ui.label("Waist"); ui.label("Waist");
}); });
} }

View file

@ -8,29 +8,29 @@ use crate::{
#[derive(Debug)] #[derive(Debug)]
pub struct SlotObject { pub struct SlotObject {
#[paramacro::serialized_field = "SavedDataVersion"] #[paramacro::serialized_field = "SavedDataVersion"]
version: IntProperty, pub version: IntProperty,
#[paramacro::serialized_field = "bDemoVersion"] #[paramacro::serialized_field = "bDemoVersion"]
demo: BoolProperty, pub demo: BoolProperty,
#[paramacro::serialized_field = "CreatedTimeStamp"] #[paramacro::serialized_field = "CreatedTimeStamp"]
created_timestamp: DateTimeStruct, pub created_timestamp: DateTimeStruct,
#[paramacro::serialized_field = "PlayTime"] #[paramacro::serialized_field = "PlayTime"]
playtime: FloatProperty, pub playtime: FloatProperty,
#[paramacro::serialized_field = "RegisteredName"] #[paramacro::serialized_field = "RegisteredName"]
name: StrProperty, pub name: StrProperty,
#[paramacro::serialized_field = "LoadOption"] #[paramacro::serialized_field = "LoadOption"]
load_option: DALoadOptionStruct, pub load_option: DALoadOptionStruct,
#[paramacro::serialized_field = "DistrictTag"] #[paramacro::serialized_field = "DistrictTag"]
district_tag: NameProperty, pub district_tag: NameProperty,
#[paramacro::serialized_field = "CycleCount"] #[paramacro::serialized_field = "CycleCount"]
cycle_count: IntProperty, pub cycle_count: IntProperty,
#[paramacro::serialized_field = "SlotInfo"] #[paramacro::serialized_field = "SlotInfo"]
slot_info: SaveSlotInfoStruct, pub slot_info: SaveSlotInfoStruct,
} }

View file

@ -1,4 +1,5 @@
use binrw::binrw; use binrw::binrw;
use chrono::Datelike;
use crate::property::PropertyBase; use crate::property::PropertyBase;
@ -26,3 +27,13 @@ impl PropertyBase for DateTimeStruct {
8 8
} }
} }
impl DateTimeStruct {
pub fn to_datetime(&self) -> chrono::NaiveDateTime {
let datetime = chrono::NaiveDate::from_ymd_opt(1, 1, 1).unwrap();
let datetime = chrono::NaiveDateTime::from(datetime);
datetime
.checked_add_signed(chrono::TimeDelta::microseconds(self.ticks / 10))
.unwrap()
}
}