use std::io::{self, Cursor, Read};
use super::{
buff::{Buff, BuffEffect},
error::Error,
inventory::{InventorySlot, ItemSlot, Loadout, SingleItemSlot},
Difficulty, Player, SpawnPoint, Visibility, MAGIC_NUMBER, PLAYER_ENCRYPTION_KEY,
SUPPORTED_VERSIONS,
};
use crate::{
player::looks::{HairDye, Style},
round_division_i64, TerrariaFileType,
};
use aes::{
cipher::{BlockDecryptMut, KeyIvInit},
Aes128Dec,
};
use bounded_vector::{bvec, BoundedVec};
use byteorder::LittleEndian;
use byteorder::ReadBytesExt;
use cs_datetime_parse::DateTimeCs;
use cs_string_rw::ReadCsStrExt;
use inout::{block_padding::Pkcs7, InOutBuf};
use terra_items::{Item, Prefix};
use terra_types::{Color, NonNegativeI32, PositiveI32, Vec2};
use time::{ext::NumericalDuration, Duration};
type Aes128CbcDec = cbc::Decryptor<Aes128Dec>;
impl super::Player {
pub fn read_player_unencrypted<R: Read + ?Sized>(reader: &mut R) -> Result<Player, Error> {
let mut plr = Player::default();
PlayerReader::new(reader)?.read_player(&mut plr)?;
Ok(plr)
}
pub fn read_player<R: Read + ?Sized>(reader: &mut R) -> Result<Player, Error> {
let mut player_bytes = vec![];
if reader.read_to_end(&mut player_bytes)? % 16 != 0 {
return Err(io::Error::new(
io::ErrorKind::UnexpectedEof,
"Stream size isn't multiple of 16 bytes",
)
.into());
}
let decryptor =
Aes128CbcDec::new(&PLAYER_ENCRYPTION_KEY.into(), &PLAYER_ENCRYPTION_KEY.into());
decryptor
.decrypt_padded_mut::<Pkcs7>(&mut player_bytes[..])
.map_err(|_| {
Error::Io(io::Error::new(
io::ErrorKind::InvalidData,
"Invalid pkcs7 stream ending",
))
})?;
Self::read_player_unencrypted(&mut Cursor::new(player_bytes))
}
pub fn try_read_unencrypted<R: Read + ?Sized>(&mut self, reader: &mut R) -> Result<(), Error> {
PlayerReader::new(reader)?.read_player(self)
}
pub fn try_read<R: Read + ?Sized>(&mut self, reader: &mut R) -> Result<(), Error> {
let mut player_bytes = vec![];
let result_read = reader.read_to_end(&mut player_bytes);
let is_invalid_len = player_bytes.len() % 16 != 0;
let mut decryptor =
Aes128CbcDec::new(&PLAYER_ENCRYPTION_KEY.into(), &PLAYER_ENCRYPTION_KEY.into());
decryptor.decrypt_blocks_mut(
InOutBuf::from(&mut player_bytes[..])
.into_chunks()
.0
.into_out(),
);
let len = player_bytes.len();
let player_bytes = &mut player_bytes[..len / 16 * 16];
self.try_read_unencrypted(&mut Cursor::new(player_bytes))?;
let _ = result_read?;
if is_invalid_len {
Err(io::Error::new(
io::ErrorKind::UnexpectedEof,
"Stream size isn't multiple of 16 bytes",
)
.into())
} else {
Ok(())
}
}
}
struct PlayerReader<'a, R: Read + ?Sized> {
version: i32,
reader: &'a mut R,
}
impl<'a, R: Read + ?Sized> PlayerReader<'a, R> {
fn new(reader: &'a mut R) -> Result<PlayerReader<R>, Error> {
let version = reader.read_i32::<LittleEndian>()?;
if !SUPPORTED_VERSIONS.contains(&version) {
return Err(Error::UnsupportedVersion {
version,
version_bounds: SUPPORTED_VERSIONS,
});
}
Ok(Self { reader, version })
}
fn read_i64(&mut self) -> io::Result<i64> {
self.reader.read_i64::<LittleEndian>()
}
fn read_i32(&mut self) -> io::Result<i32> {
self.reader.read_i32::<LittleEndian>()
}
fn read_i16(&mut self) -> io::Result<i16> {
self.reader.read_i16::<LittleEndian>()
}
fn read_u8(&mut self) -> io::Result<u8> {
self.reader.read_u8()
}
fn read_f32(&mut self) -> io::Result<f32> {
self.reader.read_f32::<LittleEndian>()
}
fn read_bool(&mut self) -> io::Result<bool> {
Ok(self.read_u8()? != 0)
}
fn read_string(&mut self) -> io::Result<String> {
self.reader.read_cs_string()
}
fn read_item(&mut self) -> io::Result<Option<Item>> {
Ok(if self.version < 38 {
let id = self.reader.read_cs_string()?;
Item::from_old_id(&id, self.version)
} else {
let id = self.read_i32()?;
Item::from_id(id, self.version)
})
}
fn read_datetime(&mut self) -> Result<DateTimeCs, Error> {
Ok(DateTimeCs::from_binary(self.read_i64()?)?)
}
fn read_item_slot(&mut self) -> io::Result<Option<ItemSlot>> {
let item = self.read_item()?;
let count = PositiveI32::new(self.read_i32()?);
let prefix = self.read_prefix()?;
let Some(item) = item else { return Ok(None) };
let Some(count) = count else { return Ok(None) };
Ok(Some(ItemSlot {
item,
prefix,
count,
}))
}
fn read_inventory_slot(
&mut self,
is_read_is_favorite: bool,
) -> io::Result<Option<InventorySlot>> {
let item_slot = self.read_item_slot()?;
let is_favorite = if is_read_is_favorite {
self.read_bool()?
} else {
false
};
let Some(item_slot) = item_slot else {
return Ok(None);
};
Ok(Some(InventorySlot {
item: item_slot.item,
count: item_slot.count,
prefix: item_slot.prefix,
is_favorite,
}))
}
fn read_single_item_slot(&mut self) -> io::Result<Option<SingleItemSlot>> {
let item = self.read_item()?;
let prefix = self.read_prefix()?;
let Some(item) = item else { return Ok(None) };
Ok(Some(SingleItemSlot { item, prefix }))
}
fn verify_terraria_file(&mut self) -> Result<(), Error> {
let mut magic_number = [0u8; 7];
self.reader.read_exact(&mut magic_number)?;
if &magic_number != MAGIC_NUMBER {
return Err(Error::InvalidMagicNumber {
found: magic_number.to_vec(),
expected: MAGIC_NUMBER.to_vec(),
});
}
Ok(())
}
fn read_terraria_file_type(&mut self) -> io::Result<TerrariaFileType> {
Ok(TerrariaFileType::from_id(self.read_u8()?))
}
fn read_duration_cs_ticks(&mut self) -> io::Result<Duration> {
let cs_ticks = self.read_i64()?;
Ok(Duration::new(
cs_ticks / 10000000,
(cs_ticks % 10000000) as i32 * 100,
))
}
fn read_duration_terra_ticks(&mut self) -> io::Result<Duration> {
let nano_seconds = round_division_i64(self.read_i32()? as i64 * 1000000000, 60);
Ok(nano_seconds.nanoseconds())
}
fn read_color(&mut self) -> io::Result<Color> {
Ok(Color {
red: self.read_u8()?,
green: self.read_u8()?,
blue: self.read_u8()?,
})
}
fn read_prefix(&mut self) -> io::Result<Option<Prefix>> {
if self.version < 36 {
return Ok(None);
}
Ok(Prefix::from_id(self.read_u8()?))
}
fn read_difficulty(&mut self) -> io::Result<Difficulty> {
Ok(match self.version {
..=10 => Difficulty::SoftCore,
11..=17 => {
if self.read_bool()? {
Difficulty::Hardcore
} else {
Difficulty::SoftCore
}
}
_ => Difficulty::from_id(self.read_u8()?).unwrap_or_default(),
})
}
fn read_inventory(&mut self) -> io::Result<[[Option<InventorySlot>; 10]; 5]> {
let mut inventory = [[None; 10]; 5];
for row in inventory
.iter_mut()
.take(if self.version < 58 { 4 } else { 5 })
{
for item in row {
*item = self.read_inventory_slot(self.version >= 114)?;
}
}
Ok(inventory)
}
fn read_small_inventory(&mut self) -> io::Result<[Option<InventorySlot>; 4]> {
let mut small_inventory = [None; 4];
for item in small_inventory.iter_mut() {
*item = self.read_inventory_slot(self.version >= 114)?;
}
Ok(small_inventory)
}
fn read_container(&mut self) -> io::Result<[[Option<ItemSlot>; 10]; 4]> {
let mut container = [[None; 10]; 4];
for row in container.iter_mut() {
for item in row.iter_mut().take(if self.version >= 58 { 10 } else { 5 }) {
*item = self.read_item_slot()?;
}
}
Ok(container)
}
fn read_void_vault(&mut self) -> io::Result<Option<[[Option<InventorySlot>; 10]; 4]>> {
let mut inventory = [[None; 10]; 4];
if self.version >= 198 {
for row in inventory.iter_mut() {
for item in row {
*item = self.read_inventory_slot(self.version >= 255)?;
}
}
}
let is_vault_unlocked = if self.version >= 199 {
self.read_bool()?
} else {
false
};
let is_vault_unlocked = if is_vault_unlocked {
true
} else {
inventory.iter().any(|x| x.iter().any(|x| x.is_some()))
};
if !is_vault_unlocked {
return Ok(None);
}
Ok(Some(inventory))
}
fn read_visibility(&mut self) -> io::Result<Visibility> {
Ok(Visibility::from_id(self.read_i32()?).unwrap_or_default())
}
fn read_buffs(&mut self) -> io::Result<BoundedVec<BuffEffect, 0, 44>> {
let mut buffs = bvec![];
if self.version < 11 {
return Ok(buffs);
}
for _ in 0..(match self.version {
..=73 => 10,
74..=251 => 22,
252.. => 44,
}) {
let buff = Buff::from_id(self.read_i32()?);
let time = self.read_duration_terra_ticks()?;
if let Some(buff) = buff {
let _ = buffs.push(BuffEffect { buff, time });
}
}
Ok(buffs)
}
fn read_spawn_points(&mut self) -> io::Result<BoundedVec<SpawnPoint, 0, 200>> {
let mut spawn_points = bvec![];
for _ in 0..200 {
let x = self.read_i32()?;
if x == -1 {
break;
}
let x = NonNegativeI32::new(x).unwrap_or(NonNegativeI32::new(0).expect("In bounds"));
let y = NonNegativeI32::new(self.read_i32()?)
.unwrap_or(NonNegativeI32::new(0).expect("In bounds"));
let _ = spawn_points.push(SpawnPoint {
coords: Vec2::new(x, y),
world_id: self.read_i32()?,
world_name: self.read_string()?,
});
}
Ok(spawn_points)
}
fn read_loadout(&mut self) -> io::Result<Loadout> {
let mut loadout = Loadout::default();
loadout.helmet.armor = self.read_item_slot()?.map(|x| x.into());
loadout.breastplate.armor = self.read_item_slot()?.map(|x| x.into());
loadout.pants.armor = self.read_item_slot()?.map(|x| x.into());
for accessory in loadout.accessories.iter_mut() {
accessory.accessory = self.read_item_slot()?.map(|x| x.into());
}
loadout.helmet.vanity_armor = self.read_item_slot()?.map(|x| x.into());
loadout.breastplate.vanity_armor = self.read_item_slot()?.map(|x| x.into());
loadout.pants.vanity_armor = self.read_item_slot()?.map(|x| x.into());
for accessory in loadout.accessories.iter_mut() {
accessory.vanity_accessory = self.read_item_slot()?.map(|x| x.into());
}
loadout.helmet.dye = self.read_item_slot()?.map(|x| x.into());
loadout.breastplate.dye = self.read_item_slot()?.map(|x| x.into());
loadout.pants.dye = self.read_item_slot()?.map(|x| x.into());
for accessory in loadout.accessories.iter_mut() {
accessory.dye = self.read_item_slot()?.map(|x| x.into());
}
for _ in 0..3 {
self.read_bool()?;
}
for accessory in loadout.accessories.iter_mut() {
accessory.is_accessory_shown = !self.read_bool()?;
}
Ok(loadout)
}
fn read_player(&mut self, plr: &mut Player) -> Result<(), Error> {
let version = self.version;
if version >= 135 {
self.verify_terraria_file()?;
let terraria_file_type = self.read_terraria_file_type()?;
if terraria_file_type != TerrariaFileType::Player {
return Err(Error::InvalidFileType {
found: terraria_file_type,
expected: TerrariaFileType::Player,
});
}
self.read_i32()?; plr.is_favorite = self.read_i64()? != 0;
}
plr.name = self.read_string()?;
plr.difficulty = self.read_difficulty()?;
if version >= 138 {
plr.playtime = Some(self.read_duration_cs_ticks()?)
}
plr.hair_style = self.read_i32()?;
if version >= 83 {
plr.hair_dye = HairDye::from_id(self.read_u8()?);
}
if version >= 83 {
let mut bytes = if version >= 124 {
self.reader.read_u16::<LittleEndian>()?
} else {
self.read_u8()? as u16
} >> 3;
for accessory_row in plr.loadouts.loadouts[0].accessories.iter_mut() {
accessory_row.is_accessory_shown = bytes & 1 != 1;
bytes >>= 1;
}
}
if version >= 119 {
let byte = self.read_u8()?;
plr.pet.is_shown = byte & 1 != 1;
plr.light_pet.is_shown = byte >> 1 & 1 != 1;
}
plr.style = match version {
..=17 => match plr.hair_style {
5 | 6 | 9 | 11 => Style::FemaleStarter,
_ => Style::MaleStarter,
},
18..=107 => {
if self.read_bool()? {
Style::MaleStarter
} else {
Style::FemaleStarter
}
}
_ => Style::from_id(self.read_u8()?, version).unwrap_or(Style::MaleCoat),
};
plr.life = self.read_i32()?;
plr.max_life = self.read_i32()?;
plr.mana = self.read_i32()?;
plr.max_mana = self.read_i32()?;
if version >= 125 {
plr.is_demon_heart_accessory_slot_unlocked = self.read_bool()?;
}
let is_biome_torches_unlocked = if version >= 229 {
self.read_bool()?
} else {
false
};
if version >= 229 {
self.read_bool()?; }
if version >= 256 {
plr.is_artisan_bread_eaten = self.read_bool()?;
}
if version >= 260 {
plr.is_aegis_crystal_used = self.read_bool()?;
plr.is_aegis_fruit_used = self.read_bool()?;
plr.is_arcane_crystal_used = self.read_bool()?;
plr.is_galaxy_pearl_used = self.read_bool()?;
plr.is_gummy_worm_used = self.read_bool()?;
plr.is_ambrosia_used = self.read_bool()?;
}
if version >= 182 {
plr.is_dd2_event_downed = self.read_bool()?;
}
if version >= 128 {
plr.tax_money_in_copper_coins = self.read_i32()?;
}
if version >= 254 {
plr.deaths_not_caused_by_player_count = self.read_i32()?;
}
if version >= 254 {
plr.deaths_caused_by_player_count = self.read_i32()?;
}
plr.hair_color = self.read_color()?;
plr.skin_color = self.read_color()?;
plr.eye_color = self.read_color()?;
plr.shirt_color = self.read_color()?;
plr.under_shirt_color = self.read_color()?;
plr.pants_color = self.read_color()?;
plr.shoe_color = self.read_color()?;
let loadout = &mut plr.loadouts.loadouts[0];
loadout.helmet.armor = self.read_single_item_slot()?;
loadout.breastplate.armor = self.read_single_item_slot()?;
loadout.pants.armor = self.read_single_item_slot()?;
for accessory in loadout
.accessories
.iter_mut()
.take(if version >= 124 { 7 } else { 5 })
{
accessory.accessory = self.read_single_item_slot()?;
}
if version >= 6 {
loadout.helmet.vanity_armor = self.read_single_item_slot()?;
loadout.breastplate.vanity_armor = self.read_single_item_slot()?;
loadout.pants.vanity_armor = self.read_single_item_slot()?;
}
for accessory in loadout.accessories.iter_mut().take(match version {
..=80 => 0,
81..=123 => 5,
124.. => 7,
}) {
accessory.vanity_accessory = self.read_single_item_slot()?;
}
if version >= 47 {
loadout.helmet.dye = self.read_single_item_slot()?;
loadout.breastplate.dye = self.read_single_item_slot()?;
loadout.pants.dye = self.read_single_item_slot()?;
}
for accessory in loadout.accessories.iter_mut().take(match version {
..=80 => 0,
81..=123 => 5,
124.. => 7,
}) {
accessory.dye = self.read_single_item_slot()?;
}
plr.inventory = self.read_inventory()?;
plr.coins = self.read_small_inventory()?;
if version >= 15 {
plr.ammo = self.read_small_inventory()?;
}
if version >= 136 {
plr.pet.item = self.read_single_item_slot()?;
plr.pet.dye = self.read_single_item_slot()?;
}
if version >= 117 {
plr.light_pet.item = self.read_single_item_slot()?;
plr.light_pet.dye = self.read_single_item_slot()?;
plr.minecart.item = self.read_single_item_slot()?;
plr.minecart.dye = self.read_single_item_slot()?;
plr.mount.item = self.read_single_item_slot()?;
plr.mount.dye = self.read_single_item_slot()?;
plr.hook.item = self.read_single_item_slot()?;
plr.hook.dye = self.read_single_item_slot()?;
}
plr.piggy_bank = self.read_container()?;
if version >= 20 {
plr.safe = self.read_container()?;
}
if version >= 182 {
plr.defenders_forge = self.read_container()?;
}
plr.void_vault = self.read_void_vault()?;
plr.buffs = self.read_buffs()?;
plr.spawn_points = self.read_spawn_points()?;
if version >= 16 {
plr.is_hotbar_locked = self.read_bool()?;
}
if version >= 115 {
plr.is_time_info_shown = !self.read_bool()?;
plr.is_weather_info_shown = !self.read_bool()?;
plr.is_fishing_power_info_shown = !self.read_bool()?;
plr.is_position_info_shown = !self.read_bool()?;
plr.is_depth_info_shown = !self.read_bool()?;
plr.is_creature_count_info_shown = !self.read_bool()?;
plr.is_kill_count_info_shown = !self.read_bool()?;
plr.is_moon_phase_info_shown = !self.read_bool()?;
self.read_bool()?; plr.is_movement_speed_info_shown = !self.read_bool()?;
plr.is_treasure_finder_info_shown = !self.read_bool()?;
plr.is_rare_creatures_finder_info_shown = !self.read_bool()?;
plr.is_damage_per_second_info_shown = !self.read_bool()?;
plr.finished_angler_quests_count = self.read_i32()?;
}
if version >= 164 {
plr.dpad_shortcuts.up = NonNegativeI32::new(self.read_i32()?);
plr.dpad_shortcuts.right = NonNegativeI32::new(self.read_i32()?);
plr.dpad_shortcuts.down = NonNegativeI32::new(self.read_i32()?);
plr.dpad_shortcuts.left = NonNegativeI32::new(self.read_i32()?);
plr.is_ruler_enabled = self.read_i32()? == 0;
plr.is_mechanical_ruler_enabled = self.read_i32()? == 0;
plr.is_auto_paint_enabled = self.read_i32()? == 0;
plr.is_auto_paint_enabled = self.read_i32()? == 0;
plr.red_wires_visibility = self.read_visibility()?;
plr.blue_wires_visibility = self.read_visibility()?;
plr.green_wires_visibility = self.read_visibility()?;
plr.yellow_wires_visibility = self.read_visibility()?;
}
if version >= 167 {
plr.is_always_showing_wires_and_actuators = self.read_i32()? == 0;
plr.actuators_visibility = self.read_visibility()?;
}
if version >= 197 {
plr.is_tile_replacement_enabled = self.read_i32()? == 0;
}
if version >= 230 {
let is_biome_torches_enabled = self.read_i32()? == 0;
plr.is_using_biome_torches = if is_biome_torches_unlocked {
Some(is_biome_torches_enabled)
} else {
None
}
}
if version >= 181 {
plr.is_talked_to_bartender = self.read_i32()? == 1;
}
if version >= 200 {
plr.time_to_respawn = if self.read_bool()? {
Some(self.read_duration_terra_ticks()?)
} else {
None
}
}
if version >= 202 {
plr.last_time_player_was_saved = Some(self.read_datetime()?);
}
if version >= 206 {
plr.golfer_score_accumulated = self.read_i32()?;
}
if version >= 218 {
for _ in 0..self.read_i32()? {
let code_name = self.read_string()?;
let count = self.read_i32()?;
let Some(item) = Item::from_code_name(&code_name) else {
continue;
};
plr.item_researched_count.insert(item, count);
}
}
if version >= 214 {
let byte = self.read_u8()?;
if byte & 1 == 1 {
plr.temporary_mouse_item = self.read_item_slot()?;
}
if byte >> 1 & 1 == 1 {
plr.temporary_research_item = self.read_item_slot()?;
}
if byte >> 2 & 1 == 1 {
plr.temporary_guide_item = self.read_item_slot()?;
}
if byte >> 3 & 1 == 1 {
plr.temporary_goblin_item = self.read_item_slot()?;
}
}
if version >= 220 {
while self.read_bool()? {
let key = self.read_i16()?;
match key {
5 => plr.is_godmode_enabled = self.read_bool()?,
11 => plr.is_far_placement_enabled = self.read_bool()?,
14 => plr.spawn_rate_factor = self.read_f32()? * 2f32,
_ => {}
}
}
}
if version >= 253 {
let byte = self.read_u8()?;
plr.is_super_cart_enabled = if byte & 1 == 1 {
Some(byte & 2 == 2)
} else {
None
}
}
if version >= 262 {
plr.loadouts.selected_loadout_index = self.read_i32()?;
self.read_loadout()?;
for loadout in plr.loadouts.loadouts.iter_mut().skip(1) {
*loadout = self.read_loadout()?;
}
}
Ok(())
}
}