remove redundant patch.rs
This commit is contained in:
parent
7f421fd2c7
commit
dd8b81e69c
@ -4,7 +4,7 @@ use log::{info};
|
|||||||
use networking::interserver::AuthToken;
|
use networking::interserver::AuthToken;
|
||||||
use elseware::login::login::LoginServerState;
|
use elseware::login::login::LoginServerState;
|
||||||
use elseware::login::character::CharacterServerState;
|
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 elseware::ship::ship::ShipServerStateBuilder;
|
||||||
|
|
||||||
use maps::Holiday;
|
use maps::Holiday;
|
||||||
|
@ -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;
|
use log::info;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
|
434
src/patch/mod.rs
434
src/patch/mod.rs
@ -1,2 +1,432 @@
|
|||||||
#[allow(clippy::module_inception)]
|
use std::collections::{HashMap, HashSet};
|
||||||
pub mod patch;
|
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<NetworkError> for PatchError {
|
||||||
|
fn from(err: NetworkError) -> PatchError {
|
||||||
|
PatchError::NetworkError(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<std::io::Error> 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<PatchFileTree>),
|
||||||
|
File(PathBuf, u32), // file_id
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PatchFileTree {
|
||||||
|
fn iter_dir(tree: &PatchFileTree) -> Vec<PatchTreeIterItem> {
|
||||||
|
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<PatchTreeIterItem> {
|
||||||
|
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<RecvPatchPacket, PacketParseError> {
|
||||||
|
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<FileSend>),
|
||||||
|
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<u8> {
|
||||||
|
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<u32, PatchFile>,
|
||||||
|
patch_file_info: Vec<FileInfoReply>,
|
||||||
|
patch_motd: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PatchServerState {
|
||||||
|
pub fn new(patch_file_tree: PatchFileTree, patch_file_lookup: HashMap<u32, PatchFile>, 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<Vec<OnConnect<Self::SendPacket, Self::Cipher>>, 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<Vec<(ClientId, SendPatchPacket)>, 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::<Vec<_>>();
|
||||||
|
|
||||||
|
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<Vec<(ClientId, SendPatchPacket)>, PatchError> {
|
||||||
|
Ok(Vec::new())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn load_patch_dir(basedir: &str, patchbase: &str, file_ids: &mut HashMap<u32, PatchFile>) -> 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<u32, PatchFile>) {
|
||||||
|
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<SendPatchPacket> {
|
||||||
|
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<u32, PatchFile>) -> 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<dyn Iterator<Item = PatchTreeIterItem> + Send>,
|
||||||
|
patch_file_lookup: HashMap<u32, PatchFile>,
|
||||||
|
current_file: Option<io::BufReader<fs::File>>,
|
||||||
|
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::<HashSet<_>>();
|
||||||
|
|
||||||
|
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<Self::Item> {
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -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<NetworkError> for PatchError {
|
|
||||||
fn from(err: NetworkError) -> PatchError {
|
|
||||||
PatchError::NetworkError(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<std::io::Error> 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<PatchFileTree>),
|
|
||||||
File(PathBuf, u32), // file_id
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PatchFileTree {
|
|
||||||
fn iter_dir(tree: &PatchFileTree) -> Vec<PatchTreeIterItem> {
|
|
||||||
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<PatchTreeIterItem> {
|
|
||||||
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<RecvPatchPacket, PacketParseError> {
|
|
||||||
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<FileSend>),
|
|
||||||
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<u8> {
|
|
||||||
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<u32, PatchFile>,
|
|
||||||
patch_file_info: Vec<FileInfoReply>,
|
|
||||||
patch_motd: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PatchServerState {
|
|
||||||
pub fn new(patch_file_tree: PatchFileTree, patch_file_lookup: HashMap<u32, PatchFile>, 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<Vec<OnConnect<Self::SendPacket, Self::Cipher>>, 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<Vec<(ClientId, SendPatchPacket)>, 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::<Vec<_>>();
|
|
||||||
|
|
||||||
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<Vec<(ClientId, SendPatchPacket)>, PatchError> {
|
|
||||||
Ok(Vec::new())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn load_patch_dir(basedir: &str, patchbase: &str, file_ids: &mut HashMap<u32, PatchFile>) -> 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<u32, PatchFile>) {
|
|
||||||
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<SendPatchPacket> {
|
|
||||||
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<u32, PatchFile>) -> 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<dyn Iterator<Item = PatchTreeIterItem> + Send>,
|
|
||||||
patch_file_lookup: HashMap<u32, PatchFile>,
|
|
||||||
current_file: Option<io::BufReader<fs::File>>,
|
|
||||||
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::<HashSet<_>>();
|
|
||||||
|
|
||||||
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<Self::Item> {
|
|
||||||
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()
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
x
Reference in New Issue
Block a user