mod random_level_worker;
mod random;
use fancy_regex::{Regex, SubCaptureMatches};
use rusqlite::{Connection, Result};
use serde::{Deserialize, Serialize};
use serde_json;
use snap;
use snap::raw::{Decoder, Encoder};
use core::time;
use std::collections::HashMap;
use std::fs::{self, create_dir, Metadata};
use std::time::SystemTime;
#[derive(Serialize, Deserialize, Debug)]
pub struct Data {
pub js_level: JSLevel,
pub settings: Settings
}
impl Data {
pub fn new (js_level: JSLevel, settings: Settings) -> Self {
Data {js_level, settings}
}
}
#[derive(Serialize, Deserialize, Debug)]
pub struct JSLevel {
pub worldSeed: i64,
pub changedBlocks: HashMap<String,ChangedBlocks>,
pub worldSize: i32,
pub version: u8
}
impl JSLevel {
pub fn new (worldSeed: i64, changedBlocks: HashMap<String,ChangedBlocks>, worldSize: i32, version: u8) -> Self {
JSLevel { worldSeed, changedBlocks, worldSize, version }
}
pub fn default () -> Self {
JSLevel { worldSeed: 1, changedBlocks: HashMap::new(), worldSize: 256, version: 1 }
}
}
#[derive(Serialize, Deserialize, Debug)]
pub struct ChangedBlocks {pub a: u8, pub bt: u8}
impl ChangedBlocks { pub fn new (a: u8, bt: u8) -> Self {ChangedBlocks { a, bt }}}
#[derive(Serialize, Deserialize, Debug)]
pub struct Settings {
pub music: bool,
pub sound: bool,
pub invert: bool,
pub fps: bool,
pub drawDistance: i32,
pub forward: String,
pub left: String,
pub backward: String,
pub right: String,
pub jump: String,
pub build: String,
pub chat: String,
pub fog: String,
pub saveLoc: String,
pub loadLoc: String,
pub username: String
}
impl Settings {
pub fn new(
music: bool,
sound: bool,
invert: bool,
fps: bool,
drawDistance: i32,
forward: String,
left: String,
backward: String,
right: String,
jump: String,
build: String,
chat: String,
fog: String,
saveLoc: String,
loadLoc: String,
username: String
) -> Self {
Settings { music, sound, invert, fps, drawDistance, forward, left, backward, right, jump, build, chat, fog, saveLoc, loadLoc, username }
}
pub fn default () -> Self {
Settings {
music: false,
sound: true,
invert: false,
fps: false,
drawDistance: 0,
forward: String::from("W"),
left: String::from("A"),
backward: String::from("S"),
right: String::from("D"),
jump: String::from("<space>"),
build: String::from("B"),
chat: String::from("T"),
fog: String::from("F"),
saveLoc: String::from("<enter>"),
loadLoc: String::from("R"),
username: String::from("noname")
}
}
}
pub struct LocalStorage {
key: String,
utf16_length: i32,
conversion_type: i32,
compression_type: i32,
last_access_time: i32,
value: Vec<u8>
}
pub fn deserialize_saved_game (json_string: String) -> JSLevel {
let level: JSLevel = serde_json::from_str(&json_string).unwrap();
return level;
}
pub fn deserialize_settings (json_string: String) -> Settings {
let settings: Settings = serde_json::from_str(&json_string).unwrap();
return settings;
}
pub fn deserialize_data (json_string1: String, json_string2: String) -> Data {
let level: JSLevel = serde_json::from_str(&json_string1).unwrap();
let settings: Settings = serde_json::from_str(&json_string2).unwrap();
return Data { js_level: level, settings: settings}
}
pub fn serialize_saved_game (level: JSLevel, tile_map: Vec<u8>, opt: u8) -> String {
let x: i32 = level.worldSize;
let y: i32 = 64;
let z: i32 = level.worldSize;
let tile_map1 = get_tile_map(level.worldSize, level.worldSeed);
let mut output: String = String::from("{");
output += &format!(r#""worldSeed":{},"#,level.worldSeed.to_string());
output += r#""changedBlocks":"#; output += "{";
let mut t: u8;
let mut t1: u8;
let mut a: u8;
let mut flag: bool = false;
for i in 0..y {
for j in 0..z {
for k in 0..x {
let mut flag1 = false;
let key: String = String::from(format!(r#"p{}_{}_{}"#,k,i,j));
let bt: u8 = level.changedBlocks.get(&key).unwrap_or(&ChangedBlocks::new(1,255)).bt;
t = tile_map[((i*z*x) + (j*x) + k) as usize];
t1 = tile_map1[((i*z*x) + (j*x) + k) as usize];
if bt != 255 { t = bt }
if t == t1 { a = 0 } else { a = 1 }
if (opt == 2 && a == 1) || (opt == 1 && (bt != 255 || a == 1)) || opt == 0 { flag1 = true }
if flag1 {
output += &key;
output += "{";
output += &format!(r#""a":{},"bt":{}"#,a,t);
output += "},";
flag = true;
}
}
}
}
if flag {output.pop();} output += "},";
output += &format!{r#""worldSize":{},"#,level.worldSize}; output += &format!{r#""version":{}"#,level.version};
output += "}"; return output;
}
pub fn serialize_settings (settings: Settings) -> String {
let mut output: String = String::from("{"); output += &format!{r#""music":{},"#,settings.music};
output += &format!{r#""sound":{},"#,settings.sound};
output += &format!{r#""invert":{},"#,settings.invert};
output += &format!{r#""fps":{},"#,settings.fps};
output += &format!{r#""drawDistance":{},"#,settings.drawDistance};
output += &format!{r#""forward":"{}","#,settings.forward};
output += &format!{r#""left":"{}","#,settings.left};
output += &format!{r#""backward":"{}","#,settings.backward};
output += &format!{r#""right":"{}","#,settings.right};
output += &format!{r#""jump":"{}","#,settings.jump};
output += &format!{r#""build":"{}","#,settings.build};
output += &format!{r#""chat":"{}","#,settings.chat};
output += &format!{r#""fog":"{}","#,settings.fog};
output += &format!{r#""saveLoc":"{}","#,settings.saveLoc};
output += &format!{r#""loadLoc":"{}","#,settings.loadLoc};
output += &format!{r#""username":"{}""#,settings.username};
output += "}"; return output;
}
pub fn serialize_data (data: Data) -> [String; 2] {
let tile_map = get_tile_map(data.js_level.worldSize, data.js_level.worldSeed);
let level_str: String = serialize_saved_game(data.js_level, tile_map, 1);
let settings_str: String = serialize_settings(data.settings);
return [level_str, settings_str]
}
pub fn read_from_db (file_path: String, object: &str) -> Result<String> {
let conn: Connection = Connection::open(file_path)?;
let mut stmt = conn.prepare(
"SELECT * FROM data where key=?1;"
)?;
let entries = stmt.query_map([object], |row| Ok(
LocalStorage {
key: row.get(0)?,
utf16_length: row.get(1)?,
conversion_type: row.get(2)?,
compression_type: row.get(3)?,
last_access_time: row.get(4)?,
value: row.get(5)?,
}
))?;
let mut compressed_object: Vec<u8> = Vec::new();
let mut decompressed_length: i32 = 0;
for entry in entries {
let local: LocalStorage = entry.unwrap();
if local.key == object {
compressed_object = local.value;
decompressed_length = local.utf16_length;
break;
}
}
let mut decompressed: Vec<u8> = Vec::new();
for _ in 0..decompressed_length {
decompressed.push(0);
}
Decoder::decompress(&mut Decoder::new(), &compressed_object, &mut decompressed).unwrap();
let mut characters: Vec<char> = Vec::new();
for ch in decompressed {
characters.push(ch as char)
}
Ok(characters.iter().collect())
}
pub fn read_saved_game (file_path: String) -> Result<String> {
return read_from_db(file_path, "savedGame");
}
pub fn read_settings (file_path: String) -> Result<String> {
return read_from_db(file_path, "settings");
}
pub fn write_data (file_path: String, json_strings: [String; 2], website: String) -> Result<()> {
let timestamp = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap().as_micros() as u64;
let regex = Regex::new(r#"/|:|\*|\?|"|>|<|\||\\"#).unwrap();
let substitution = "+";
let dir_name = regex.replace_all(&website, substitution);
create_dir(file_path.clone() + "/" + &dir_name);
create_dir(file_path.clone() + "/" + &dir_name + "/ls");
let mut metadata: Vec<u8> = Vec::new();
metadata.extend_from_slice(×tamp.to_be_bytes()); metadata.push(0); metadata.extend_from_slice(&(0 as i32).to_be_bytes()); metadata.extend_from_slice(&(0 as i32).to_be_bytes());
metadata.extend_from_slice(&(website.len() as u16).to_be_bytes());
metadata.extend_from_slice(website.as_bytes());
metadata.push(0);
fs::write(file_path.clone() + "/" + &dir_name + "/.metadata-v2", metadata);
let keys: Vec<&str> = vec!["savedGame", "settings"];
let conn: Connection = Connection::open(file_path.clone() + "/" + &dir_name + "/ls/data.sqlite")?;
conn.pragma_update(None, "user_version", 80);
conn.pragma_update(None, "auto_vacuum", 2);
conn.pragma_update(None, "page_size", 1024);
conn.execute("VACUUM", []);
conn.execute(
"CREATE TABLE if not exists data (
key TEXT PRIMARY KEY,
utf16_length INTEGER NOT NULL,
conversion_type INTEGER NOT NULL,
compression_type INTEGER NOT NULL,
last_access_time INTEGER NOT NULL DEFAULT 0,
value BLOB NOT NULL)",
[]
)?;
let mut len = 0;
let mut stmt = conn.prepare("INSERT OR REPLACE INTO data (key, utf16_length, conversion_type, compression_type, value) values (?1, ?2, ?3, ?4, ?5)" )?;
for i in 0..json_strings.len() {
let utf16_length: i32 = json_strings[i].len() as i32;
len += utf16_length;
let mut decompressed: Vec<u8> = Vec::new();
decompressed.extend_from_slice(json_strings[i].as_bytes());
let max_comp_length = snap::raw::max_compress_len(decompressed.len());
let mut compressed: Vec<u8> = Vec::new();
for _ in 0..max_comp_length {
compressed.push(0);
}
Encoder::compress(&mut Encoder::new(), &decompressed, & mut compressed).unwrap();
let mut b: u8 = 0;
while b == 0 {
b = compressed.pop().unwrap();
}
compressed.push(b);
stmt.execute((keys[i], utf16_length, 1, 1, compressed))?;
}
len += 10;
let vacuum_size = fs::metadata(file_path.clone() + "/" + &dir_name + "/ls/data.sqlite").unwrap().len();
conn.execute(
"CREATE TABLE if not exists database (
origin TEXT NOT NULL,
usage INTEGER NOT NULL DEFAULT 0,
last_vacuum_time INTEGER NOT NULL DEFAULT 0,
last_analyze_time INTEGER NOT NULL DEFAULT 0,
last_vacuum_size INTEGER NOT NULL DEFAULT 0)",
[])?;
stmt = conn.prepare("INSERT OR REPLACE INTO database (origin,usage,last_vacuum_time,last_analyze_time,last_vacuum_size) values (?1, ?2, ?3, ?4, ?5)" )?;
stmt.execute((&website,len,timestamp,0,vacuum_size))?;
fs::write(file_path.clone() + "/" + &dir_name + "/ls/usage", "");
Ok(())
}
pub fn write_saved_game (file_path: String, json_string: String, website: String) -> Result<()> {
let settings: String = serialize_settings(Settings::default());
write_data(file_path, [json_string,settings], website);
return Ok(());
}
pub fn write_saved_game_command (file: String, json_string: String) -> String {
let open: String = String::from(r#"localStorage.setItem("savedGame", `"#); let close: String = String::from(r#"`)"#);
let output: String = String::from(format!{r"{open}{json_string}{close}"});
if file != "" {fs::write(file, output.clone()).expect("Error when writing to file")}
return output;
}
pub fn write_settings_command (file: String, json_string: String) -> String {
let open: String = String::from(r#"localStorage.setItem("settings", `"#); let close: String = String::from(r#"`)"#);
let output: String = String::from(format!{r"{open}{json_string}{close}"});
if file != "" {fs::write(file, output.clone()).expect("Error when writing to file")}
return output;
}
pub fn write_local_storage_command (file: String, json_strings: [String; 2]) -> String {
let open: String = String::from(r#"localStorage.setItem("savedGame", `"#); let close: String = String::from(r#"`)"#); let mut string: String = json_strings[0].clone();
let mut output: String = String::from(format!{r"{open}{string}{close}"});
output += ";";
string = json_strings[1].clone();
output += &format!{r"{open}{string}{close}"};
if file != "" {fs::write(file, output.clone()).expect("Error when writing to file")}
return output;
}
pub fn generate_saved_game_from_seed (seed: i64, tile_map: Vec<u8>) -> JSLevel {
let world_size: i32 = ((tile_map.len()/64) as f64).sqrt() as i32;
let changed_blocks: HashMap<String, ChangedBlocks> = HashMap::new();
let level = JSLevel::new(seed, changed_blocks, world_size, 1);
return deserialize_saved_game(serialize_saved_game(level, tile_map, 2));
}
pub fn get_tile_map (world_size: i32, seed: i64) -> Vec<u8> {
let y: i32 = 64;
let level: HashMap<usize, u8> = random_level_worker::start_generation(world_size, seed); let mut tile_map: Vec<u8> = Vec::new();
for i in 0..world_size * y * world_size {
tile_map.push(level.get(&(i as usize)).copied().unwrap_or(0)); }
return tile_map
}
#[deprecated(since="0.2.0", note="please use `generate_saved_game_from_seed` instead")]
pub fn serialize_saved_game_from_seed (seed: i64, tile_map: Vec<u8>) -> String {
let world_size: i32 = ((tile_map.len()/64) as f64).sqrt() as i32;
let changed_blocks: HashMap<String, ChangedBlocks> = HashMap::new();
let level = JSLevel::new(seed, changed_blocks, world_size, 1);
return serialize_saved_game(level, tile_map, 2);
}