An unofficial collection of APIs used in FreeJam games and mods
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

196 lines
7.3KB

  1. use chrono::{naive::NaiveDate, Datelike};
  2. use std::io::{Read, Write};
  3. use crate::techblox::{EntityHeader, BlockGroupEntity, parse_i64, parse_u32, Parsable, SerializedEntityDescriptor,
  4. SerializedFlyCamEntity, SerializedPhysicsCameraEntity};
  5. use crate::techblox::blocks::{lookup_hashname, SerializedWireEntity, SerializedGlobalWireSettingsEntity};
  6. /// A collection of cubes and other data from a GameSave.techblox file
  7. //#[derive(Clone)]
  8. pub struct GameSave {
  9. /// Game version that this save was created by.
  10. /// This may affect how the rest of the save file was parsed.
  11. pub version: NaiveDate,
  12. /// Time when file was saved, corresponding to ticks since 0 AD
  13. /// https://docs.microsoft.com/en-us/dotnet/api/system.datetime.ticks?view=netframework-4.7.2
  14. /// Not used for deserialization so not required to be sensible.
  15. pub ticks: i64,
  16. /// Amount of cubes present in the save data, as claimed by the file header.
  17. pub cube_len: u32,
  18. /// Maximum block entity identifier in the game save.
  19. /// Not used for deserialization so not required to be correct.
  20. pub max_entity_id: u32,
  21. /// Amount of block groups, as claimed by the file header.
  22. pub group_len: u32,
  23. /// Entity group descriptors for block group entities.
  24. pub group_headers: Vec<EntityHeader>,
  25. /// Block group entities.
  26. pub cube_groups: Vec<BlockGroupEntity>,
  27. /// Entity group descriptors for block entities.
  28. pub cube_headers: Vec<EntityHeader>,
  29. /// Blocks
  30. pub cube_entities: Vec<Box<dyn SerializedEntityDescriptor>>,
  31. /// Amount of wires in the save data, as claimed by the file.
  32. pub wire_len: u32,
  33. /// Entity group descriptor for wire entities.
  34. pub wire_headers: Vec<EntityHeader>,
  35. /// Wires
  36. pub wire_entities: Vec<SerializedWireEntity>,
  37. /// Entity group descriptor for wire settings
  38. pub wire_settings_header: EntityHeader,
  39. /// Wire settings
  40. pub wire_settings_entity: SerializedGlobalWireSettingsEntity,
  41. /// Entity group descriptor for player fly camera
  42. pub flycam_header: EntityHeader,
  43. /// Player edit mode fly camera
  44. pub flycam_entity: SerializedFlyCamEntity,
  45. /// Entity group descriptor for player simulation mode camera
  46. pub phycam_header: EntityHeader,
  47. /// Player simulation mode camera
  48. pub phycam_entity: SerializedPhysicsCameraEntity,
  49. }
  50. impl Parsable for GameSave {
  51. /// Process a Techblox save file from raw bytes.
  52. fn parse(data: &mut dyn Read) -> std::io::Result<Self> {
  53. // parse version
  54. let year = parse_u32(data)?; // parsed as i32 in-game for some reason
  55. let month = parse_u32(data)?;
  56. let day = parse_u32(data)?;
  57. let date = NaiveDate::from_ymd(year as i32, month, day);
  58. let ticks = parse_i64(data)?; // unused
  59. let cube_count = parse_u32(data)?; // parsed as i32 in-game for some reason
  60. let max_e_id = parse_u32(data)?; // unused
  61. let group_count = parse_u32(data)?; // parsed as i32 in-game for some reason
  62. // parse block groups
  63. let mut groups_h = Vec::<EntityHeader>::with_capacity(group_count as usize);
  64. let mut groups_e = Vec::<BlockGroupEntity>::with_capacity(group_count as usize);
  65. for _i in 0..group_count {
  66. groups_h.push(EntityHeader::parse(data)?);
  67. groups_e.push(BlockGroupEntity::parse(data)?);
  68. }
  69. // parse cube data
  70. let mut cubes_h = Vec::<EntityHeader>::with_capacity(cube_count as usize);
  71. let mut cubes_e = Vec::<Box<dyn SerializedEntityDescriptor>>::with_capacity(cube_count as usize);
  72. for _i in 0..cube_count {
  73. let header = EntityHeader::parse(data)?;
  74. let hash = header.hash;
  75. #[cfg(debug_assertions)]
  76. println!("Handling block {} (hash: {} id:{}/{} components: {})", cubes_h.len(), hash, header.entity_id, header.group_id, header.component_count);
  77. cubes_h.push(header);
  78. cubes_e.push(lookup_hashname(hash, data)?);
  79. }
  80. // parse wire data
  81. let wire_count = parse_u32(data)?;
  82. let mut wires_h = Vec::<EntityHeader>::with_capacity(wire_count as usize);
  83. let mut wires_e = Vec::<SerializedWireEntity>::with_capacity(wire_count as usize);
  84. for _i in 0..wire_count {
  85. wires_h.push(EntityHeader::parse(data)?);
  86. wires_e.push(SerializedWireEntity::parse(data)?);
  87. }
  88. // parse global wire settings
  89. let wire_settings_h = EntityHeader::parse(data)?;
  90. let wire_settings_e = SerializedGlobalWireSettingsEntity::parse(data)?;
  91. // parse player cameras
  92. let flycam_h = EntityHeader::parse(data)?;
  93. let flycam_e = SerializedFlyCamEntity::parse(data)?;
  94. let phycam_h = EntityHeader::parse(data)?;
  95. let phycam_e = SerializedPhysicsCameraEntity::parse(data)?;
  96. // build struct
  97. Ok(Self {
  98. version: date,
  99. ticks: ticks,
  100. cube_len: cube_count,
  101. max_entity_id: max_e_id,
  102. group_len: group_count,
  103. group_headers: groups_h,
  104. cube_groups: groups_e,
  105. cube_headers: cubes_h,
  106. cube_entities: cubes_e,
  107. wire_len: wire_count,
  108. wire_headers: wires_h,
  109. wire_entities: wires_e,
  110. wire_settings_header: wire_settings_h,
  111. wire_settings_entity: wire_settings_e,
  112. flycam_header: flycam_h,
  113. flycam_entity: flycam_e,
  114. phycam_header: phycam_h,
  115. phycam_entity: phycam_e,
  116. })
  117. }
  118. fn dump(&self, writer: &mut dyn Write) -> std::io::Result<usize> {
  119. let mut write_count: usize = 0;
  120. // version
  121. write_count += self.version.year().dump(writer)?;
  122. write_count += self.version.month().dump(writer)?;
  123. write_count += self.version.day().dump(writer)?;
  124. // unused separator \/
  125. write_count += self.ticks.dump(writer)?;
  126. write_count += self.cube_len.dump(writer)?;
  127. // unused separator \/
  128. write_count += self.max_entity_id.dump(writer)?;
  129. write_count += self.group_len.dump(writer)?;
  130. // dump block groups
  131. for i in 0..self.group_len as usize {
  132. write_count += self.group_headers[i].dump(writer)?;
  133. write_count += self.cube_groups[i].dump(writer)?;
  134. }
  135. // dump cube data
  136. for i in 0..self.cube_len as usize {
  137. write_count += self.cube_headers[i].dump(writer)?;
  138. write_count += self.cube_entities[i].dump(writer)?;
  139. }
  140. // dump wire data
  141. write_count += self.wire_len.dump(writer)?;
  142. for i in 0..self.wire_len as usize {
  143. write_count += self.wire_headers[i].dump(writer)?;
  144. write_count += self.wire_entities[i].dump(writer)?;
  145. }
  146. // dump global wire settings
  147. write_count += self.wire_settings_header.dump(writer)?;
  148. write_count += self.wire_settings_entity.dump(writer)?;
  149. // dump player cameras
  150. write_count += self.flycam_header.dump(writer)?;
  151. write_count += self.flycam_entity.dump(writer)?;
  152. write_count += self.phycam_header.dump(writer)?;
  153. write_count += self.phycam_entity.dump(writer)?;
  154. Ok(write_count)
  155. }
  156. }
  157. impl std::string::ToString for GameSave {
  158. fn to_string(&self) -> String {
  159. format!("{}g {}c {}w (v{})", self.group_len, self.cube_len, self.wire_len, self.version)
  160. }
  161. }