1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
//! Interfaces for reading and writing Brickadia save files.
//!
//! Aims to be able to read all previous versions just like the game,
//! but only write the newest version of the format.
//!
//! # Usage
//!
//! ## Reading
//!
//! First, create a reader from any
//! [`Read`](https://doc.rust-lang.org/std/io/trait.Read.html)
//! source, such as a file or buffer.
//!
//! ```no_run
//! # use std::fs::File;
//! let reader = brs::Reader::new(File::open("village.brs")?)?;
//! # Ok::<(), std::io::Error>(())
//! ```
//!
//! Brickadia save files have information split into sections ordered
//! such that one can extract simple information
//! without needing to parse the entire file.
//!
//! This library surfaces this by strictly enforcing the way that data is read
//! and made available at the type level; you can't go wrong.
//!
//! To continue, reading the first header gets you basic information.
//! For details on what is available, see
//! [`HasHeader1`](read/trait.HasHeader1.html).
//!
//! ```no_run
//! use brs::HasHeader1;
//! # let reader: brs::Reader<std::fs::File> = unimplemented!();
//! let reader = reader.read_header1()?;
//! println!("Brick count: {}", reader.brick_count());
//! println!("Map: {}", reader.map());
//! # Ok::<(), std::io::Error>(())
//! ```
//!
//! The next header contains data less likely to be relevant for simpler
//! introspection, but rather things such as tables for loading bricks.
//! See [`HasHeader2`](read/trait.HasHeader2.html).
//!
//! ```no_run
//! use brs::HasHeader2;
//! # let reader: brs::read::ReaderAfterHeader1<std::fs::File> = unimplemented!();
//! let reader = reader.read_header2()?;
//! println!("Mods: {:?}", reader.mods());
//! println!("Color count: {}", reader.colors().len());
//! // Properties from header 1 are still available:
//! use brs::HasHeader1;
//! println!("Description: {}", reader.description());
//! # Ok::<(), std::io::Error>(())
//! ```
//!
//! After both headers have been read, you may now iterate over the bricks.
//! See [`Brick`](struct.Brick.html).
//!
//! ```no_run
//! # let reader: brs::read::ReaderAfterHeader2<std::fs::File> = unimplemented!();
//! for brick in reader.iter_bricks()? {
//!     let brick = brick?;
//!     println!("{:?}", brick);
//! }
//! # Ok::<(), std::io::Error>(())
//! ```
//!
//! You may retain access to the header information while getting the iterator:
//!
//! ```no_run
//! # let reader: brs::read::ReaderAfterHeader2<std::fs::File> = unimplemented!();
//! let (reader, bricks) = reader.iter_bricks_and_reader()?;
//! # Ok::<(), std::io::Error>(())
//! ```
//!
//! ## Writing
//!
//! Writing save files isn't as fancy, for now you simply just put all the data
//! in the [`WriteData`](struct.WriteData.html) struct and pass it to
//! [`write_save`](fn.write_save.html) along with a
//! [`Write`](https://doc.rust-lang.org/std/io/trait.Write.html) destination.
//!
//! ```no_run
//! # use std::fs::File;
//! let data = brs::WriteData {
//!     map: "Plate".to_string(),
//!     author: brs::User {
//!         id: brs::uuid::Uuid::nil(),
//!         name: "Jensen".to_string(),
//!     },
//!     description: "A quaint park full of ducks and turkeys.".to_string(),
//!     save_time: chrono::Utc::now(),
//!
//!     mods: Vec::new(),
//!     brick_assets: vec!["PB_DefaultBrick".to_string()],
//!     colors: vec![brs::Color::from_rgba(255, 23, 198, 255)],
//!     materials: vec!["BMC_Plastic".to_string()],
//!     brick_owners: Vec::new(),
//!
//!     bricks: Vec::new(),
//! };
//! brs::write_save(&mut File::create("park.brs")?, &data)?;
//! # Ok::<(), std::io::Error>(())
//! ```

mod bit_reader;
mod bit_writer;
mod save;

pub mod read;
mod write;

pub use read::{HasHeader1, HasHeader2, Reader};
pub use save::*;
pub use write::{write_save, WriteData};

pub use chrono;
pub use uuid;

use chrono::prelude::*;
use num_enum::{IntoPrimitive, TryFromPrimitive};

const MAGIC: [u8; 3] = [b'B', b'R', b'S'];

/// A save file version.
#[repr(u16)]
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, IntoPrimitive, TryFromPrimitive)]
pub enum Version {
    Initial = 1,
    MaterialsStoredAsNames,
    AddedOwnerData,
    AddedDateTime,
}

/// The version that will be written.
pub const VERSION_WRITE: Version = Version::AddedDateTime;

fn ue4_date_time_base() -> DateTime<Utc> {
    Utc.ymd(1, 1, 1).and_hms(0, 0, 0)
}