diff --git a/src/bin/main.rs b/src/bin/main.rs index 18fdfbd..e50db30 100644 --- a/src/bin/main.rs +++ b/src/bin/main.rs @@ -4,7 +4,7 @@ use log::{info}; use networking::interserver::AuthToken; use elseware::login::login::LoginServerState; use elseware::login::character::CharacterServerState; -use elseware::patch::patch::{PatchServerState, generate_patch_tree, load_config, load_motd}; +use elseware::patch::{PatchServerState, generate_patch_tree, load_config, load_motd}; use elseware::ship::ship::ShipServerStateBuilder; use maps::Holiday; diff --git a/src/bin/patch.rs b/src/bin/patch.rs index fc9eff8..a9fb839 100644 --- a/src/bin/patch.rs +++ b/src/bin/patch.rs @@ -1,4 +1,4 @@ -use elseware::patch::patch::{PatchServerState, generate_patch_tree, load_config_env, load_motd}; +use elseware::patch::{PatchServerState, generate_patch_tree, load_config_env, load_motd}; use log::info; fn main() { diff --git a/src/patch/mod.rs b/src/patch/mod.rs index 323a777..565530d 100644 --- a/src/patch/mod.rs +++ b/src/patch/mod.rs @@ -1,2 +1,432 @@ -#[allow(clippy::module_inception)] -pub mod patch; +use std::collections::{HashMap, HashSet}; +use std::fs; +use std::io; +use std::io::{Read}; +use std::path::{Path, PathBuf}; +use rand::Rng; +use crc::{crc32, Hasher32}; +use libpso::{PacketParseError, PSOPacket}; +use libpso::packet::patch::*; +use libpso::crypto::pc::PSOPCCipher; +use ron::de::from_str; +use serde::Deserialize; + +use networking::mainloop::{NetworkError}; +use networking::serverstate::{RecvServerPacket, SendServerPacket, ServerState, OnConnect, ClientId}; + +#[derive(Debug)] +pub enum PatchError { + NetworkError(NetworkError), + IOError(std::io::Error), +} + +impl From for PatchError { + fn from(err: NetworkError) -> PatchError { + PatchError::NetworkError(err) + } +} + +impl From for PatchError { + fn from(err: std::io::Error) -> PatchError { + PatchError::IOError(err) + } +} + + +#[derive(Debug, Clone)] +pub struct PatchFile { + path: PathBuf, + checksum: u32, + size: u32, +} + +pub enum PatchTreeIterItem { + Directory(PathBuf), + File(PathBuf, u32), + UpDirectory, +} + +#[derive(Debug, Clone)] +pub enum PatchFileTree { + Directory(PathBuf, Vec), + File(PathBuf, u32), // file_id +} + +impl PatchFileTree { + fn iter_dir(tree: &PatchFileTree) -> Vec { + let mut v = Vec::new(); + + match tree { + PatchFileTree::Directory(dir, files) => { + v.push(PatchTreeIterItem::Directory(dir.clone())); + for file in files { + v.append(&mut PatchFileTree::iter_dir(file)); + } + v.push(PatchTreeIterItem::UpDirectory); + }, + PatchFileTree::File(path, id) => { + v.push(PatchTreeIterItem::File(path.clone(), *id)); + } + } + + v + } + + pub fn flatten(&self) -> Vec { + PatchFileTree::iter_dir(self) + } +} + + +#[derive(Debug)] +pub enum RecvPatchPacket { + PatchWelcomeReply(PatchWelcomeReply), + LoginReply(LoginReply), + FileInfoReply(FileInfoReply), + FileInfoListEnd(FileInfoListEnd), +} + +impl RecvServerPacket for RecvPatchPacket { + fn from_bytes(data: &[u8]) -> Result { + match data[2] { + 0x02 => Ok(RecvPatchPacket::PatchWelcomeReply(PatchWelcomeReply::from_bytes(data)?)), + 0x04 => Ok(RecvPatchPacket::LoginReply(LoginReply::from_bytes(data)?)), + 0x0F => Ok(RecvPatchPacket::FileInfoReply(FileInfoReply::from_bytes(data)?)), + 0x10 => Ok(RecvPatchPacket::FileInfoListEnd(FileInfoListEnd::from_bytes(data)?)), + _ => Err(PacketParseError::WrongPacketForServerType(u16::from_le_bytes([data[2], data[3]]), data.to_vec())) + } + } +} + +#[derive(Debug)] +pub enum SendPatchPacket { + ChangeDirectory(ChangeDirectory), + EndFileSend(EndFileSend), + FileInfo(FileInfo), + FileSend(Box), + FilesToPatchMetadata(FilesToPatchMetadata), + FinalizePatching(FinalizePatching), + Message(Message), + PatchEndList(PatchEndList), + PatchStartList(PatchStartList), + PatchWelcome(PatchWelcome), + RequestLogin(RequestLogin), + StartFileSend(StartFileSend), + UpOneDirectory(UpOneDirectory), +} + +impl SendServerPacket for SendPatchPacket { + fn as_bytes(&self) -> Vec { + match self { + SendPatchPacket::ChangeDirectory(pkt) => pkt.as_bytes(), + SendPatchPacket::EndFileSend(pkt) => pkt.as_bytes(), + SendPatchPacket::FileInfo(pkt) => pkt.as_bytes(), + SendPatchPacket::FileSend(pkt) => pkt.as_bytes(), + SendPatchPacket::FilesToPatchMetadata(pkt) => pkt.as_bytes(), + SendPatchPacket::FinalizePatching(pkt) => pkt.as_bytes(), + SendPatchPacket::Message(pkt) => pkt.as_bytes(), + SendPatchPacket::PatchEndList(pkt) => pkt.as_bytes(), + SendPatchPacket::PatchStartList(pkt) => pkt.as_bytes(), + SendPatchPacket::PatchWelcome(pkt) => pkt.as_bytes(), + SendPatchPacket::RequestLogin(pkt) => pkt.as_bytes(), + SendPatchPacket::StartFileSend(pkt) => pkt.as_bytes(), + SendPatchPacket::UpOneDirectory(pkt) => pkt.as_bytes(), + } + } +} + + +#[derive(Clone)] +pub struct PatchServerState { + patch_file_tree: PatchFileTree, + patch_file_lookup: HashMap, + patch_file_info: Vec, + patch_motd: String, +} + +impl PatchServerState { + pub fn new(patch_file_tree: PatchFileTree, patch_file_lookup: HashMap, patch_motd: String) -> PatchServerState { + PatchServerState { + patch_file_tree, + patch_file_lookup, + patch_file_info: Vec::new(), + patch_motd, + } + } +} + +#[async_trait::async_trait] +impl ServerState for PatchServerState { + type SendPacket = SendPatchPacket; + type RecvPacket = RecvPatchPacket; + type Cipher = PSOPCCipher; + type PacketError = PatchError; + + async fn on_connect(&mut self, _id: ClientId) -> Result>, PatchError> { + let mut rng = rand::thread_rng(); + let key_in: u32 = rng.gen(); + let key_out: u32 = rng.gen(); + + Ok(vec![OnConnect::Packet(SendPatchPacket::PatchWelcome(PatchWelcome::new(key_out, key_in))), + OnConnect::Cipher(PSOPCCipher::new(key_in), PSOPCCipher::new(key_out)) + ]) + } + + async fn handle(&mut self, id: ClientId, pkt: RecvPatchPacket) -> Result, PatchError> { + Ok(match pkt { + RecvPatchPacket::PatchWelcomeReply(_pkt) => { + vec![SendPatchPacket::RequestLogin(RequestLogin {})] + .into_iter() + .map(move |pkt| (id, pkt)) + .collect() + }, + RecvPatchPacket::LoginReply(_pkt) => { + let mut pkts = vec![SendPatchPacket::Message(Message::new(self.patch_motd.clone()))]; + pkts.append(&mut get_file_list_packets(&self.patch_file_tree)); + pkts.push(SendPatchPacket::PatchEndList(PatchEndList {})); + pkts + .into_iter() + .map(move |pkt| (id, pkt)) + .collect() + }, + RecvPatchPacket::FileInfoReply(pkt) => { + self.patch_file_info.push(pkt); + Vec::new() + }, + RecvPatchPacket::FileInfoListEnd(_pkt) => { + let need_update = self.patch_file_info.iter() + .filter(|file_info| does_file_need_updating(file_info, &self.patch_file_lookup)) + .collect::>(); + + let total_size = need_update.iter().fold(0, |a, file_info| a + file_info.size); + let total_files = need_update.len() as u32; + + vec![SendPatchPacket::FilesToPatchMetadata(FilesToPatchMetadata::new(total_size, total_files)), + SendPatchPacket::PatchStartList(PatchStartList {})] + .into_iter() + .chain(SendFileIterator::new(self)) + .map(move |pkt| (id, pkt)) + .collect() + } + }) + } + + async fn on_disconnect(&mut self, _id: ClientId) -> Result, PatchError> { + Ok(Vec::new()) + } +} + +fn load_patch_dir(basedir: &str, patchbase: &str, file_ids: &mut HashMap) -> PatchFileTree { + let paths = fs::read_dir(basedir).expect("could not read directory"); + + let mut files = Vec::new(); + let mut dirs = Vec::new(); + for p in paths { + let path = p.expect("not a real path").path(); + let patch_path = path.strip_prefix(basedir).unwrap(); + if path.is_dir() { + dirs.push(load_patch_dir(path.to_str().unwrap(), patch_path.to_str().unwrap(), file_ids)); + } + else { + files.push(PatchFileTree::File(patch_path.to_path_buf(), file_ids.len() as u32)); + let (checksum, size) = get_checksum_and_size(&path).unwrap(); + file_ids.insert(file_ids.len() as u32, PatchFile { + path, + checksum, + size, + }); + } + } + + files.append(&mut dirs); + + PatchFileTree::Directory(PathBuf::from(patchbase), files) +} + +pub fn generate_patch_tree(basedir: &str) -> (PatchFileTree, HashMap) { + let mut file_ids = HashMap::new(); + + let patch_tree = load_patch_dir(basedir, "", &mut file_ids); + + (patch_tree, file_ids) +} + + +fn get_file_list_packets(patch_file_tree: &PatchFileTree) -> Vec { + let mut pkts = Vec::new(); + + for item in patch_file_tree.flatten() { + match item { + PatchTreeIterItem::Directory(path) => { + pkts.push(SendPatchPacket::ChangeDirectory(ChangeDirectory::new(path.to_str().unwrap()))); + }, + PatchTreeIterItem::File(path, id) => { + pkts.push(SendPatchPacket::FileInfo(FileInfo::new(path.to_str().unwrap(), id))); + }, + PatchTreeIterItem::UpDirectory => { + pkts.push(SendPatchPacket::UpOneDirectory(UpOneDirectory {})); + } + } + } + + pkts +} + +fn get_checksum_and_size(path: &Path) -> Result<(u32, u32), PatchError> { + let file = fs::File::open(path)?; + let size = file.metadata().unwrap().len(); + let mut crc = crc32::Digest::new(crc32::IEEE); + let mut buf = [0u8; 1024 * 32]; + let mut reader = io::BufReader::new(file); + while let Ok(len) = reader.read(&mut buf) { + if len == 0 { + break; + } + crc.write(&buf[0..len]); + } + + Ok((crc.sum32(), size as u32)) +} + +fn does_file_need_updating(file_info: &FileInfoReply, patch_file_lookup: &HashMap) -> bool { + let patch_file = patch_file_lookup.get(&file_info.id).unwrap(); + patch_file.checksum != file_info.checksum || patch_file.size != file_info.size +} + + +struct SendFileIterator { + done: bool, + file_iter: Box + Send>, + patch_file_lookup: HashMap, + current_file: Option>, + chunk_num: u32, +} + +impl SendFileIterator { + fn new(state: &PatchServerState) -> SendFileIterator { + let file_ids_to_update = state.patch_file_info.iter() + .filter(|file_info| does_file_need_updating(file_info, &state.patch_file_lookup)) + .map(|k| k.id) + .collect::>(); + + SendFileIterator { + done: false, + patch_file_lookup: state.patch_file_lookup.clone(), + file_iter: Box::new(state.patch_file_tree.flatten().into_iter().filter(move |file| { + match file { + PatchTreeIterItem::File(_path, id) => { + file_ids_to_update.contains(id) + }, + _ => true, + } + })), + current_file: None, + chunk_num: 0, + } + } +} + +impl Iterator for SendFileIterator { + type Item = SendPatchPacket; + + fn next(&mut self) -> Option { + if self.done { + return None; + } + + match self.current_file { + Some(ref mut file) => { + let mut buf = [0u8; PATCH_FILE_CHUNK_SIZE as usize]; + let len = file.read(&mut buf).unwrap(); + if len == 0 { + self.current_file = None; + self.chunk_num = 0; + Some(SendPatchPacket::EndFileSend(EndFileSend::new())) + } + else { + let mut crc = crc32::Digest::new(crc32::IEEE); + crc.write(&buf[0..len]); + let pkt = SendPatchPacket::FileSend(Box::new(FileSend { + chunk_num: self.chunk_num, + checksum: crc.sum32(), + chunk_size: len as u32, + buffer: buf, + })); + self.chunk_num += 1; + Some(pkt) + } + }, + None => { + match self.file_iter.next() { + Some(next_file) => { + match next_file { + PatchTreeIterItem::Directory(path) => { + Some(SendPatchPacket::ChangeDirectory(ChangeDirectory::new(path.to_str().unwrap()))) + }, + PatchTreeIterItem::File(path, id) => { + let patch_file = self.patch_file_lookup.get(&id).unwrap(); + let file = fs::File::open(&patch_file.path).unwrap(); + let size = file.metadata().unwrap().len(); + self.current_file = Some(io::BufReader::new(file)); + Some(SendPatchPacket::StartFileSend(StartFileSend::new(path.to_str().unwrap(), size as u32, id))) + }, + PatchTreeIterItem::UpDirectory => { + Some(SendPatchPacket::UpOneDirectory(UpOneDirectory {})) + }, + } + }, + None => { + self.current_file = None; + self.done = true; + Some(SendPatchPacket::FinalizePatching(FinalizePatching {})) + } + } + } + } + } +} + +#[derive(Debug, Deserialize)] +pub struct PatchConfig { + pub path: String, + pub ip: String, // TODO: this does nothing + pub port: u16, +} + +pub fn load_config() -> PatchConfig { + let ini_file = match fs::File::open(std::path::Path::new("patch.ron")) { + Err(err) => panic!("Failed to open patch.ron config file. \n{err}"), + Ok(ini_file) => ini_file, + }; + + let mut s = String::new(); + if let Err(err) = (&ini_file).read_to_string(&mut s) { + panic!("Failed to read patch.ron config file. \n{err}"); + } + + let config: PatchConfig = match from_str(s.as_str()) { + Ok(config) => config, + Err(err) => panic!("Failed to load values from patch.ron \n{err}"), + }; + config +} + +pub fn load_config_env() -> PatchConfig { + let patch_path = std::env::var("PATCHFILE_DIR").unwrap(); + let patch_port = std::env::var("PATCH_PORT").unwrap().parse().unwrap(); + + PatchConfig { + path: patch_path, + ip: "127.0.0.1".into(), + port: patch_port, + } +} + +pub fn load_motd() -> String { + if let Ok(m) = fs::read_to_string("patch.motd") { + m + } + else { + "Welcome to Elseware!".to_string() + } +} diff --git a/src/patch/patch.rs b/src/patch/patch.rs deleted file mode 100644 index 565530d..0000000 --- a/src/patch/patch.rs +++ /dev/null @@ -1,432 +0,0 @@ -use std::collections::{HashMap, HashSet}; -use std::fs; -use std::io; -use std::io::{Read}; -use std::path::{Path, PathBuf}; -use rand::Rng; -use crc::{crc32, Hasher32}; -use libpso::{PacketParseError, PSOPacket}; -use libpso::packet::patch::*; -use libpso::crypto::pc::PSOPCCipher; -use ron::de::from_str; -use serde::Deserialize; - -use networking::mainloop::{NetworkError}; -use networking::serverstate::{RecvServerPacket, SendServerPacket, ServerState, OnConnect, ClientId}; - -#[derive(Debug)] -pub enum PatchError { - NetworkError(NetworkError), - IOError(std::io::Error), -} - -impl From for PatchError { - fn from(err: NetworkError) -> PatchError { - PatchError::NetworkError(err) - } -} - -impl From for PatchError { - fn from(err: std::io::Error) -> PatchError { - PatchError::IOError(err) - } -} - - -#[derive(Debug, Clone)] -pub struct PatchFile { - path: PathBuf, - checksum: u32, - size: u32, -} - -pub enum PatchTreeIterItem { - Directory(PathBuf), - File(PathBuf, u32), - UpDirectory, -} - -#[derive(Debug, Clone)] -pub enum PatchFileTree { - Directory(PathBuf, Vec), - File(PathBuf, u32), // file_id -} - -impl PatchFileTree { - fn iter_dir(tree: &PatchFileTree) -> Vec { - let mut v = Vec::new(); - - match tree { - PatchFileTree::Directory(dir, files) => { - v.push(PatchTreeIterItem::Directory(dir.clone())); - for file in files { - v.append(&mut PatchFileTree::iter_dir(file)); - } - v.push(PatchTreeIterItem::UpDirectory); - }, - PatchFileTree::File(path, id) => { - v.push(PatchTreeIterItem::File(path.clone(), *id)); - } - } - - v - } - - pub fn flatten(&self) -> Vec { - PatchFileTree::iter_dir(self) - } -} - - -#[derive(Debug)] -pub enum RecvPatchPacket { - PatchWelcomeReply(PatchWelcomeReply), - LoginReply(LoginReply), - FileInfoReply(FileInfoReply), - FileInfoListEnd(FileInfoListEnd), -} - -impl RecvServerPacket for RecvPatchPacket { - fn from_bytes(data: &[u8]) -> Result { - match data[2] { - 0x02 => Ok(RecvPatchPacket::PatchWelcomeReply(PatchWelcomeReply::from_bytes(data)?)), - 0x04 => Ok(RecvPatchPacket::LoginReply(LoginReply::from_bytes(data)?)), - 0x0F => Ok(RecvPatchPacket::FileInfoReply(FileInfoReply::from_bytes(data)?)), - 0x10 => Ok(RecvPatchPacket::FileInfoListEnd(FileInfoListEnd::from_bytes(data)?)), - _ => Err(PacketParseError::WrongPacketForServerType(u16::from_le_bytes([data[2], data[3]]), data.to_vec())) - } - } -} - -#[derive(Debug)] -pub enum SendPatchPacket { - ChangeDirectory(ChangeDirectory), - EndFileSend(EndFileSend), - FileInfo(FileInfo), - FileSend(Box), - FilesToPatchMetadata(FilesToPatchMetadata), - FinalizePatching(FinalizePatching), - Message(Message), - PatchEndList(PatchEndList), - PatchStartList(PatchStartList), - PatchWelcome(PatchWelcome), - RequestLogin(RequestLogin), - StartFileSend(StartFileSend), - UpOneDirectory(UpOneDirectory), -} - -impl SendServerPacket for SendPatchPacket { - fn as_bytes(&self) -> Vec { - match self { - SendPatchPacket::ChangeDirectory(pkt) => pkt.as_bytes(), - SendPatchPacket::EndFileSend(pkt) => pkt.as_bytes(), - SendPatchPacket::FileInfo(pkt) => pkt.as_bytes(), - SendPatchPacket::FileSend(pkt) => pkt.as_bytes(), - SendPatchPacket::FilesToPatchMetadata(pkt) => pkt.as_bytes(), - SendPatchPacket::FinalizePatching(pkt) => pkt.as_bytes(), - SendPatchPacket::Message(pkt) => pkt.as_bytes(), - SendPatchPacket::PatchEndList(pkt) => pkt.as_bytes(), - SendPatchPacket::PatchStartList(pkt) => pkt.as_bytes(), - SendPatchPacket::PatchWelcome(pkt) => pkt.as_bytes(), - SendPatchPacket::RequestLogin(pkt) => pkt.as_bytes(), - SendPatchPacket::StartFileSend(pkt) => pkt.as_bytes(), - SendPatchPacket::UpOneDirectory(pkt) => pkt.as_bytes(), - } - } -} - - -#[derive(Clone)] -pub struct PatchServerState { - patch_file_tree: PatchFileTree, - patch_file_lookup: HashMap, - patch_file_info: Vec, - patch_motd: String, -} - -impl PatchServerState { - pub fn new(patch_file_tree: PatchFileTree, patch_file_lookup: HashMap, patch_motd: String) -> PatchServerState { - PatchServerState { - patch_file_tree, - patch_file_lookup, - patch_file_info: Vec::new(), - patch_motd, - } - } -} - -#[async_trait::async_trait] -impl ServerState for PatchServerState { - type SendPacket = SendPatchPacket; - type RecvPacket = RecvPatchPacket; - type Cipher = PSOPCCipher; - type PacketError = PatchError; - - async fn on_connect(&mut self, _id: ClientId) -> Result>, PatchError> { - let mut rng = rand::thread_rng(); - let key_in: u32 = rng.gen(); - let key_out: u32 = rng.gen(); - - Ok(vec![OnConnect::Packet(SendPatchPacket::PatchWelcome(PatchWelcome::new(key_out, key_in))), - OnConnect::Cipher(PSOPCCipher::new(key_in), PSOPCCipher::new(key_out)) - ]) - } - - async fn handle(&mut self, id: ClientId, pkt: RecvPatchPacket) -> Result, PatchError> { - Ok(match pkt { - RecvPatchPacket::PatchWelcomeReply(_pkt) => { - vec![SendPatchPacket::RequestLogin(RequestLogin {})] - .into_iter() - .map(move |pkt| (id, pkt)) - .collect() - }, - RecvPatchPacket::LoginReply(_pkt) => { - let mut pkts = vec![SendPatchPacket::Message(Message::new(self.patch_motd.clone()))]; - pkts.append(&mut get_file_list_packets(&self.patch_file_tree)); - pkts.push(SendPatchPacket::PatchEndList(PatchEndList {})); - pkts - .into_iter() - .map(move |pkt| (id, pkt)) - .collect() - }, - RecvPatchPacket::FileInfoReply(pkt) => { - self.patch_file_info.push(pkt); - Vec::new() - }, - RecvPatchPacket::FileInfoListEnd(_pkt) => { - let need_update = self.patch_file_info.iter() - .filter(|file_info| does_file_need_updating(file_info, &self.patch_file_lookup)) - .collect::>(); - - let total_size = need_update.iter().fold(0, |a, file_info| a + file_info.size); - let total_files = need_update.len() as u32; - - vec![SendPatchPacket::FilesToPatchMetadata(FilesToPatchMetadata::new(total_size, total_files)), - SendPatchPacket::PatchStartList(PatchStartList {})] - .into_iter() - .chain(SendFileIterator::new(self)) - .map(move |pkt| (id, pkt)) - .collect() - } - }) - } - - async fn on_disconnect(&mut self, _id: ClientId) -> Result, PatchError> { - Ok(Vec::new()) - } -} - -fn load_patch_dir(basedir: &str, patchbase: &str, file_ids: &mut HashMap) -> PatchFileTree { - let paths = fs::read_dir(basedir).expect("could not read directory"); - - let mut files = Vec::new(); - let mut dirs = Vec::new(); - for p in paths { - let path = p.expect("not a real path").path(); - let patch_path = path.strip_prefix(basedir).unwrap(); - if path.is_dir() { - dirs.push(load_patch_dir(path.to_str().unwrap(), patch_path.to_str().unwrap(), file_ids)); - } - else { - files.push(PatchFileTree::File(patch_path.to_path_buf(), file_ids.len() as u32)); - let (checksum, size) = get_checksum_and_size(&path).unwrap(); - file_ids.insert(file_ids.len() as u32, PatchFile { - path, - checksum, - size, - }); - } - } - - files.append(&mut dirs); - - PatchFileTree::Directory(PathBuf::from(patchbase), files) -} - -pub fn generate_patch_tree(basedir: &str) -> (PatchFileTree, HashMap) { - let mut file_ids = HashMap::new(); - - let patch_tree = load_patch_dir(basedir, "", &mut file_ids); - - (patch_tree, file_ids) -} - - -fn get_file_list_packets(patch_file_tree: &PatchFileTree) -> Vec { - let mut pkts = Vec::new(); - - for item in patch_file_tree.flatten() { - match item { - PatchTreeIterItem::Directory(path) => { - pkts.push(SendPatchPacket::ChangeDirectory(ChangeDirectory::new(path.to_str().unwrap()))); - }, - PatchTreeIterItem::File(path, id) => { - pkts.push(SendPatchPacket::FileInfo(FileInfo::new(path.to_str().unwrap(), id))); - }, - PatchTreeIterItem::UpDirectory => { - pkts.push(SendPatchPacket::UpOneDirectory(UpOneDirectory {})); - } - } - } - - pkts -} - -fn get_checksum_and_size(path: &Path) -> Result<(u32, u32), PatchError> { - let file = fs::File::open(path)?; - let size = file.metadata().unwrap().len(); - let mut crc = crc32::Digest::new(crc32::IEEE); - let mut buf = [0u8; 1024 * 32]; - let mut reader = io::BufReader::new(file); - while let Ok(len) = reader.read(&mut buf) { - if len == 0 { - break; - } - crc.write(&buf[0..len]); - } - - Ok((crc.sum32(), size as u32)) -} - -fn does_file_need_updating(file_info: &FileInfoReply, patch_file_lookup: &HashMap) -> bool { - let patch_file = patch_file_lookup.get(&file_info.id).unwrap(); - patch_file.checksum != file_info.checksum || patch_file.size != file_info.size -} - - -struct SendFileIterator { - done: bool, - file_iter: Box + Send>, - patch_file_lookup: HashMap, - current_file: Option>, - chunk_num: u32, -} - -impl SendFileIterator { - fn new(state: &PatchServerState) -> SendFileIterator { - let file_ids_to_update = state.patch_file_info.iter() - .filter(|file_info| does_file_need_updating(file_info, &state.patch_file_lookup)) - .map(|k| k.id) - .collect::>(); - - SendFileIterator { - done: false, - patch_file_lookup: state.patch_file_lookup.clone(), - file_iter: Box::new(state.patch_file_tree.flatten().into_iter().filter(move |file| { - match file { - PatchTreeIterItem::File(_path, id) => { - file_ids_to_update.contains(id) - }, - _ => true, - } - })), - current_file: None, - chunk_num: 0, - } - } -} - -impl Iterator for SendFileIterator { - type Item = SendPatchPacket; - - fn next(&mut self) -> Option { - if self.done { - return None; - } - - match self.current_file { - Some(ref mut file) => { - let mut buf = [0u8; PATCH_FILE_CHUNK_SIZE as usize]; - let len = file.read(&mut buf).unwrap(); - if len == 0 { - self.current_file = None; - self.chunk_num = 0; - Some(SendPatchPacket::EndFileSend(EndFileSend::new())) - } - else { - let mut crc = crc32::Digest::new(crc32::IEEE); - crc.write(&buf[0..len]); - let pkt = SendPatchPacket::FileSend(Box::new(FileSend { - chunk_num: self.chunk_num, - checksum: crc.sum32(), - chunk_size: len as u32, - buffer: buf, - })); - self.chunk_num += 1; - Some(pkt) - } - }, - None => { - match self.file_iter.next() { - Some(next_file) => { - match next_file { - PatchTreeIterItem::Directory(path) => { - Some(SendPatchPacket::ChangeDirectory(ChangeDirectory::new(path.to_str().unwrap()))) - }, - PatchTreeIterItem::File(path, id) => { - let patch_file = self.patch_file_lookup.get(&id).unwrap(); - let file = fs::File::open(&patch_file.path).unwrap(); - let size = file.metadata().unwrap().len(); - self.current_file = Some(io::BufReader::new(file)); - Some(SendPatchPacket::StartFileSend(StartFileSend::new(path.to_str().unwrap(), size as u32, id))) - }, - PatchTreeIterItem::UpDirectory => { - Some(SendPatchPacket::UpOneDirectory(UpOneDirectory {})) - }, - } - }, - None => { - self.current_file = None; - self.done = true; - Some(SendPatchPacket::FinalizePatching(FinalizePatching {})) - } - } - } - } - } -} - -#[derive(Debug, Deserialize)] -pub struct PatchConfig { - pub path: String, - pub ip: String, // TODO: this does nothing - pub port: u16, -} - -pub fn load_config() -> PatchConfig { - let ini_file = match fs::File::open(std::path::Path::new("patch.ron")) { - Err(err) => panic!("Failed to open patch.ron config file. \n{err}"), - Ok(ini_file) => ini_file, - }; - - let mut s = String::new(); - if let Err(err) = (&ini_file).read_to_string(&mut s) { - panic!("Failed to read patch.ron config file. \n{err}"); - } - - let config: PatchConfig = match from_str(s.as_str()) { - Ok(config) => config, - Err(err) => panic!("Failed to load values from patch.ron \n{err}"), - }; - config -} - -pub fn load_config_env() -> PatchConfig { - let patch_path = std::env::var("PATCHFILE_DIR").unwrap(); - let patch_port = std::env::var("PATCH_PORT").unwrap().parse().unwrap(); - - PatchConfig { - path: patch_path, - ip: "127.0.0.1".into(), - port: patch_port, - } -} - -pub fn load_motd() -> String { - if let Ok(m) = fs::read_to_string("patch.motd") { - m - } - else { - "Welcome to Elseware!".to_string() - } -}