mirror of
https://github.com/redstrate/Kawari.git
synced 2025-07-09 15:37:45 +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");
|
||||
|
||||
// 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
|
||||
output_str.push_str("}\n\n");
|
||||
}
|
||||
|
|
|
@ -20,7 +20,9 @@ use kawari::packet::oodle::OodleNetwork;
|
|||
use kawari::packet::{
|
||||
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::{
|
||||
ClientHandle, Event, FromServer, LuaPlayer, PlayerData, ServerHandle, StatusEffects, ToServer,
|
||||
WorldDatabase, handle_custom_ipc, server_main_loop,
|
||||
|
@ -1099,6 +1101,7 @@ async fn main() {
|
|||
last_keep_alive: Instant::now(),
|
||||
gracefully_logged_out: false,
|
||||
weather_id: 0,
|
||||
obsfucation_data: ObsfucationData::default(),
|
||||
});
|
||||
}
|
||||
Some((mut socket, _)) = handle_rcon(&rcon_listener) => {
|
||||
|
|
|
@ -2,20 +2,23 @@ use binrw::binrw;
|
|||
|
||||
use crate::{
|
||||
opcodes::ServerChatIpcType,
|
||||
packet::{IpcSegment, ReadWriteIpcSegment},
|
||||
packet::{IPC_HEADER_SIZE, IpcSegment, ReadWriteIpcSegment},
|
||||
};
|
||||
|
||||
pub type ServerChatIpcSegment = IpcSegment<ServerChatIpcType, ServerChatIpcData>;
|
||||
|
||||
impl ReadWriteIpcSegment for ServerChatIpcSegment {
|
||||
fn calc_size(&self) -> u32 {
|
||||
// 16 is the size of the IPC header
|
||||
16 + self.op_code.calc_size()
|
||||
IPC_HEADER_SIZE + self.op_code.calc_size()
|
||||
}
|
||||
|
||||
fn get_name(&self) -> &'static str {
|
||||
self.op_code.get_name()
|
||||
}
|
||||
|
||||
fn get_opcode(&self) -> u16 {
|
||||
self.op_code.get_opcode()
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: make generic
|
||||
|
|
|
@ -3,34 +3,38 @@ use binrw::binrw;
|
|||
use crate::{
|
||||
common::{CHAR_NAME_MAX_LENGTH, read_bool_from, read_string, write_bool_as, write_string},
|
||||
ipc::lobby::CharacterDetails,
|
||||
packet::{IpcSegment, ReadWriteIpcSegment},
|
||||
packet::{IPC_HEADER_SIZE, IpcSegment, ReadWriteIpcSegment},
|
||||
};
|
||||
|
||||
pub type CustomIpcSegment = IpcSegment<CustomIpcType, CustomIpcData>;
|
||||
|
||||
impl ReadWriteIpcSegment for CustomIpcSegment {
|
||||
fn calc_size(&self) -> u32 {
|
||||
// 16 is the size of the IPC header
|
||||
16 + match self.op_code {
|
||||
CustomIpcType::RequestCreateCharacter => 1024 + CHAR_NAME_MAX_LENGTH as u32,
|
||||
CustomIpcType::CharacterCreated => 12,
|
||||
CustomIpcType::GetActorId => 8,
|
||||
CustomIpcType::ActorIdFound => 4,
|
||||
CustomIpcType::CheckNameIsAvailable => CHAR_NAME_MAX_LENGTH as u32,
|
||||
CustomIpcType::NameIsAvailableResponse => 1,
|
||||
CustomIpcType::RequestCharacterList => 4,
|
||||
CustomIpcType::RequestCharacterListRepsonse => 1 + (1184 * 8),
|
||||
CustomIpcType::DeleteCharacter => 8,
|
||||
CustomIpcType::CharacterDeleted => 1,
|
||||
CustomIpcType::ImportCharacter => 132,
|
||||
CustomIpcType::RemakeCharacter => 1024 + 8,
|
||||
CustomIpcType::CharacterRemade => 8,
|
||||
}
|
||||
IPC_HEADER_SIZE
|
||||
+ match self.op_code {
|
||||
CustomIpcType::RequestCreateCharacter => 1024 + CHAR_NAME_MAX_LENGTH as u32,
|
||||
CustomIpcType::CharacterCreated => 12,
|
||||
CustomIpcType::GetActorId => 8,
|
||||
CustomIpcType::ActorIdFound => 4,
|
||||
CustomIpcType::CheckNameIsAvailable => CHAR_NAME_MAX_LENGTH as u32,
|
||||
CustomIpcType::NameIsAvailableResponse => 1,
|
||||
CustomIpcType::RequestCharacterList => 4,
|
||||
CustomIpcType::RequestCharacterListRepsonse => 1 + (1184 * 8),
|
||||
CustomIpcType::DeleteCharacter => 8,
|
||||
CustomIpcType::CharacterDeleted => 1,
|
||||
CustomIpcType::ImportCharacter => 132,
|
||||
CustomIpcType::RemakeCharacter => 1024 + 8,
|
||||
CustomIpcType::CharacterRemade => 8,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_name(&self) -> &'static str {
|
||||
""
|
||||
}
|
||||
|
||||
fn get_opcode(&self) -> u16 {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for CustomIpcSegment {
|
||||
|
|
|
@ -15,20 +15,23 @@ pub use login_reply::{LoginReply, ServiceAccount};
|
|||
use crate::{
|
||||
common::{read_string, write_string},
|
||||
opcodes::{ClientLobbyIpcType, ServerLobbyIpcType},
|
||||
packet::{IpcSegment, ReadWriteIpcSegment},
|
||||
packet::{IPC_HEADER_SIZE, IpcSegment, ReadWriteIpcSegment},
|
||||
};
|
||||
|
||||
pub type ClientLobbyIpcSegment = IpcSegment<ClientLobbyIpcType, ClientLobbyIpcData>;
|
||||
|
||||
impl ReadWriteIpcSegment for ClientLobbyIpcSegment {
|
||||
fn calc_size(&self) -> u32 {
|
||||
// 16 is the size of the IPC header
|
||||
16 + self.op_code.calc_size()
|
||||
IPC_HEADER_SIZE + self.op_code.calc_size()
|
||||
}
|
||||
|
||||
fn get_name(&self) -> &'static str {
|
||||
self.op_code.get_name()
|
||||
}
|
||||
|
||||
fn get_opcode(&self) -> u16 {
|
||||
self.op_code.get_opcode()
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: make generic
|
||||
|
@ -53,13 +56,16 @@ pub type ServerLobbyIpcSegment = IpcSegment<ServerLobbyIpcType, ServerLobbyIpcDa
|
|||
|
||||
impl ReadWriteIpcSegment for ServerLobbyIpcSegment {
|
||||
fn calc_size(&self) -> u32 {
|
||||
// 16 is the size of the IPC header
|
||||
16 + self.op_code.calc_size()
|
||||
IPC_HEADER_SIZE + self.op_code.calc_size()
|
||||
}
|
||||
|
||||
fn get_name(&self) -> &'static str {
|
||||
self.op_code.get_name()
|
||||
}
|
||||
|
||||
fn get_opcode(&self) -> u16 {
|
||||
self.op_code.get_opcode()
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: make generic
|
||||
|
|
|
@ -17,8 +17,12 @@ pub struct InitZone {
|
|||
/// 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.
|
||||
pub obsfucation_mode: u8,
|
||||
pub unk1: u8,
|
||||
pub unk2: u32,
|
||||
/// First seed used in deobsfucation on the client side.
|
||||
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 additional_festival_id: u16,
|
||||
pub unk3: u32,
|
||||
|
@ -26,7 +30,7 @@ pub struct InitZone {
|
|||
pub unk5: u32,
|
||||
pub unk6: [u32; 4],
|
||||
pub unk7: [u32; 3],
|
||||
pub unk8_9: [u8; 9],
|
||||
pub unk8_9: [u8; 8],
|
||||
pub position: Position,
|
||||
pub unk8: [u32; 4],
|
||||
pub unk9: u32,
|
||||
|
|
|
@ -94,6 +94,7 @@ use crate::common::read_string;
|
|||
use crate::common::write_string;
|
||||
use crate::opcodes::ClientZoneIpcType;
|
||||
use crate::opcodes::ServerZoneIpcType;
|
||||
use crate::packet::IPC_HEADER_SIZE;
|
||||
use crate::packet::IpcSegment;
|
||||
use crate::packet::ReadWriteIpcSegment;
|
||||
|
||||
|
@ -101,13 +102,16 @@ pub type ClientZoneIpcSegment = IpcSegment<ClientZoneIpcType, ClientZoneIpcData>
|
|||
|
||||
impl ReadWriteIpcSegment for ClientZoneIpcSegment {
|
||||
fn calc_size(&self) -> u32 {
|
||||
// 16 is the size of the IPC header
|
||||
16 + self.op_code.calc_size()
|
||||
IPC_HEADER_SIZE + self.op_code.calc_size()
|
||||
}
|
||||
|
||||
fn get_name(&self) -> &'static str {
|
||||
self.op_code.get_name()
|
||||
}
|
||||
|
||||
fn get_opcode(&self) -> u16 {
|
||||
self.op_code.get_opcode()
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: make generic
|
||||
|
@ -128,13 +132,16 @@ pub type ServerZoneIpcSegment = IpcSegment<ServerZoneIpcType, ServerZoneIpcData>
|
|||
|
||||
impl ReadWriteIpcSegment for ServerZoneIpcSegment {
|
||||
fn calc_size(&self) -> u32 {
|
||||
// 16 is the size of the IPC header
|
||||
16 + self.op_code.calc_size()
|
||||
IPC_HEADER_SIZE + self.op_code.calc_size()
|
||||
}
|
||||
|
||||
fn get_name(&self) -> &'static str {
|
||||
self.op_code.get_name()
|
||||
}
|
||||
|
||||
fn get_opcode(&self) -> u16 {
|
||||
self.op_code.get_opcode()
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: make generic
|
||||
|
|
|
@ -66,9 +66,6 @@ pub fn get_supported_expac_versions() -> HashMap<&'static str, Version<'static>>
|
|||
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.
|
||||
pub const UNLOCK_BITMASK_SIZE: usize = 92;
|
||||
|
||||
|
|
|
@ -54,6 +54,7 @@ impl LobbyConnection {
|
|||
ConnectionType::Lobby,
|
||||
CompressionType::Uncompressed,
|
||||
&[segment],
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
@ -166,6 +167,7 @@ impl LobbyConnection {
|
|||
ConnectionType::Lobby,
|
||||
CompressionType::Uncompressed,
|
||||
&packets,
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
|
||||
|
@ -560,6 +562,7 @@ pub async fn send_custom_world_packet(segment: CustomIpcSegment) -> Option<Custo
|
|||
ConnectionType::None,
|
||||
CompressionType::Uncompressed,
|
||||
&[segment],
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
|
||||
|
|
|
@ -6,9 +6,10 @@ use binrw::{BinRead, BinResult};
|
|||
use crate::{
|
||||
config::get_config,
|
||||
packet::{PacketHeader, PacketSegment},
|
||||
world::ScramblerKeys,
|
||||
};
|
||||
|
||||
use super::{PacketState, ReadWriteIpcSegment, oodle::OodleNetwork};
|
||||
use super::{IPC_HEADER_SIZE, PacketState, ReadWriteIpcSegment, SegmentData, oodle::OodleNetwork};
|
||||
|
||||
#[binrw]
|
||||
#[brw(repr = u8)]
|
||||
|
@ -77,18 +78,44 @@ pub(crate) fn compress<T: ReadWriteIpcSegment>(
|
|||
state: &mut PacketState,
|
||||
compression_type: &CompressionType,
|
||||
segments: &[PacketSegment<T>],
|
||||
keys: Option<&ScramblerKeys>,
|
||||
) -> (Vec<u8>, usize) {
|
||||
let mut segments_buffer = Cursor::new(Vec::new());
|
||||
let mut segments_buffer = Vec::new();
|
||||
for segment in segments {
|
||||
segment
|
||||
.write_le_args(
|
||||
&mut segments_buffer,
|
||||
(state.client_key.as_ref().map(|s: &[u8; 16]| s.as_slice()),),
|
||||
)
|
||||
.unwrap();
|
||||
let mut buffer = Vec::new();
|
||||
|
||||
// write to buffer
|
||||
{
|
||||
let mut cursor = Cursor::new(&mut buffer);
|
||||
|
||||
segment
|
||||
.write_le_args(
|
||||
&mut cursor,
|
||||
(state.client_key.as_ref().map(|s: &[u8; 16]| s.as_slice()),),
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
// 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 = segments_buffer.into_inner();
|
||||
let segments_buffer_len = segments_buffer.len();
|
||||
|
||||
match compression_type {
|
||||
|
|
|
@ -10,6 +10,9 @@ pub trait ReadWriteIpcSegment:
|
|||
|
||||
/// Returns a human-readable name of the opcode.
|
||||
fn get_name(&self) -> &'static str;
|
||||
|
||||
/// Returns the integer opcode.
|
||||
fn get_opcode(&self) -> u16;
|
||||
}
|
||||
|
||||
/// An IPC packet segment.
|
||||
|
@ -60,3 +63,5 @@ where
|
|||
#[br(args(&op_code, size))]
|
||||
pub data: Data,
|
||||
}
|
||||
|
||||
pub const IPC_HEADER_SIZE: u32 = 16;
|
||||
|
|
|
@ -11,7 +11,7 @@ mod encryption;
|
|||
pub use encryption::generate_encryption_key;
|
||||
|
||||
mod ipc;
|
||||
pub use ipc::{IpcSegment, ReadWriteIpcSegment};
|
||||
pub use ipc::{IPC_HEADER_SIZE, IpcSegment, ReadWriteIpcSegment};
|
||||
|
||||
/// Bindings for Oodle network compression.
|
||||
pub mod oodle;
|
||||
|
|
|
@ -3,7 +3,7 @@ use std::io::Cursor;
|
|||
use binrw::BinWrite;
|
||||
use tokio::{io::AsyncWriteExt, net::TcpStream};
|
||||
|
||||
use crate::common::timestamp_msecs;
|
||||
use crate::{common::timestamp_msecs, world::ScramblerKeys};
|
||||
|
||||
use super::{
|
||||
CompressionType, ConnectionType, PacketHeader, PacketSegment, PacketState, ReadWriteIpcSegment,
|
||||
|
@ -16,8 +16,9 @@ pub async fn send_packet<T: ReadWriteIpcSegment>(
|
|||
connection_type: ConnectionType,
|
||||
compression_type: CompressionType,
|
||||
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 header = PacketHeader {
|
||||
|
@ -61,6 +62,7 @@ pub async fn send_keep_alive<T: ReadWriteIpcSegment>(
|
|||
connection_type,
|
||||
CompressionType::Uncompressed,
|
||||
&[response_packet],
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ use mlua::Function;
|
|||
use tokio::net::TcpStream;
|
||||
|
||||
use crate::{
|
||||
COMPLETED_QUEST_BITMASK_SIZE, OBFUSCATION_ENABLED_MODE,
|
||||
COMPLETED_QUEST_BITMASK_SIZE,
|
||||
common::{
|
||||
GameData, ObjectId, ObjectTypeId, Position, timestamp_secs, value_to_flag_byte_index_value,
|
||||
},
|
||||
|
@ -34,8 +34,8 @@ use crate::{
|
|||
};
|
||||
|
||||
use super::{
|
||||
Actor, CharacterData, EffectsBuilder, Event, LuaPlayer, StatusEffects, ToServer, WorldDatabase,
|
||||
Zone,
|
||||
Actor, CharacterData, EffectsBuilder, Event, LuaPlayer, OBFUSCATION_ENABLED_MODE,
|
||||
ScramblerKeyGenerator, ScramblerKeys, StatusEffects, ToServer, WorldDatabase, Zone,
|
||||
common::{ClientId, ServerHandle},
|
||||
load_init_script,
|
||||
lua::Task,
|
||||
|
@ -85,7 +85,16 @@ pub struct PlayerData {
|
|||
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 config: WorldConfig,
|
||||
pub socket: TcpStream,
|
||||
|
@ -119,6 +128,8 @@ pub struct ZoneConnection {
|
|||
|
||||
// TODO: really needs to be moved somewhere else
|
||||
pub weather_id: u16,
|
||||
|
||||
pub obsfucation_data: ObsfucationData,
|
||||
}
|
||||
|
||||
impl ZoneConnection {
|
||||
|
@ -140,6 +151,7 @@ impl ZoneConnection {
|
|||
CompressionType::Uncompressed
|
||||
},
|
||||
&[segment],
|
||||
self.obsfucation_data.keys.as_ref(),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
@ -155,6 +167,7 @@ impl ZoneConnection {
|
|||
CompressionType::Uncompressed
|
||||
},
|
||||
&[segment],
|
||||
self.obsfucation_data.keys.as_ref(),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
@ -350,6 +363,27 @@ impl ZoneConnection {
|
|||
// Player Class Info
|
||||
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
|
||||
{
|
||||
let config = get_config();
|
||||
|
@ -372,6 +406,9 @@ impl ZoneConnection {
|
|||
} else {
|
||||
0
|
||||
},
|
||||
seed1: !self.obsfucation_data.seed1,
|
||||
seed2: !self.obsfucation_data.seed2,
|
||||
seed3: !self.obsfucation_data.seed3,
|
||||
..Default::default()
|
||||
}),
|
||||
..Default::default()
|
||||
|
|
|
@ -161,6 +161,7 @@ pub async fn handle_custom_ipc(connection: &mut ZoneConnection, data: &CustomIpc
|
|||
},
|
||||
..Default::default()
|
||||
}],
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
@ -186,6 +187,7 @@ pub async fn handle_custom_ipc(connection: &mut ZoneConnection, data: &CustomIpc
|
|||
},
|
||||
..Default::default()
|
||||
}],
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
@ -234,6 +236,7 @@ pub async fn handle_custom_ipc(connection: &mut ZoneConnection, data: &CustomIpc
|
|||
},
|
||||
..Default::default()
|
||||
}],
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@ mod chat_handler;
|
|||
pub use chat_handler::ChatHandler;
|
||||
|
||||
mod connection;
|
||||
pub use connection::{ExtraLuaState, PlayerData, ZoneConnection};
|
||||
pub use connection::{ExtraLuaState, ObsfucationData, PlayerData, ZoneConnection};
|
||||
|
||||
mod database;
|
||||
pub use database::{CharacterData, WorldDatabase};
|
||||
|
@ -30,3 +30,6 @@ pub use custom_ipc_handler::handle_custom_ipc;
|
|||
|
||||
mod common;
|
||||
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