mirror of
https://github.com/redstrate/Kawari.git
synced 2025-07-10 07:57:46 +00:00
Begin correctly implementing packet obsfucation
I re-implemented Unscrambler, but in reverse! This currently only affects names in the PlayerSpawn packet, it needs to be extended into others to be considered complete. See #9
This commit is contained in:
parent
8894e25da4
commit
fb46a44e18
17 changed files with 271 additions and 53 deletions
18
build.rs
18
build.rs
|
@ -78,6 +78,24 @@ fn main() {
|
||||||
output_str.push_str("}\n\n");
|
output_str.push_str("}\n\n");
|
||||||
output_str.push_str("}\n\n");
|
output_str.push_str("}\n\n");
|
||||||
|
|
||||||
|
// opcodes
|
||||||
|
output_str.push_str("/// Returns the integer opcode.\n");
|
||||||
|
output_str.push_str("pub fn get_opcode(&self) -> u16 {\n");
|
||||||
|
output_str.push_str("match self {\n");
|
||||||
|
|
||||||
|
for opcode in opcodes {
|
||||||
|
let opcode = opcode.as_object().unwrap();
|
||||||
|
let name = opcode.get("name").unwrap().as_str().unwrap();
|
||||||
|
let opcode = opcode.get("opcode").unwrap().as_number().unwrap();
|
||||||
|
|
||||||
|
output_str.push_str(&format!("{key}::{name} => {opcode},\n"));
|
||||||
|
}
|
||||||
|
|
||||||
|
output_str.push_str(&format!("{key}::Unknown(opcode) => *opcode,\n"));
|
||||||
|
|
||||||
|
output_str.push_str("}\n\n");
|
||||||
|
output_str.push_str("}\n\n");
|
||||||
|
|
||||||
// end impl
|
// end impl
|
||||||
output_str.push_str("}\n\n");
|
output_str.push_str("}\n\n");
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,7 +20,9 @@ use kawari::packet::oodle::OodleNetwork;
|
||||||
use kawari::packet::{
|
use kawari::packet::{
|
||||||
ConnectionType, PacketSegment, PacketState, SegmentData, SegmentType, send_keep_alive,
|
ConnectionType, PacketSegment, PacketState, SegmentData, SegmentType, send_keep_alive,
|
||||||
};
|
};
|
||||||
use kawari::world::{ChatHandler, ExtraLuaState, LuaZone, Zone, ZoneConnection, load_init_script};
|
use kawari::world::{
|
||||||
|
ChatHandler, ExtraLuaState, LuaZone, ObsfucationData, Zone, ZoneConnection, load_init_script,
|
||||||
|
};
|
||||||
use kawari::world::{
|
use kawari::world::{
|
||||||
ClientHandle, Event, FromServer, LuaPlayer, PlayerData, ServerHandle, StatusEffects, ToServer,
|
ClientHandle, Event, FromServer, LuaPlayer, PlayerData, ServerHandle, StatusEffects, ToServer,
|
||||||
WorldDatabase, handle_custom_ipc, server_main_loop,
|
WorldDatabase, handle_custom_ipc, server_main_loop,
|
||||||
|
@ -1099,6 +1101,7 @@ async fn main() {
|
||||||
last_keep_alive: Instant::now(),
|
last_keep_alive: Instant::now(),
|
||||||
gracefully_logged_out: false,
|
gracefully_logged_out: false,
|
||||||
weather_id: 0,
|
weather_id: 0,
|
||||||
|
obsfucation_data: ObsfucationData::default(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
Some((mut socket, _)) = handle_rcon(&rcon_listener) => {
|
Some((mut socket, _)) = handle_rcon(&rcon_listener) => {
|
||||||
|
|
|
@ -2,20 +2,23 @@ use binrw::binrw;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
opcodes::ServerChatIpcType,
|
opcodes::ServerChatIpcType,
|
||||||
packet::{IpcSegment, ReadWriteIpcSegment},
|
packet::{IPC_HEADER_SIZE, IpcSegment, ReadWriteIpcSegment},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub type ServerChatIpcSegment = IpcSegment<ServerChatIpcType, ServerChatIpcData>;
|
pub type ServerChatIpcSegment = IpcSegment<ServerChatIpcType, ServerChatIpcData>;
|
||||||
|
|
||||||
impl ReadWriteIpcSegment for ServerChatIpcSegment {
|
impl ReadWriteIpcSegment for ServerChatIpcSegment {
|
||||||
fn calc_size(&self) -> u32 {
|
fn calc_size(&self) -> u32 {
|
||||||
// 16 is the size of the IPC header
|
IPC_HEADER_SIZE + self.op_code.calc_size()
|
||||||
16 + self.op_code.calc_size()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_name(&self) -> &'static str {
|
fn get_name(&self) -> &'static str {
|
||||||
self.op_code.get_name()
|
self.op_code.get_name()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_opcode(&self) -> u16 {
|
||||||
|
self.op_code.get_opcode()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: make generic
|
// TODO: make generic
|
||||||
|
|
|
@ -3,15 +3,15 @@ use binrw::binrw;
|
||||||
use crate::{
|
use crate::{
|
||||||
common::{CHAR_NAME_MAX_LENGTH, read_bool_from, read_string, write_bool_as, write_string},
|
common::{CHAR_NAME_MAX_LENGTH, read_bool_from, read_string, write_bool_as, write_string},
|
||||||
ipc::lobby::CharacterDetails,
|
ipc::lobby::CharacterDetails,
|
||||||
packet::{IpcSegment, ReadWriteIpcSegment},
|
packet::{IPC_HEADER_SIZE, IpcSegment, ReadWriteIpcSegment},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub type CustomIpcSegment = IpcSegment<CustomIpcType, CustomIpcData>;
|
pub type CustomIpcSegment = IpcSegment<CustomIpcType, CustomIpcData>;
|
||||||
|
|
||||||
impl ReadWriteIpcSegment for CustomIpcSegment {
|
impl ReadWriteIpcSegment for CustomIpcSegment {
|
||||||
fn calc_size(&self) -> u32 {
|
fn calc_size(&self) -> u32 {
|
||||||
// 16 is the size of the IPC header
|
IPC_HEADER_SIZE
|
||||||
16 + match self.op_code {
|
+ match self.op_code {
|
||||||
CustomIpcType::RequestCreateCharacter => 1024 + CHAR_NAME_MAX_LENGTH as u32,
|
CustomIpcType::RequestCreateCharacter => 1024 + CHAR_NAME_MAX_LENGTH as u32,
|
||||||
CustomIpcType::CharacterCreated => 12,
|
CustomIpcType::CharacterCreated => 12,
|
||||||
CustomIpcType::GetActorId => 8,
|
CustomIpcType::GetActorId => 8,
|
||||||
|
@ -31,6 +31,10 @@ impl ReadWriteIpcSegment for CustomIpcSegment {
|
||||||
fn get_name(&self) -> &'static str {
|
fn get_name(&self) -> &'static str {
|
||||||
""
|
""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_opcode(&self) -> u16 {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for CustomIpcSegment {
|
impl Default for CustomIpcSegment {
|
||||||
|
|
|
@ -15,20 +15,23 @@ pub use login_reply::{LoginReply, ServiceAccount};
|
||||||
use crate::{
|
use crate::{
|
||||||
common::{read_string, write_string},
|
common::{read_string, write_string},
|
||||||
opcodes::{ClientLobbyIpcType, ServerLobbyIpcType},
|
opcodes::{ClientLobbyIpcType, ServerLobbyIpcType},
|
||||||
packet::{IpcSegment, ReadWriteIpcSegment},
|
packet::{IPC_HEADER_SIZE, IpcSegment, ReadWriteIpcSegment},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub type ClientLobbyIpcSegment = IpcSegment<ClientLobbyIpcType, ClientLobbyIpcData>;
|
pub type ClientLobbyIpcSegment = IpcSegment<ClientLobbyIpcType, ClientLobbyIpcData>;
|
||||||
|
|
||||||
impl ReadWriteIpcSegment for ClientLobbyIpcSegment {
|
impl ReadWriteIpcSegment for ClientLobbyIpcSegment {
|
||||||
fn calc_size(&self) -> u32 {
|
fn calc_size(&self) -> u32 {
|
||||||
// 16 is the size of the IPC header
|
IPC_HEADER_SIZE + self.op_code.calc_size()
|
||||||
16 + self.op_code.calc_size()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_name(&self) -> &'static str {
|
fn get_name(&self) -> &'static str {
|
||||||
self.op_code.get_name()
|
self.op_code.get_name()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_opcode(&self) -> u16 {
|
||||||
|
self.op_code.get_opcode()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: make generic
|
// TODO: make generic
|
||||||
|
@ -53,13 +56,16 @@ pub type ServerLobbyIpcSegment = IpcSegment<ServerLobbyIpcType, ServerLobbyIpcDa
|
||||||
|
|
||||||
impl ReadWriteIpcSegment for ServerLobbyIpcSegment {
|
impl ReadWriteIpcSegment for ServerLobbyIpcSegment {
|
||||||
fn calc_size(&self) -> u32 {
|
fn calc_size(&self) -> u32 {
|
||||||
// 16 is the size of the IPC header
|
IPC_HEADER_SIZE + self.op_code.calc_size()
|
||||||
16 + self.op_code.calc_size()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_name(&self) -> &'static str {
|
fn get_name(&self) -> &'static str {
|
||||||
self.op_code.get_name()
|
self.op_code.get_name()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_opcode(&self) -> u16 {
|
||||||
|
self.op_code.get_opcode()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: make generic
|
// TODO: make generic
|
||||||
|
|
|
@ -17,8 +17,12 @@ pub struct InitZone {
|
||||||
/// Zero means "no obsfucation" (not really, but functionally yes.)
|
/// Zero means "no obsfucation" (not really, but functionally yes.)
|
||||||
/// To enable obsfucation, you need to set this to a constant that changes every patch. See lib.rs for the constant.
|
/// To enable obsfucation, you need to set this to a constant that changes every patch. See lib.rs for the constant.
|
||||||
pub obsfucation_mode: u8,
|
pub obsfucation_mode: u8,
|
||||||
pub unk1: u8,
|
/// First seed used in deobsfucation on the client side.
|
||||||
pub unk2: u32,
|
pub seed1: u8,
|
||||||
|
/// Second seed used in deobsfucation on the client side.
|
||||||
|
pub seed2: u8,
|
||||||
|
/// Third seed used in deobsfucation on the client size.
|
||||||
|
pub seed3: u32,
|
||||||
pub festival_id: u16,
|
pub festival_id: u16,
|
||||||
pub additional_festival_id: u16,
|
pub additional_festival_id: u16,
|
||||||
pub unk3: u32,
|
pub unk3: u32,
|
||||||
|
@ -26,7 +30,7 @@ pub struct InitZone {
|
||||||
pub unk5: u32,
|
pub unk5: u32,
|
||||||
pub unk6: [u32; 4],
|
pub unk6: [u32; 4],
|
||||||
pub unk7: [u32; 3],
|
pub unk7: [u32; 3],
|
||||||
pub unk8_9: [u8; 9],
|
pub unk8_9: [u8; 8],
|
||||||
pub position: Position,
|
pub position: Position,
|
||||||
pub unk8: [u32; 4],
|
pub unk8: [u32; 4],
|
||||||
pub unk9: u32,
|
pub unk9: u32,
|
||||||
|
|
|
@ -94,6 +94,7 @@ use crate::common::read_string;
|
||||||
use crate::common::write_string;
|
use crate::common::write_string;
|
||||||
use crate::opcodes::ClientZoneIpcType;
|
use crate::opcodes::ClientZoneIpcType;
|
||||||
use crate::opcodes::ServerZoneIpcType;
|
use crate::opcodes::ServerZoneIpcType;
|
||||||
|
use crate::packet::IPC_HEADER_SIZE;
|
||||||
use crate::packet::IpcSegment;
|
use crate::packet::IpcSegment;
|
||||||
use crate::packet::ReadWriteIpcSegment;
|
use crate::packet::ReadWriteIpcSegment;
|
||||||
|
|
||||||
|
@ -101,13 +102,16 @@ pub type ClientZoneIpcSegment = IpcSegment<ClientZoneIpcType, ClientZoneIpcData>
|
||||||
|
|
||||||
impl ReadWriteIpcSegment for ClientZoneIpcSegment {
|
impl ReadWriteIpcSegment for ClientZoneIpcSegment {
|
||||||
fn calc_size(&self) -> u32 {
|
fn calc_size(&self) -> u32 {
|
||||||
// 16 is the size of the IPC header
|
IPC_HEADER_SIZE + self.op_code.calc_size()
|
||||||
16 + self.op_code.calc_size()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_name(&self) -> &'static str {
|
fn get_name(&self) -> &'static str {
|
||||||
self.op_code.get_name()
|
self.op_code.get_name()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_opcode(&self) -> u16 {
|
||||||
|
self.op_code.get_opcode()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: make generic
|
// TODO: make generic
|
||||||
|
@ -128,13 +132,16 @@ pub type ServerZoneIpcSegment = IpcSegment<ServerZoneIpcType, ServerZoneIpcData>
|
||||||
|
|
||||||
impl ReadWriteIpcSegment for ServerZoneIpcSegment {
|
impl ReadWriteIpcSegment for ServerZoneIpcSegment {
|
||||||
fn calc_size(&self) -> u32 {
|
fn calc_size(&self) -> u32 {
|
||||||
// 16 is the size of the IPC header
|
IPC_HEADER_SIZE + self.op_code.calc_size()
|
||||||
16 + self.op_code.calc_size()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_name(&self) -> &'static str {
|
fn get_name(&self) -> &'static str {
|
||||||
self.op_code.get_name()
|
self.op_code.get_name()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_opcode(&self) -> u16 {
|
||||||
|
self.op_code.get_opcode()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: make generic
|
// TODO: make generic
|
||||||
|
|
|
@ -66,9 +66,6 @@ pub fn get_supported_expac_versions() -> HashMap<&'static str, Version<'static>>
|
||||||
HashMap::from(SUPPORTED_EXPAC_VERSIONS)
|
HashMap::from(SUPPORTED_EXPAC_VERSIONS)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Constant to enable packet obsfucation. Changes every patch.
|
|
||||||
pub const OBFUSCATION_ENABLED_MODE: u8 = 41;
|
|
||||||
|
|
||||||
/// The size of the unlock bitmask.
|
/// The size of the unlock bitmask.
|
||||||
pub const UNLOCK_BITMASK_SIZE: usize = 92;
|
pub const UNLOCK_BITMASK_SIZE: usize = 92;
|
||||||
|
|
||||||
|
|
|
@ -54,6 +54,7 @@ impl LobbyConnection {
|
||||||
ConnectionType::Lobby,
|
ConnectionType::Lobby,
|
||||||
CompressionType::Uncompressed,
|
CompressionType::Uncompressed,
|
||||||
&[segment],
|
&[segment],
|
||||||
|
None,
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
|
@ -166,6 +167,7 @@ impl LobbyConnection {
|
||||||
ConnectionType::Lobby,
|
ConnectionType::Lobby,
|
||||||
CompressionType::Uncompressed,
|
CompressionType::Uncompressed,
|
||||||
&packets,
|
&packets,
|
||||||
|
None,
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
|
@ -560,6 +562,7 @@ pub async fn send_custom_world_packet(segment: CustomIpcSegment) -> Option<Custo
|
||||||
ConnectionType::None,
|
ConnectionType::None,
|
||||||
CompressionType::Uncompressed,
|
CompressionType::Uncompressed,
|
||||||
&[segment],
|
&[segment],
|
||||||
|
None,
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
|
|
|
@ -6,9 +6,10 @@ use binrw::{BinRead, BinResult};
|
||||||
use crate::{
|
use crate::{
|
||||||
config::get_config,
|
config::get_config,
|
||||||
packet::{PacketHeader, PacketSegment},
|
packet::{PacketHeader, PacketSegment},
|
||||||
|
world::ScramblerKeys,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{PacketState, ReadWriteIpcSegment, oodle::OodleNetwork};
|
use super::{IPC_HEADER_SIZE, PacketState, ReadWriteIpcSegment, SegmentData, oodle::OodleNetwork};
|
||||||
|
|
||||||
#[binrw]
|
#[binrw]
|
||||||
#[brw(repr = u8)]
|
#[brw(repr = u8)]
|
||||||
|
@ -77,18 +78,44 @@ pub(crate) fn compress<T: ReadWriteIpcSegment>(
|
||||||
state: &mut PacketState,
|
state: &mut PacketState,
|
||||||
compression_type: &CompressionType,
|
compression_type: &CompressionType,
|
||||||
segments: &[PacketSegment<T>],
|
segments: &[PacketSegment<T>],
|
||||||
|
keys: Option<&ScramblerKeys>,
|
||||||
) -> (Vec<u8>, usize) {
|
) -> (Vec<u8>, usize) {
|
||||||
let mut segments_buffer = Cursor::new(Vec::new());
|
let mut segments_buffer = Vec::new();
|
||||||
for segment in segments {
|
for segment in segments {
|
||||||
|
let mut buffer = Vec::new();
|
||||||
|
|
||||||
|
// write to buffer
|
||||||
|
{
|
||||||
|
let mut cursor = Cursor::new(&mut buffer);
|
||||||
|
|
||||||
segment
|
segment
|
||||||
.write_le_args(
|
.write_le_args(
|
||||||
&mut segments_buffer,
|
&mut cursor,
|
||||||
(state.client_key.as_ref().map(|s: &[u8; 16]| s.as_slice()),),
|
(state.client_key.as_ref().map(|s: &[u8; 16]| s.as_slice()),),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
let segments_buffer = segments_buffer.into_inner();
|
// obsfucate if needed
|
||||||
|
if let Some(keys) = keys {
|
||||||
|
if let SegmentData::Ipc { data } = &segment.data {
|
||||||
|
let opcode = data.get_opcode();
|
||||||
|
let base_key = keys.get_base_key(opcode);
|
||||||
|
|
||||||
|
if data.get_name() == "PlayerSpawn" {
|
||||||
|
let name_offset = 610;
|
||||||
|
for i in 0..32 {
|
||||||
|
buffer[(IPC_HEADER_SIZE + name_offset + i) as usize] = buffer
|
||||||
|
[(IPC_HEADER_SIZE + name_offset + i) as usize]
|
||||||
|
.wrapping_add(base_key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
segments_buffer.append(&mut buffer);
|
||||||
|
}
|
||||||
|
|
||||||
let segments_buffer_len = segments_buffer.len();
|
let segments_buffer_len = segments_buffer.len();
|
||||||
|
|
||||||
match compression_type {
|
match compression_type {
|
||||||
|
|
|
@ -10,6 +10,9 @@ pub trait ReadWriteIpcSegment:
|
||||||
|
|
||||||
/// Returns a human-readable name of the opcode.
|
/// Returns a human-readable name of the opcode.
|
||||||
fn get_name(&self) -> &'static str;
|
fn get_name(&self) -> &'static str;
|
||||||
|
|
||||||
|
/// Returns the integer opcode.
|
||||||
|
fn get_opcode(&self) -> u16;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An IPC packet segment.
|
/// An IPC packet segment.
|
||||||
|
@ -60,3 +63,5 @@ where
|
||||||
#[br(args(&op_code, size))]
|
#[br(args(&op_code, size))]
|
||||||
pub data: Data,
|
pub data: Data,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub const IPC_HEADER_SIZE: u32 = 16;
|
||||||
|
|
|
@ -11,7 +11,7 @@ mod encryption;
|
||||||
pub use encryption::generate_encryption_key;
|
pub use encryption::generate_encryption_key;
|
||||||
|
|
||||||
mod ipc;
|
mod ipc;
|
||||||
pub use ipc::{IpcSegment, ReadWriteIpcSegment};
|
pub use ipc::{IPC_HEADER_SIZE, IpcSegment, ReadWriteIpcSegment};
|
||||||
|
|
||||||
/// Bindings for Oodle network compression.
|
/// Bindings for Oodle network compression.
|
||||||
pub mod oodle;
|
pub mod oodle;
|
||||||
|
|
|
@ -3,7 +3,7 @@ use std::io::Cursor;
|
||||||
use binrw::BinWrite;
|
use binrw::BinWrite;
|
||||||
use tokio::{io::AsyncWriteExt, net::TcpStream};
|
use tokio::{io::AsyncWriteExt, net::TcpStream};
|
||||||
|
|
||||||
use crate::common::timestamp_msecs;
|
use crate::{common::timestamp_msecs, world::ScramblerKeys};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
CompressionType, ConnectionType, PacketHeader, PacketSegment, PacketState, ReadWriteIpcSegment,
|
CompressionType, ConnectionType, PacketHeader, PacketSegment, PacketState, ReadWriteIpcSegment,
|
||||||
|
@ -16,8 +16,9 @@ pub async fn send_packet<T: ReadWriteIpcSegment>(
|
||||||
connection_type: ConnectionType,
|
connection_type: ConnectionType,
|
||||||
compression_type: CompressionType,
|
compression_type: CompressionType,
|
||||||
segments: &[PacketSegment<T>],
|
segments: &[PacketSegment<T>],
|
||||||
|
keys: Option<&ScramblerKeys>,
|
||||||
) {
|
) {
|
||||||
let (data, uncompressed_size) = compress(state, &compression_type, segments);
|
let (data, uncompressed_size) = compress(state, &compression_type, segments, keys);
|
||||||
let size = std::mem::size_of::<PacketHeader>() + data.len();
|
let size = std::mem::size_of::<PacketHeader>() + data.len();
|
||||||
|
|
||||||
let header = PacketHeader {
|
let header = PacketHeader {
|
||||||
|
@ -61,6 +62,7 @@ pub async fn send_keep_alive<T: ReadWriteIpcSegment>(
|
||||||
connection_type,
|
connection_type,
|
||||||
CompressionType::Uncompressed,
|
CompressionType::Uncompressed,
|
||||||
&[response_packet],
|
&[response_packet],
|
||||||
|
None,
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@ use mlua::Function;
|
||||||
use tokio::net::TcpStream;
|
use tokio::net::TcpStream;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
COMPLETED_QUEST_BITMASK_SIZE, OBFUSCATION_ENABLED_MODE,
|
COMPLETED_QUEST_BITMASK_SIZE,
|
||||||
common::{
|
common::{
|
||||||
GameData, ObjectId, ObjectTypeId, Position, timestamp_secs, value_to_flag_byte_index_value,
|
GameData, ObjectId, ObjectTypeId, Position, timestamp_secs, value_to_flag_byte_index_value,
|
||||||
},
|
},
|
||||||
|
@ -34,8 +34,8 @@ use crate::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
Actor, CharacterData, EffectsBuilder, Event, LuaPlayer, StatusEffects, ToServer, WorldDatabase,
|
Actor, CharacterData, EffectsBuilder, Event, LuaPlayer, OBFUSCATION_ENABLED_MODE,
|
||||||
Zone,
|
ScramblerKeyGenerator, ScramblerKeys, StatusEffects, ToServer, WorldDatabase, Zone,
|
||||||
common::{ClientId, ServerHandle},
|
common::{ClientId, ServerHandle},
|
||||||
load_init_script,
|
load_init_script,
|
||||||
lua::Task,
|
lua::Task,
|
||||||
|
@ -85,7 +85,16 @@ pub struct PlayerData {
|
||||||
pub completed_quests: Vec<u8>,
|
pub completed_quests: Vec<u8>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Represents a single connection between an instance of the client and the world server
|
/// Various obsfucation-related bits like the seeds and keys for this connection.
|
||||||
|
#[derive(Debug, Default, Clone)]
|
||||||
|
pub struct ObsfucationData {
|
||||||
|
pub keys: Option<ScramblerKeys>,
|
||||||
|
pub seed1: u8,
|
||||||
|
pub seed2: u8,
|
||||||
|
pub seed3: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Represents a single connection between an instance of the client and the world server.
|
||||||
pub struct ZoneConnection {
|
pub struct ZoneConnection {
|
||||||
pub config: WorldConfig,
|
pub config: WorldConfig,
|
||||||
pub socket: TcpStream,
|
pub socket: TcpStream,
|
||||||
|
@ -119,6 +128,8 @@ pub struct ZoneConnection {
|
||||||
|
|
||||||
// TODO: really needs to be moved somewhere else
|
// TODO: really needs to be moved somewhere else
|
||||||
pub weather_id: u16,
|
pub weather_id: u16,
|
||||||
|
|
||||||
|
pub obsfucation_data: ObsfucationData,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ZoneConnection {
|
impl ZoneConnection {
|
||||||
|
@ -140,6 +151,7 @@ impl ZoneConnection {
|
||||||
CompressionType::Uncompressed
|
CompressionType::Uncompressed
|
||||||
},
|
},
|
||||||
&[segment],
|
&[segment],
|
||||||
|
self.obsfucation_data.keys.as_ref(),
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
|
@ -155,6 +167,7 @@ impl ZoneConnection {
|
||||||
CompressionType::Uncompressed
|
CompressionType::Uncompressed
|
||||||
},
|
},
|
||||||
&[segment],
|
&[segment],
|
||||||
|
self.obsfucation_data.keys.as_ref(),
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
|
@ -350,6 +363,27 @@ impl ZoneConnection {
|
||||||
// Player Class Info
|
// Player Class Info
|
||||||
self.update_class_info().await;
|
self.update_class_info().await;
|
||||||
|
|
||||||
|
// Generate obsfucation-related keys if needed.
|
||||||
|
if self.config.enable_packet_obsfucation {
|
||||||
|
let seed1 = fastrand::u8(..);
|
||||||
|
let seed2 = fastrand::u8(..);
|
||||||
|
let seed3 = fastrand::u32(..);
|
||||||
|
|
||||||
|
let generator = ScramblerKeyGenerator::new();
|
||||||
|
|
||||||
|
self.obsfucation_data = ObsfucationData {
|
||||||
|
keys: Some(generator.generate(seed1, seed2, seed3)),
|
||||||
|
seed1,
|
||||||
|
seed2,
|
||||||
|
seed3,
|
||||||
|
};
|
||||||
|
|
||||||
|
tracing::info!(
|
||||||
|
"You enabled packet obsfucation in your World config, things will break! {:?}",
|
||||||
|
self.obsfucation_data
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// Init Zone
|
// Init Zone
|
||||||
{
|
{
|
||||||
let config = get_config();
|
let config = get_config();
|
||||||
|
@ -372,6 +406,9 @@ impl ZoneConnection {
|
||||||
} else {
|
} else {
|
||||||
0
|
0
|
||||||
},
|
},
|
||||||
|
seed1: !self.obsfucation_data.seed1,
|
||||||
|
seed2: !self.obsfucation_data.seed2,
|
||||||
|
seed3: !self.obsfucation_data.seed3,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}),
|
}),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
|
|
@ -161,6 +161,7 @@ pub async fn handle_custom_ipc(connection: &mut ZoneConnection, data: &CustomIpc
|
||||||
},
|
},
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}],
|
}],
|
||||||
|
None,
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
|
@ -186,6 +187,7 @@ pub async fn handle_custom_ipc(connection: &mut ZoneConnection, data: &CustomIpc
|
||||||
},
|
},
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}],
|
}],
|
||||||
|
None,
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
|
@ -234,6 +236,7 @@ pub async fn handle_custom_ipc(connection: &mut ZoneConnection, data: &CustomIpc
|
||||||
},
|
},
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}],
|
}],
|
||||||
|
None,
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@ mod chat_handler;
|
||||||
pub use chat_handler::ChatHandler;
|
pub use chat_handler::ChatHandler;
|
||||||
|
|
||||||
mod connection;
|
mod connection;
|
||||||
pub use connection::{ExtraLuaState, PlayerData, ZoneConnection};
|
pub use connection::{ExtraLuaState, ObsfucationData, PlayerData, ZoneConnection};
|
||||||
|
|
||||||
mod database;
|
mod database;
|
||||||
pub use database::{CharacterData, WorldDatabase};
|
pub use database::{CharacterData, WorldDatabase};
|
||||||
|
@ -30,3 +30,6 @@ pub use custom_ipc_handler::handle_custom_ipc;
|
||||||
|
|
||||||
mod common;
|
mod common;
|
||||||
pub use common::{ClientHandle, ClientId, FromServer, ServerHandle, ToServer};
|
pub use common::{ClientHandle, ClientId, FromServer, ServerHandle, ToServer};
|
||||||
|
|
||||||
|
mod scrambler;
|
||||||
|
pub use scrambler::{OBFUSCATION_ENABLED_MODE, ScramblerKeyGenerator, ScramblerKeys};
|
||||||
|
|
96
src/world/scrambler.rs
Normal file
96
src/world/scrambler.rs
Normal file
|
@ -0,0 +1,96 @@
|
||||||
|
//! Obfuscation-related structures and procedures. This is based on the ever fantastic work of Perchbird and his Unscrambler: https://github.com/perchbirdd/Unscrambler
|
||||||
|
//! This is simply a Rust-reimplementation of Unscrambler.
|
||||||
|
|
||||||
|
/// Constant to enable packet obfuscation. Changes every patch.
|
||||||
|
pub const OBFUSCATION_ENABLED_MODE: u8 = 118;
|
||||||
|
|
||||||
|
/// Generates the necessary keys from three seeds.
|
||||||
|
pub struct ScramblerKeyGenerator {
|
||||||
|
table0: &'static [i32],
|
||||||
|
table1: &'static [i32],
|
||||||
|
table2: &'static [i32],
|
||||||
|
mid_table: &'static [u8],
|
||||||
|
day_table: &'static [u8],
|
||||||
|
table_radixes: &'static [i32],
|
||||||
|
table_max: &'static [i32],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ScramblerKeyGenerator {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
// Technically unsafe, but Unscrambler's tables should be correct anyway
|
||||||
|
unsafe {
|
||||||
|
Self {
|
||||||
|
table0: std::mem::transmute::<&[u8], &[i32]>(include_bytes!(
|
||||||
|
"../../resources/table0.bin"
|
||||||
|
)),
|
||||||
|
table1: std::mem::transmute::<&[u8], &[i32]>(include_bytes!(
|
||||||
|
"../../resources/table1.bin"
|
||||||
|
)),
|
||||||
|
table2: std::mem::transmute::<&[u8], &[i32]>(include_bytes!(
|
||||||
|
"../../resources/table2.bin"
|
||||||
|
)),
|
||||||
|
mid_table: include_bytes!("../../resources/midtable.bin"),
|
||||||
|
day_table: include_bytes!("../../resources/daytable.bin"),
|
||||||
|
|
||||||
|
// TODO: is it possible to calculate these automatically?
|
||||||
|
table_radixes: &[93, 94, 113],
|
||||||
|
table_max: &[219, 187, 113],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn derive(&self, set: u8, n_seed_1: u8, n_seed_2: u8, epoch: u32) -> u8 {
|
||||||
|
// FIXME: so many probably unnecessary casts here
|
||||||
|
|
||||||
|
let mid_index = 8 * (n_seed_1 as usize % (self.mid_table.len() / 8));
|
||||||
|
let mid_table_value = self.mid_table[4 + mid_index];
|
||||||
|
let mut mid_bytes = [0u8; 4];
|
||||||
|
mid_bytes.copy_from_slice(&self.mid_table[mid_index..mid_index + 4]);
|
||||||
|
let mid_value = u32::from_le_bytes(mid_bytes);
|
||||||
|
|
||||||
|
let epoch_days = 3 * (epoch as usize / 60 / 60 / 24);
|
||||||
|
let day_table_index = 4 * (epoch_days % (self.day_table.len() / 4));
|
||||||
|
let day_table_value = self.day_table[day_table_index];
|
||||||
|
|
||||||
|
let set_radix = self.table_radixes[set as usize];
|
||||||
|
let set_max = self.table_max[set as usize];
|
||||||
|
let table_index = (set_radix as i32 * (n_seed_2 as i32 % set_max as i32)) as usize
|
||||||
|
+ mid_value as usize * n_seed_1 as usize % set_radix as usize;
|
||||||
|
let set_result = match set {
|
||||||
|
0 => self.table0[table_index],
|
||||||
|
1 => self.table1[table_index],
|
||||||
|
2 => self.table2[table_index],
|
||||||
|
_ => 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
(n_seed_1 as i32 + mid_table_value as i32 + day_table_value as i32 + set_result) as u8
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generates keys for scrambling or unscrambling packets. The callee must keep track of their seeds, we only generate the keys.
|
||||||
|
pub fn generate(&self, seed1: u8, seed2: u8, seed3: u32) -> ScramblerKeys {
|
||||||
|
let neg_seed_1 = seed1;
|
||||||
|
let neg_seed_2 = seed2;
|
||||||
|
let neg_seed_3 = seed3;
|
||||||
|
|
||||||
|
ScramblerKeys {
|
||||||
|
keys: [
|
||||||
|
self.derive(0, neg_seed_1, neg_seed_2, neg_seed_3),
|
||||||
|
self.derive(1, neg_seed_1, neg_seed_2, neg_seed_3),
|
||||||
|
self.derive(2, neg_seed_1, neg_seed_2, neg_seed_3),
|
||||||
|
],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Holds the keys generated by `ScramblerKeyGenerator`.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct ScramblerKeys {
|
||||||
|
keys: [u8; 3],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ScramblerKeys {
|
||||||
|
/// Fetches the required base key for the given opcode.
|
||||||
|
pub fn get_base_key(&self, opcode: u16) -> u8 {
|
||||||
|
self.keys[(opcode % 3) as usize]
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue