Refactoring, moving out proto to separate repo, fixed login bug

This commit is contained in:
Nobody 2021-10-21 02:51:01 +05:00
parent 39f2618916
commit 4d32da1e2e
23 changed files with 921 additions and 740 deletions

6
.gitignore vendored
View File

@ -9,15 +9,9 @@ Cargo.lock
# These are backup files generated by rustfmt # These are backup files generated by rustfmt
**/*.rs.bk **/*.rs.bk
# *.proto files
/protobuf/
# keys # keys
/keys/ /keys/
# Vim temp files # Vim temp files
.*.swp .*.swp
.*.swo .*.swo
# Autogenerated files
/gen/

View File

@ -8,11 +8,26 @@ edition = "2018"
[dependencies] [dependencies]
kcp = { path = "../kcp" } kcp = { path = "../kcp" }
mhycrypt = { path = "../mhycrypt" } mhycrypt = { path = "../mhycrypt" }
proto = { path = "../proto" }
packet-processor-macro = { path = "packet-processor-macro" }
packet-processor = { path = "packet-processor" }
prost = "0.8" prost = "0.8"
bytes = "1.1.0" bytes = "1.1.0"
base64 = "0.13.0"
tokio = { version = "1", features = ["full"] }
actix-web = { version = "3", features = ["openssl"] }
futures = "0.3"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
pretty_env_logger = "0.4"
num-traits = "0.2" num-traits = "0.2"
num-derive = "0.3" num-derive = "0.3"
base64 = "0.13.0"
[build-dependencies] pretty-hex = "0.2"
prost-build = { version = "0.8.0" }
[target.'cfg(windows)'.dependencies]
openssl = { version = "0.10", features = ["vendored"] }
[target.'cfg(unix)'.dependencies]
openssl = "0.10"

View File

@ -1,65 +0,0 @@
use std::io::Result;
fn main() -> Result<()> {
let proto_dir = "protobuf";
let protos = vec![
// Dispatch protocol
"QueryRegionListHttpRsp",
"QueryCurrRegionHttpRsp",
"RegionSimpleInfo",
"RegionInfo",
// Game protocol
"packet_header",
"GetPlayerTokenReq",
"GetPlayerTokenRsp",
"PlayerLoginReq",
"OpenStateUpdateNotify",
"StoreWeightLimitNotify",
"PlayerStoreNotify",
"AvatarDataNotify",
"PlayerEnterSceneNotify",
"PlayerLoginRsp",
"GetPlayerSocialDetailReq",
"GetPlayerSocialDetailRsp",
"EnterSceneReadyReq",
"EnterSceneReadyRsp",
"SceneInitFinishReq",
"EnterScenePeerNotify",
"WorldDataNotify",
"WorldPlayerInfoNotify",
"ScenePlayerInfoNotify",
"PlayerEnterSceneInfoNotify",
"PlayerGameTimeNotify",
"SceneTimeNotify",
"SceneDataNotify",
"HostPlayerNotify",
"SceneTeamUpdateNotify",
"SceneInitFinishRsp",
"EnterSceneDoneReq",
"SceneEntityAppearNotify",
"EnterSceneDoneRsp",
"PostEnterSceneReq",
"PostEnterSceneRsp",
"WorldPlayerRTTNotify",
"PingReq",
"PingRsp",
"PlayerDataNotify",
"EnterWorldAreaReq",
"EnterWorldAreaRsp",
];
let protos: Vec<String> = protos.iter().map(|&x| format!("{}/{}.proto", proto_dir, x)).collect();
let ret = prost_build::compile_protos(&protos, &[format!("{}/", proto_dir)]);
match ret {
Ok(_) => return Ok(()),
Err(e) => panic!("{}", e),
}
}

View File

@ -1,27 +0,0 @@
#!/bin/bash
#echo "#[macro_use]";
#echo "extern crate num_derive;";
#echo "";
echo "// Autogenerated file, do not edit!"
echo "";
echo "#[repr(u16)]";
echo "#[derive(FromPrimitive, ToPrimitive)]";
echo "#[derive(Debug, PartialEq, Eq, Clone, Hash)]";
echo "pub enum PacketId {";
for f in protobuf/*; do
CMD_ID=`cat $f | grep CMD_ID | cut -d'=' -f2 | tr -d ' ;\015'`;
if [ "x${CMD_ID}" != "x" ]; then
NAME=`echo $f | cut -d'/' -f2 | cut -d'.' -f1`;
echo " ${NAME} = ${CMD_ID},"
fi
done
echo "}";

7
misc/gen_key.sh Executable file
View File

@ -0,0 +1,7 @@
#!/bin/bash
echo -ne "\x45\x63\x32\x62" > fake.ec2b
echo -ne "\x10\x00\x00\x00" >> fake.ec2b
dd if=/dev/urandom bs=16 count=1 >> fake.ec2b
echo -ne "\x00\x08\x00\x00" >> fake.ec2b
dd if=/dev/urandom bs=2048 count=1 >> fake.ec2b

5
misc/grab_key.sh Executable file
View File

@ -0,0 +1,5 @@
#!/bin/bash
xxd -r text.log > gptr.bin
./xor.py gptr.bin 0x20
dd if=gptr.bin of=key.bin bs=4096 count=1 skip=1

3
misc/ssl_stuff/gen_cert.sh Executable file
View File

@ -0,0 +1,3 @@
#!/bin/bash
openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -days 3650 -config ssl.conf -nodes -sha256 -extensions 'req_ext'

41
misc/ssl_stuff/ssl.conf Normal file
View File

@ -0,0 +1,41 @@
[ req ]
default_bits = 2048
distinguished_name = req_distinguished_name
req_extensions = req_ext
extensions = req_ext
prompt = no
[ req_distinguished_name ]
countryName = CN
stateOrProvinceName = Beijing
localityName = Xicheng
organizationName = oYoHim
[ req_ext ]
subjectAltName = @alt_names
[ alt_names ]
DNS.1 = api-os-takumi.mihoyo.com
DNS.2 = api-takumi.mihoyo.com
DNS.3 = autopatchhk.yuanshen.com
DNS.4 = autopatchcn.yuanshen.com
DNS.5 = dispatchosglobal.yuanshen.com
DNS.6 = hk4e-api-os-static.mihoyo.com
DNS.7 = hk4e-api-static.mihoyo.com
DNS.8 = hk4e-api-os.mihoyo.com
DNS.9 = hk4e-api.mihoyo.com
DNS.10 = hk4e-sdk-os.mihoyo.com
DNS.11 = hk4e-sdk.mihoyo.com
DNS.12 = log-upload-os.mihoyo.com
DNS.13 = log-upload.mihoyo.com
DNS.14 = osusadispatch.yuanshen.com
DNS.15 = oseurodispatch.yuanshen.com
DNS.16 = osasiadispatch.yuanshen.com
DNS.17 = sdk-os-static.mihoyo.com
DNS.18 = sdk-static.mihoyo.com
DNS.19 = webstatic-sea.mihoyo.com
DNS.20 = webstatic.mihoyo.com
DNS.21 = uploadstatic-sea.mihoyo.com
DNS.22 = uploadstatic.mihoyo.com
DNS.23 = devlog-upload.mihoyo.com
DNS.24 = localhost

9
misc/xor.py Executable file
View File

@ -0,0 +1,9 @@
#!/usr/bin/env python3
import sys
filename = sys.argv[1]
key = int(sys.argv[2], 16)
data = open(filename, "rb").read()
open(filename, "wb").write(bytes([i ^ key for i in data]))

14
packet-processor-macro/.gitignore vendored Normal file
View File

@ -0,0 +1,14 @@
# Generated by Cargo
# will have compiled files and executables
/target/
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
Cargo.lock
# These are backup files generated by rustfmt
**/*.rs.bk
# Vim temp files
.*.swp
.*.swo

View File

@ -0,0 +1,16 @@
[package]
name = "packet-processor-macro"
version = "0.1.0"
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[lib]
proc-macro = true
[dependencies]
regex = "1"
syn = { version = "1.0", features = ["full","derive"] }
quote = "1.0"
proc-macro2 = "1.0"
convert_case = "0.4.0"

View File

@ -0,0 +1,107 @@
extern crate regex;
extern crate convert_case;
use proc_macro::{self, TokenStream};
use quote::quote;
use syn::{parse_macro_input, AttributeArgs};
use regex::Regex;
use convert_case::{Case, Casing};
fn get_nested_meta_name(nested_meta: &syn::NestedMeta) -> String {
match nested_meta {
syn::NestedMeta::Meta(meta) => match meta {
syn::Meta::Path(ident) => ident.get_ident().cloned().unwrap().to_string(),
_ => panic!("Unsupported macro argument"),
},
syn::NestedMeta::Lit(_) => panic!("Only identifiers are supported as an arguments to the macro"),
}
}
#[proc_macro_attribute]
pub fn packet_processor(args: TokenStream, input: TokenStream) -> TokenStream {
let args = parse_macro_input!(args as AttributeArgs);
//let input = parse_macro_input!(input as DeriveInput);
let mut found_struct = false;
let mut struct_name = None;
let modified_struct: proc_macro::TokenStream = input.into_iter().map(|r| {
match &r {
&proc_macro::TokenTree::Ident(ref ident) if ident.to_string() == "struct" => { // react on keyword "struct" so we don't randomly modify non-structs
found_struct = true;
r
},
&proc_macro::TokenTree::Ident(ref ident) if found_struct == true && struct_name == None => { // Next ident right after "struct" is the struct name
struct_name = Some(ident.to_string());
r
},
&proc_macro::TokenTree::Group(ref group) if group.delimiter() == proc_macro::Delimiter::Brace && found_struct == true => { // Opening brackets for the struct
let mut stream = proc_macro::TokenStream::new();
stream.extend(
vec![proc_macro::TokenStream::from(quote!(packet_callbacks: HashMap<proto::PacketId, fn(&mut Self, u32, &proto::PacketHead, Vec<u8>) -> ()>,))]
);
stream.extend(group.stream());
proc_macro::TokenTree::Group(
proc_macro::Group::new(
proc_macro::Delimiter::Brace,
stream
)
)
}
_ => r
}
}).collect();
let args = args.clone().into_iter().map(|a| get_nested_meta_name(&a));
let re = Regex::new(r"Req$").unwrap();
let request = args.clone().into_iter().filter(|a| a.ends_with("Req"));
let notify = args.clone().into_iter().filter(|a| a.ends_with("Notify"));
let response = request.clone().into_iter().map(|a| re.replace_all(&a, "Rsp").to_string());
let req_handler = request.clone().into_iter().map(|a| format!("process_{}", &a[..a.len()-3].to_case(Case::Snake)));
let notify_handler = notify.clone().into_iter().map(|a| format!("process_{}", &a[..a.len()-6].to_case(Case::Snake)));
let request: Vec<proc_macro2::TokenStream> = request.map(|a| a.parse().unwrap()).collect();
let notify: Vec<proc_macro2::TokenStream> = notify.map(|a| a.parse().unwrap()).collect();
let response: Vec<proc_macro2::TokenStream> = response.map(|a| a.parse().unwrap()).collect();
let req_handler: Vec<proc_macro2::TokenStream> = req_handler.map(|a| a.parse().unwrap()).collect();
let notify_handler: Vec<proc_macro2::TokenStream> = notify_handler.map(|a| a.parse().unwrap()).collect();
let struct_name: proc_macro2::TokenStream = match &struct_name {
None => panic!("Failed to find struct name"),
Some(name) => name.clone().parse().unwrap(),
};
let implementation = vec![proc_macro::TokenStream::from(quote!(
impl PacketProcessor for #struct_name {
fn register(&mut self) {
let mut callbacks = &mut self.packet_callbacks;
#(register_callback!(callbacks, #request, #response, #req_handler);)*
#(register_callback!(callbacks, #notify, #notify_handler);)*
}
fn supported(&self) -> Vec<proto::PacketId> {
return self.packet_callbacks.keys().cloned().collect();
}
fn process(&mut self, user_id: u32, packet_id: proto::PacketId, metadata: Vec<u8>, data: Vec<u8>) {
let callback = self.packet_callbacks.get(&packet_id);
let metadata = proto::PacketHead::decode(&mut std::io::Cursor::new(metadata)).unwrap();
match callback {
Some(callback) => callback(self, user_id, &metadata, data),
None => println!("Unhandled packet {:?}", packet_id),
}
}
}
))];
let mut ret = proc_macro::TokenStream::new();
ret.extend(modified_struct);
ret.extend(implementation);
//println!("{}", ret);
ret
}

14
packet-processor/.gitignore vendored Normal file
View File

@ -0,0 +1,14 @@
# Generated by Cargo
# will have compiled files and executables
/target/
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
Cargo.lock
# These are backup files generated by rustfmt
**/*.rs.bk
# Vim temp files
.*.swp
.*.swo

View File

@ -0,0 +1,9 @@
[package]
name = "packet-processor"
version = "0.1.0"
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
proto = { path = "../../proto" }

View File

@ -0,0 +1,32 @@
pub trait PacketProcessor {
fn register(&mut self);
fn supported(&self) -> Vec<proto::PacketId>;
fn process(&mut self, user_id: u32, packet_id: proto::PacketId, metadata: Vec<u8>, data: Vec<u8>);
}
#[macro_export]
macro_rules! register_callback {
($hashmap:ident, $req:ident, $rsp:ident, $handler:ident) => {
$hashmap.insert(proto::PacketId::$req, |slef: &mut Self, user_id: u32, metadata: &proto::PacketHead, data: Vec<u8>| {
let req = proto::$req::decode(&mut std::io::Cursor::new(data)).unwrap();
let mut rsp = proto::$rsp::default();
println!("Received REQ {:?}", req);
slef.$handler(user_id, &metadata, &req, &mut rsp);
let message = IpcMessage::new_from_proto(user_id, proto::PacketId::$rsp, metadata, &rsp);
slef.packets_to_send_tx.send(message).unwrap();
});
};
($hashmap:ident, $notify:ident, $handler:ident) => {
$hashmap.insert(proto::PacketId::$notify, |slef: &mut Self, user_id: u32, metadata: &proto::PacketHead, data: Vec<u8>| {
let notify = proto::$req::decode(&mut std::io::Cursor::new(data)).unwrap();
println!("Received NOTIFY {:?}", notify);
slef.$handler(user_id, &metadata, &notify);
});
};
}

View File

@ -1,3 +1,5 @@
extern crate pretty_env_logger;
#[macro_use] #[macro_use]
extern crate num_derive; extern crate num_derive;
@ -6,19 +8,15 @@ use std::thread;
mod server; mod server;
mod utils; mod utils;
pub mod proto {
include!(concat!(env!("OUT_DIR"), "/proto.rs"));
include!(concat!("..", "/gen", "/packet_id.rs"));
include!(concat!("..", "/gen", "/player_prop.rs"));
include!(concat!("..", "/gen", "/open_state.rs"));
}
use server::NetworkServer; use server::NetworkServer;
use server::DispatchServer; use server::DispatchServer;
fn main() { fn main() {
pretty_env_logger::init();
thread::spawn(|| { thread::spawn(|| {
let mut ds = DispatchServer::new("127.0.0.1", 9696); //let mut ds = DispatchServer::new("127.0.0.1", 9696);
let mut ds = DispatchServer::new();
ds.run(); ds.run();
}); });

View File

@ -1,20 +1,67 @@
use std::sync::mpsc; use std::sync::mpsc;
use std::collections::HashMap;
use prost::Message;
use crate::server::IpcMessage; use crate::server::IpcMessage;
use packet_processor_macro::*;
#[macro_use]
use packet_processor::*;
#[packet_processor(GetPlayerTokenReq)]
pub struct AuthManager { pub struct AuthManager {
conv_to_user: HashMap<u32, u32>,
user_to_conv: HashMap<u32, u32>,
packets_to_send_tx: mpsc::Sender<IpcMessage>, packets_to_send_tx: mpsc::Sender<IpcMessage>,
} }
impl AuthManager { impl AuthManager {
const SPOOFED_PLAYER_UID: u32 = 1337;
pub fn new(packets_to_send_tx: mpsc::Sender<IpcMessage>) -> AuthManager { pub fn new(packets_to_send_tx: mpsc::Sender<IpcMessage>) -> AuthManager {
let am = AuthManager { let mut am = AuthManager {
conv_to_user: HashMap::new(),
user_to_conv: HashMap::new(),
packet_callbacks: HashMap::new(),
packets_to_send_tx: packets_to_send_tx, packets_to_send_tx: packets_to_send_tx,
}; };
am.register();
return am; return am;
} }
pub fn process_packet(&mut self, conv: u32, packet_id: u16, metadata: Vec<u8>, data: Vec<u8>) { pub fn process_get_player_token(&mut self, conv: u32, metadata: &proto::PacketHead, req: &proto::GetPlayerTokenReq, rsp: &mut proto::GetPlayerTokenRsp) {
let seed: u64 = 0x123456789ABCDEF0; // TODO: use real value!
let uid = self.get_uid_by_account_id(req.account_uid.parse().unwrap());
rsp.account_type = req.account_type;
rsp.account_uid = req.account_uid.clone();
rsp.token = format!("token-game-{}", req.account_uid);
rsp.secret_key_seed = seed;
rsp.uid = uid;
self.conv_to_user.insert(conv, uid);
self.user_to_conv.insert(uid, conv);
}
fn get_uid_by_account_id(&self, account_uid: u32) -> u32 {
// TODO!
return AuthManager::SPOOFED_PLAYER_UID;
}
pub fn resolve_conv(&self, conv: u32) -> Option<u32> {
match self.conv_to_user.get(&conv) {
Some(uid) => return Some(*uid),
None => return None,
};
}
pub fn resolve_uid(&self, uid: u32) -> Option<u32> {
match self.user_to_conv.get(&uid) {
Some(conv) => return Some(*conv),
None => return None,
};
} }
} }

View File

@ -76,6 +76,12 @@ impl ClientConnection {
match self.ikcp.recv(&mut buf) { match self.ikcp.recv(&mut buf) {
Err(_) => break, Err(_) => break,
Ok(size) => { Ok(size) => {
#[cfg(feature = "raw_packet_dump")]
{
use pretty_hex::*;
let cfg = HexConfig {title: true, width: 16, group: 0, ascii: true, ..HexConfig::default() };
println!("{:?}", buf[..size].to_vec().hex_conf(cfg));
}
mhycrypt::mhy_xor(&mut buf[..size], &self.key); mhycrypt::mhy_xor(&mut buf[..size], &self.key);
let data = buf[..size].to_owned(); let data = buf[..size].to_owned();
packets.push(data); packets.push(data);

View File

@ -3,104 +3,126 @@ use std::net::TcpListener;
use std::net::TcpStream; use std::net::TcpStream;
use std::collections::HashMap; use std::collections::HashMap;
use std::fs; use std::fs;
use std::sync::Arc;
extern crate futures;
extern crate base64;
extern crate actix_web;
extern crate openssl;
use serde::{Deserialize,Serialize};
use futures::executor;
use actix_web::{rt::System, web, get, App, HttpRequest, HttpResponse, HttpServer, Responder, middleware::Logger};
use openssl::ssl::{SslAcceptor, SslFiletype, SslMethod, SslVerifyMode, SslOptions, SslMode};
use prost::Message; use prost::Message;
extern crate base64;
use crate::proto;
use mhycrypt; use mhycrypt;
type RequestCallback = fn(slef: &DispatchServer, args: Vec<(&str, &str)>) -> (String, String); #[derive(Clone)]
pub struct DispatchServer { pub struct DispatchServer {
listener: TcpListener, }
callbacks: HashMap<String, RequestCallback>,
ip: String, #[derive(Deserialize,Debug)]
port: u16, struct ClientInfo {
version: String,
lang: i32,
platform: i32,
binary: i32,
time: i32,
channel_id: i32,
sub_channel_id: i32,
account_type: Option<i32>,
}
#[derive(Deserialize,Debug)]
struct TokenToVerify
{
uid: String,
token: String,
}
#[derive(Deserialize,Debug)]
struct ActionToCheck
{
action_type: String,
api_name: String,
username: Option<String>,
}
#[derive(Deserialize,Debug)]
struct LoginData {
account: String,
is_crypto: bool,
password: String,
}
#[derive(Deserialize,Debug)]
struct GranterData {
app_id: String,
channel_id: String,
device: String,
sign: String,
data: String,
} }
impl DispatchServer { impl DispatchServer {
pub fn new(ip: &str, port: u16) -> DispatchServer { pub fn new() -> DispatchServer {
let mut callbacks = HashMap::new(); let ds = DispatchServer { };
let mut ds = DispatchServer {
listener: TcpListener::bind(format!("{}:{}", ip, port)).unwrap(),
callbacks: callbacks,
ip: ip.to_string(),
port: port,
};
ds.callbacks.insert("/query_region_list".to_string(), DispatchServer::query_region_list);
ds.callbacks.insert("/query_cur_region".to_string(), DispatchServer::query_cur_region);
ds.callbacks.insert("/account/risky/api/check".to_string(), DispatchServer::risky_api_check);
ds.callbacks.insert("/hk4e_global/mdk/shield/api/login".to_string(), DispatchServer::risky_api_check);
ds.callbacks.insert("/hk4e_global/combo/granter/login/v2/login".to_string(), DispatchServer::granter_login);
ds.callbacks.insert("/hk4e_global/mdk/shield/api/verify".to_string(), DispatchServer::shield_verify);
return ds; return ds;
} }
pub fn run(&self) { pub fn run(self) {
for stream in self.listener.incoming() { let mut sys = System::new("http-server");
let stream = stream.unwrap(); let slef = Arc::new(self);
executor::block_on(slef.run_internal());
self.handle_connection(stream); System::current().stop();
} println!("Finished!");
} }
fn handle_connection(&self, mut stream: TcpStream) { async fn run_internal(self: &Arc<Self>) {
let mut buffer = [0; 1024]; //let (http_port, https_port) = (2880, 2443);
let (http_port, https_port) = (80, 443);
stream.read(&mut buffer).unwrap(); let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls_server()).unwrap();
builder.set_verify(SslVerifyMode::NONE);
builder.set_min_proto_version(None).unwrap();
builder.set_cipher_list("DEFAULT").unwrap();
builder.set_mode(SslMode::NO_AUTO_CHAIN | SslMode::SEND_FALLBACK_SCSV);
builder.set_private_key_file("keys/ssl.key", SslFiletype::PEM).unwrap();
builder.set_certificate_chain_file("keys/ssl.cer").unwrap();
let buffer = String::from_utf8_lossy(&buffer); let http_server = HttpServer::new(move || {
App::new()
.wrap(Logger::default())
.route("/", web::get().to(|| HttpResponse::Ok()))
.route("/query_region_list", web::get().to(DispatchServer::query_region_list))
.route("/query_cur_region", web::get().to(DispatchServer::query_cur_region))
//.route("", web::post().to(DispatchServer::))
.route("/hk4e_global/mdk/shield/api/verify", web::post().to(DispatchServer::shield_verify))
.route("/account/risky/api/check", web::post().to(DispatchServer::risky_api_check))
.route("/hk4e_global/mdk/shield/api/login", web::post().to(DispatchServer::shield_login))
.route("/hk4e_global/combo/granter/login/v2/login", web::post().to(DispatchServer::granter_login))
})
.bind(format!("127.0.0.1:{}", http_port)).expect("Failed to bind HTTP port")
.bind_openssl(format!("127.0.0.1:{}", https_port), builder).expect("Failed to bind HTTPS port")
.run();
let data = buffer.split_whitespace().collect::<Vec<_>>(); http_server.stop(true).await;
let method = data[0];
let url = data[1];
println!("Access to '{}' using {}", url, method);
let (status_code, contents) = match method {
"GET" | "POST" => {
let parts = url.split('?').collect::<Vec<_>>();
let uri = parts[0];
let params = if parts.len() > 1 { parts[1].split('&').collect::<Vec<_>>() } else { Vec::new() };
let params = params.into_iter().map(|x| x.split('=').collect::<Vec<_>>()).filter(|x| x.len() > 1).map(|x| (x[0], x[1])).collect();
match self.callbacks.get(uri) {
Some(callback) => callback(&self, params),
None => ("404 Not Found".into(), "Nothing here".into()),
}
},
_ => ("400 Bad Request".into(), "You are stupid".into()),
};
//let contents = fs::read_to_string(filename).unwrap();
let status_line = format!("HTTP/1.1 {}", status_code);
let response = format!(
"{}\r\nContent-Length: {}\r\n\r\n{}",
status_line,
contents.len(),
contents
);
stream.write(response.as_bytes()).unwrap();
stream.flush().unwrap();
} }
fn query_region_list(&self, args: Vec<(&str, &str)>) -> (String, String) { async fn query_region_list(c: web::Query<ClientInfo>) -> String {
println!("Args: {:?}", args); println!("Client: {:?}", c);
let keys = self.load_keys("master"); let keys = DispatchServer::load_keys("master");
let mut region_info = proto::RegionSimpleInfo::default(); let mut region_info = proto::RegionSimpleInfo::default();
region_info.name = "private_server".into(); region_info.name = "private_server".into();
region_info.title = "Private Server".into(); region_info.title = "Private Server".into();
region_info.r#type = "DEV_PUBLIC".into(); region_info.r#type = "DEV_PUBLIC".into();
region_info.dispatch_url = format!("http://localhost:{}/query_cur_region", self.port); region_info.dispatch_url = format!("http://localhost:{}/query_cur_region", 80);
let mut region_list = proto::QueryRegionListHttpRsp::default(); let mut region_list = proto::QueryRegionListHttpRsp::default();
region_list.region_list = vec![region_info]; region_list.region_list = vec![region_info];
@ -120,13 +142,13 @@ impl DispatchServer {
region_list.encode(&mut region_list_buf).unwrap(); region_list.encode(&mut region_list_buf).unwrap();
return ("200 OK".into(), base64::encode(region_list_buf)); return base64::encode(region_list_buf);
} }
fn query_cur_region(&self, args: Vec<(&str, &str)>) -> (String, String) { async fn query_cur_region(c: web::Query<ClientInfo>) -> String {
println!("Args: {:?}", args); println!("Client: {:?}", c);
let keys = self.load_keys("master"); let keys = DispatchServer::load_keys("master");
let mut region_info = proto::RegionInfo::default(); let mut region_info = proto::RegionInfo::default();
region_info.gateserver_ip = "127.0.0.1".to_string(); region_info.gateserver_ip = "127.0.0.1".to_string();
@ -138,7 +160,7 @@ impl DispatchServer {
region_config.client_secret_key = keys.0.clone(); region_config.client_secret_key = keys.0.clone();
let json_config = format!("{{\"coverSwitch\": [\"8\"], \"perf_report_config_url\": \"http://localhost:{}/config/verify\", \"perf_report_record_url\": \"http://localhost:{}/dataUpload\" }}", let json_config = format!("{{\"coverSwitch\": [\"8\"], \"perf_report_config_url\": \"http://localhost:{}/config/verify\", \"perf_report_record_url\": \"http://localhost:{}/dataUpload\" }}",
self.port, self.port); 80, 80);
let mut custom_config = json_config.as_bytes().to_owned(); let mut custom_config = json_config.as_bytes().to_owned();
@ -150,38 +172,57 @@ impl DispatchServer {
region_config.encode(&mut region_conf_buf).unwrap(); region_config.encode(&mut region_conf_buf).unwrap();
return ("200 OK".into(), base64::encode(region_conf_buf)); return base64::encode(region_conf_buf);
} }
fn risky_api_check(&self, args: Vec<(&str, &str)>) -> (String, String) { async fn risky_api_check(a: web::Json<ActionToCheck>) -> String {
println!("Action: {:?}", a);
let email = "ceo@hoyolab.com"; let email = "ceo@hoyolab.com";
let name = "Ceo"; let name = "Ceo";
let token = "Fake-token-hahaha"; let token = "Fake-token-hahaha";
let uid = 0x1234; let uid = 0x1234;
let payload = self.build_account_data(email, name, token, uid); let payload = DispatchServer::build_account_data(email, name, token, uid);
return ("200 OK".into(), self.make_answer(0, &payload)); return DispatchServer::make_answer(0, &payload);
} }
fn granter_login(&self, args: Vec<(&str, &str)>) -> (String, String) { async fn shield_login(l: web::Json<LoginData>) -> String {
let payload = self.verify_token_v2(); println!("Login: {:?}", l);
return ("200 OK".into(), self.make_answer(0, &payload));
}
fn shield_verify(&self, args: Vec<(&str, &str)>) -> (String, String) {
let email = "ceo@hoyolab.com"; let email = "ceo@hoyolab.com";
let name = "Ceo"; let name = "Ceo";
let token = "Fake-token-hahaha"; let token = "Fake-token-hahaha";
let uid = 0x1234; let uid = 0x1234;
let payload = self.build_account_data(email, name, token, uid); let payload = DispatchServer::build_account_data(email, name, token, uid);
return ("200 OK".into(), self.make_answer(0, &payload)); return DispatchServer::make_answer(0, &payload);
} }
fn load_keys(&self, name: &str) -> (Vec<u8>, Vec<u8>) { async fn granter_login(g: web::Json<GranterData>) -> String {
println!("Granter: {:?}", g);
let payload = DispatchServer::verify_token_v2();
return DispatchServer::make_answer(0, &payload);
}
async fn shield_verify(t: web::Json<TokenToVerify>) -> String {
println!("Token: {:?}", t);
let email = "ceo@hoyolab.com";
let name = "Ceo";
let token = "Fake-token-hahaha";
let uid = t.uid.parse().unwrap();
let payload = DispatchServer::build_account_data(email, name, token, uid);
return DispatchServer::make_answer(0, &payload);
}
fn load_keys(name: &str) -> (Vec<u8>, Vec<u8>) {
// Key // Key
let filename = format!("./{}/{}.key", "keys", name); let filename = format!("./{}/{}.key", "keys", name);
let mut f = fs::File::open(&filename).expect(&format!("File '{}' not found", filename)); let mut f = fs::File::open(&filename).expect(&format!("File '{}' not found", filename));
@ -197,12 +238,16 @@ impl DispatchServer {
return (ec2b, key); return (ec2b, key);
} }
fn verify_token_v2(&self) -> String { fn verify_token_v2() -> String {
let account_type = 1; let account_type = 1;
let combo_id = 0x4321; let combo_id = 0x4321;
let combo_token = "Fake-token-hehehe";
let open_id = 0x1234; let open_id = 0x1234;
#[cfg(not(feature = "raw_packet_dump"))]
let combo_token = "Fake-token-hehehe";
#[cfg(feature = "raw_packet_dump")]
let combo_token = std::str::from_utf8(&[32u8; 4096*3]).unwrap();
return format!("{{ return format!("{{
\"account_type\": \"{}\", \"account_type\": \"{}\",
\"combo_id\": \"{}\", \"combo_id\": \"{}\",
@ -213,7 +258,7 @@ impl DispatchServer {
}}", account_type, combo_id, combo_token, open_id); }}", account_type, combo_id, combo_token, open_id);
} }
fn build_account_data(&self, email: &str, name: &str, token: &str, uid: i32) -> String { fn build_account_data(email: &str, name: &str, token: &str, uid: i32) -> String {
let payload = format!("{{ let payload = format!("{{
\"account\": {{ \"account\": {{
\"apple_name\": \"\", \"apple_name\": \"\",
@ -242,7 +287,7 @@ impl DispatchServer {
return payload.into(); return payload.into();
} }
fn make_answer(&self, code: i32, data: &str) -> String { fn make_answer(code: i32, data: &str) -> String {
let message = match code { let message = match code {
0 => "OK", 0 => "OK",
-1 => "not matched", -1 => "not matched",

View File

@ -4,6 +4,7 @@ use std::collections::HashMap;
use std::collections::hash_map::Entry::{Occupied, Vacant}; use std::collections::hash_map::Entry::{Occupied, Vacant};
use crate::server::GameWorld; use crate::server::GameWorld;
use packet_processor::PacketProcessor;
use crate::server::IpcMessage; use crate::server::IpcMessage;
pub struct GameServer { pub struct GameServer {
@ -27,15 +28,15 @@ impl GameServer {
let world_processor = thread::spawn(move || { let world_processor = thread::spawn(move || {
println!("Starting world processor"); println!("Starting world processor");
// TODO: Load worlds! // TODO: Load worlds!
loop { //loop {
} //}
}); });
loop { loop {
let IpcMessage(conv, packet_id, metadata, data) = self.packets_to_process_rx.recv().unwrap(); let IpcMessage(user_id, packet_id, metadata, data) = self.packets_to_process_rx.recv().unwrap();
// TODO: each conv will have a distinct world! // TODO: each user_id will have a distinct world!
let world = match self.worlds.entry(conv) { let world = match self.worlds.entry(user_id) {
Occupied(world) => world.into_mut(), Occupied(world) => world.into_mut(),
Vacant(entry) => { Vacant(entry) => {
let mut world = GameWorld::new(self.packets_to_send_tx.clone()); let mut world = GameWorld::new(self.packets_to_send_tx.clone());
@ -43,7 +44,7 @@ impl GameServer {
}, },
}; };
world.process_packet(conv, packet_id, metadata, data); world.process(user_id, packet_id, metadata, data);
} }
} }
} }

File diff suppressed because it is too large Load Diff

View File

@ -1,12 +1,9 @@
use crate::proto;
use prost;
use prost::Message; use prost::Message;
pub struct IpcMessage(pub u32, pub proto::PacketId, pub Vec<u8>, pub Vec<u8>); pub struct IpcMessage(pub u32, pub proto::PacketId, pub Vec<u8>, pub Vec<u8>);
impl IpcMessage { impl IpcMessage {
pub fn new_from_proto<M: prost::Message>(conv: u32, packet_id: proto::PacketId, metadata: &proto::PacketHead, data: &M) -> IpcMessage { pub fn new_from_proto<M: prost::Message>(user_id: u32, packet_id: proto::PacketId, metadata: &proto::PacketHead, data: &M) -> IpcMessage {
println!("Replying with {:?}", packet_id); println!("Replying with {:?}", packet_id);
println!("Data: {:?}", data); println!("Data: {:?}", data);
@ -19,7 +16,7 @@ impl IpcMessage {
metadata.encode(&mut metabuf).unwrap(); metadata.encode(&mut metabuf).unwrap();
return IpcMessage( return IpcMessage(
conv, user_id,
packet_id, packet_id,
metabuf, metabuf,
buf buf

View File

@ -16,21 +16,25 @@ use crate::utils::HandshakePacket;
use crate::utils::DataPacket; use crate::utils::DataPacket;
use crate::server::ClientConnection; use crate::server::ClientConnection;
use crate::server::GameServer; use crate::server::GameServer;
use crate::server::AuthManager;
use crate::server::IpcMessage; use crate::server::IpcMessage;
use crate::proto; use proto::PacketHead;
use crate::proto::PacketHead; use proto::GetPlayerTokenRsp;
use crate::proto::GetPlayerTokenRsp; use proto::get_player_token_rsp;
use crate::proto::get_player_token_rsp;
use prost::Message; use prost::Message;
use packet_processor::PacketProcessor;
extern crate kcp; extern crate kcp;
pub struct NetworkServer { pub struct NetworkServer {
socket: UdpSocket, socket: UdpSocket,
clients: Arc<Mutex<HashMap<u32,ClientConnection>>>, clients: Arc<Mutex<HashMap<u32,ClientConnection>>>,
packets_to_process_tx: Option<mpsc::Sender<IpcMessage>>, packets_to_process_tx: Option<mpsc::Sender<IpcMessage>>,
packets_to_send_tx: Option<mpsc::Sender<IpcMessage>>,
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@ -59,6 +63,7 @@ impl NetworkServer {
}, },
clients: Arc::new(Mutex::new(HashMap::new())), clients: Arc::new(Mutex::new(HashMap::new())),
packets_to_process_tx: None, packets_to_process_tx: None,
packets_to_send_tx: None,
}; };
print!("Connection established\n"); print!("Connection established\n");
@ -76,12 +81,20 @@ impl NetworkServer {
let (packets_to_send_tx, packets_to_send_rx) = mpsc::channel(); let (packets_to_send_tx, packets_to_send_rx) = mpsc::channel();
self.packets_to_process_tx = Some(packets_to_process_tx); self.packets_to_process_tx = Some(packets_to_process_tx);
self.packets_to_send_tx = Some(packets_to_send_tx.clone()); // TODO: hack!
let clients = self.clients.clone(); let clients = self.clients.clone();
let mut auth_manager = Arc::new(Mutex::new(AuthManager::new(packets_to_send_tx.clone())));
let am = auth_manager.clone();
let packet_relaying_thread = thread::spawn(move || { let packet_relaying_thread = thread::spawn(move || {
loop { loop {
let IpcMessage(conv, packet_id, metadata, data) = packets_to_send_rx.recv().unwrap(); let IpcMessage(user_id, packet_id, metadata, data) = packets_to_send_rx.recv().unwrap();
let conv = match packet_id {
proto::PacketId::GetPlayerTokenRsp => user_id, // Mapping is not performed on those
_ => am.lock().unwrap().resolve_uid(user_id).unwrap_or_else(|| panic!("Unknown user ID {}!", user_id)),
};
let data_packet = DataPacket::new(packet_id.clone() as u16, metadata, data.clone()); let data_packet = DataPacket::new(packet_id.clone() as u16, metadata, data.clone());
@ -110,7 +123,7 @@ impl NetworkServer {
loop { loop {
match self.socket.recv_from(&mut buffer) { match self.socket.recv_from(&mut buffer) {
Ok( (bytes_number, source_address) ) => self.process_udp_packet(source_address, &buffer[..bytes_number]), Ok( (bytes_number, source_address) ) => self.process_udp_packet(source_address, &buffer[..bytes_number], &mut auth_manager),
Err(e) => panic!("Failed to receive data: {}", e), Err(e) => panic!("Failed to receive data: {}", e),
} }
} }
@ -120,16 +133,16 @@ impl NetworkServer {
return Ok(0); return Ok(0);
} }
fn process_udp_packet(&mut self, source_address: SocketAddr, packet_bytes: &[u8]) { fn process_udp_packet(&mut self, source_address: SocketAddr, packet_bytes: &[u8], auth_manager: &mut Arc<Mutex<AuthManager>>) {
print!("Received packet! Len = {}\n", packet_bytes.len()); //print!("Received packet! Len = {}\n", packet_bytes.len());
let hs_packet = HandshakePacket::new(packet_bytes); let hs_packet = HandshakePacket::new(packet_bytes);
match hs_packet { match hs_packet {
Ok(hs_packet) => { Ok(hs_packet) => {
print!("Received handshake packet: {:#?}\n", hs_packet); //print!("Received handshake packet: {:#?}\n", hs_packet);
if hs_packet.is_connect() { if hs_packet.is_connect() {
print!("Sending reply to CONNECT\n"); //print!("Sending reply to CONNECT\n");
// TODO: assign conv and token! // TODO: assign conv and token!
let conv = 0x96969696u32; let conv = 0x96969696u32;
let token = 0x42424242u32; let token = 0x42424242u32;
@ -158,13 +171,13 @@ impl NetworkServer {
}; };
for packet in packets.iter() { for packet in packets.iter() {
self.process_game_packet(conv, packet); self.process_game_packet(conv, packet, auth_manager);
} }
}, },
}; };
} }
fn process_game_packet(&mut self, conv: u32, packet: &[u8]) { fn process_game_packet(&mut self, conv: u32, packet: &[u8], auth_manager: &mut Arc<Mutex<AuthManager>>) {
let data = match DataPacket::new_from_bytes(packet) { let data = match DataPacket::new_from_bytes(packet) {
Ok(data) => data, Ok(data) => data,
Err(e) => panic!("Malformed data packet: {:#?}!", e), Err(e) => panic!("Malformed data packet: {:#?}!", e),
@ -183,13 +196,46 @@ impl NetworkServer {
} }
}; };
println!("Got packet {:?}", packet_id); let user_id = match packet_id {
proto::PacketId::GetPlayerTokenReq => {
auth_manager.lock().unwrap().process(conv, packet_id, data.metadata, data.data);
return;
},
_ => match auth_manager.lock().unwrap().resolve_conv(conv) {
None => {
println!("Unknown user with conv {}! Skipping", conv);
return;
},
Some(user_id) => user_id,
},
};
if packet_id == proto::PacketId::UnionCmdNotify {
let union = proto::UnionCmdNotify::decode(&mut Cursor::new(&data.data)).unwrap();
for u_cmd in union.cmd_list.into_iter() {
self.send_packet_to_process(user_id, u_cmd.message_id as u16, &data.metadata, &u_cmd.body);
}
} else {
self.send_packet_to_process(user_id, data.packet_id, &data.metadata, &data.data);
}
}
fn send_packet_to_process(&mut self, user_id: u32, packet_id: u16, metadata: &[u8], data: &[u8])
{
let sender = match &self.packets_to_process_tx { let sender = match &self.packets_to_process_tx {
Some(sender) => sender, Some(sender) => sender,
None => panic!("Processing queue wasn't set up!"), None => panic!("Processing queue wasn't set up!"),
}; };
sender.send( IpcMessage(conv, packet_id, data.metadata, data.data) ).unwrap(); let packet_id: proto::PacketId = match FromPrimitive::from_u16(packet_id) {
Some(packet_id) => packet_id,
None => {
println!("Skipping unknown packet ID {}", packet_id);
return;
},
};
println!("Got packet {:?}", packet_id);
sender.send( IpcMessage(user_id, packet_id, metadata.to_vec(), data.to_vec()) ).unwrap();
} }
} }