diff --git a/examples/unreal2server.rs b/examples/unreal2server.rs new file mode 100644 index 00000000..06a28ac6 --- /dev/null +++ b/examples/unreal2server.rs @@ -0,0 +1,79 @@ +/// ! Pretend to be an unreal 2 server by using the ToPacket/FromPacket trait. +use std::fmt::Debug; +use std::net::{SocketAddr, UdpSocket}; + +use gamedig::protocols::types::{FromPacket, ToPacket}; +use gamedig::protocols::unreal2::{ + MutatorsAndRules, + PacketKind, + PacketRequest, + PacketResponse, + Player, + Players, + ServerInfo, +}; + +type GResult = Result>; + +fn send_response>, I: ToPacket + Debug>( + socket: &UdpSocket, + client: SocketAddr, + response: T, +) -> GResult<()> { + let response = response.into(); + + println!("Sending: {:?}", response); + + socket.send_to(&response.as_packet()?, client)?; + + Ok(()) +} + +fn main() -> GResult<()> { + let socket = UdpSocket::bind("0.0.0.0:7777")?; + + let mut buf = [0; 100]; + + loop { + let (size, client) = socket.recv_from(&mut buf)?; + let request = PacketRequest::from_packet(&buf[.. size])?; + println!("Recieved {:?}", request); + + match request.packet_type { + PacketKind::ServerInfo => { + send_response( + &socket, + client, + ServerInfo { + server_id: 69, + ip: String::from("test.server"), + game_port: 7776, + query_port: 7777, + name: String::from("Test server"), + map: String::from("No map"), + game_type: String::from("None"), + num_players: 1, + max_players: 5, + }, + ) + } + PacketKind::MutatorsAndRules => send_response(&socket, client, MutatorsAndRules::default()), + PacketKind::Players => { + send_response( + &socket, + client, + Players { + players: vec![Player { + id: 6, + name: String::from("Tom"), + ping: u32::MAX, + score: -1, + stats_id: 1, + }], + bots: vec![], + }, + ) + } + }?; + } +} diff --git a/src/errors/kind.rs b/src/errors/kind.rs index 65c0d1bc..d1eaa7af 100644 --- a/src/errors/kind.rs +++ b/src/errors/kind.rs @@ -34,6 +34,8 @@ pub enum GDErrorKind { JsonParse, /// Couldn't parse a value. TypeParse, + /// Out of memory. + OutOfMemory, } impl GDErrorKind { diff --git a/src/lib.rs b/src/lib.rs index 7cafba7c..102d6bb5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -44,6 +44,7 @@ pub mod services; mod buffer; mod socket; mod utils; +mod wbuffer; pub use errors::*; #[cfg(feature = "games")] diff --git a/src/protocols/types.rs b/src/protocols/types.rs index 67e7d13c..fff0aa6d 100644 --- a/src/protocols/types.rs +++ b/src/protocols/types.rs @@ -366,3 +366,29 @@ mod tests { let _: valve::GatheringSettings = settings.into(); } } + +/// Allows a type to be represented as a u8 slice, and sent over the network as +/// a packet. +pub trait ToPacket: Sized { + /// Encode self as raw bytes to be sent as a packet. + fn as_packet(&self) -> GDResult>; +} + +/// Allow creating a type from raw u8 bytes (a packet). +pub trait FromPacket: Sized { + /// Parse the packet bytes to create a new value. + fn from_packet(packet: &[u8]) -> GDResult; +} + +/// Allow extending a pre-existing value using raw u8 bytes (a packet). +pub trait ExtendFromPacket<'a> { + /// The type of the value to be extended (without &mut). + type Input; + /// The type the extend function will return, mainly for use with types + /// where an inner value is being extended, otherwise unit type can be + /// returned. + type Output: Sized; + /// Extend the input value using the packet's bytes and return the an Output + /// value. + fn extend_from_packet(packet: &[u8], input: &'a mut Self::Input) -> GDResult; +} diff --git a/src/protocols/unreal2/mod.rs b/src/protocols/unreal2/mod.rs index 7cf5e5c5..184d55ae 100644 --- a/src/protocols/unreal2/mod.rs +++ b/src/protocols/unreal2/mod.rs @@ -1,8 +1,11 @@ +/// Raw packet types used by the implementation. +pub mod packets; /// The implementation. pub mod protocol; /// All types used by the implementation. pub mod types; +pub use packets::*; pub use protocol::*; pub use types::*; diff --git a/src/protocols/unreal2/packets.rs b/src/protocols/unreal2/packets.rs new file mode 100644 index 00000000..d971ff16 --- /dev/null +++ b/src/protocols/unreal2/packets.rs @@ -0,0 +1,305 @@ +use crate::errors::GDErrorKind::PacketBad; +use crate::protocols::types::{ExtendFromPacket, FromPacket, ToPacket}; +use crate::GDResult; + +use super::{MutatorsAndRules, PacketKind, Player, Players, ServerInfo, Unreal2StringDecoder}; + +use byteorder::LittleEndian; + +type Buffer<'a> = crate::buffer::Buffer<'a, LittleEndian>; +type WriteBuffer = crate::wbuffer::WriteBuffer; +type StringEncoder = crate::wbuffer::UTF8LengthPrefixedEncoder; + +/// The first byte of unreal2 requests. +const REQUEST_FLAG: u8 = 0x79; + +/// Unreal2 client request. +#[derive(Debug, Clone, PartialEq)] +pub struct PacketRequest { + /// Should always be 0x79. + pub request_flag: u8, + pub padding: [u8; 3], + pub packet_type: PacketKind, +} + +impl From for PacketRequest { + fn from(packet_type: PacketKind) -> Self { + Self { + request_flag: REQUEST_FLAG, + padding: [0, 0, 0], + packet_type, + } + } +} + +impl ToPacket for PacketRequest { + fn as_packet(&self) -> GDResult> { + let mut buffer = Vec::with_capacity(std::mem::size_of::()); + + if self.request_flag != REQUEST_FLAG { + return Err(PacketBad.context("Unreal2 request should start with 0x79")); + } + + buffer.push(self.request_flag); + buffer.extend_from_slice(&self.padding); + buffer.push(self.packet_type as u8); + Ok(buffer) + } +} + +impl FromPacket for PacketRequest { + fn from_packet(packet: &[u8]) -> GDResult { + let mut buffer = Buffer::new(packet); + let request_flag = buffer.read()?; + + if request_flag != REQUEST_FLAG { + return Err(PacketBad.context("Unreal2 request should start with 0x79")); + } + + Ok(Self { + request_flag, + padding: [buffer.read()?, buffer.read()?, buffer.read()?], + packet_type: PacketKind::try_from(buffer.read::()?)?, + }) + } +} + +/// Unreal 2 server response. +#[derive(Debug, Clone, PartialEq)] +pub struct PacketResponse { + pub padding: [u8; 4], + pub packet_type: PacketKind, + pub body: T, +} + +impl FromPacket for PacketResponse { + fn from_packet(packet: &[u8]) -> GDResult { + let mut buffer = Buffer::new(packet); + + let padding = [ + buffer.read()?, + buffer.read()?, + buffer.read()?, + buffer.read()?, + ]; + + let packet_type = PacketKind::try_from(buffer.read::()?)?; + + Ok(Self { + padding, + packet_type, + body: T::from_packet(buffer.remaining_bytes())?, + }) + } +} + +impl<'a, T> ExtendFromPacket<'a> for PacketResponse<&'a mut T> +where for<'b> T: ExtendFromPacket<'b, Input = T> +{ + type Input = T; + type Output = PacketResponse<&'a mut T>; + fn extend_from_packet(packet: &[u8], input: &'a mut Self::Input) -> GDResult { + let mut buffer = Buffer::new(packet); + + let padding = [ + buffer.read()?, + buffer.read()?, + buffer.read()?, + buffer.read()?, + ]; + + let packet_type = PacketKind::try_from(buffer.read::()?)?; + + T::extend_from_packet(buffer.remaining_bytes(), input)?; + + Ok(Self { + padding, + packet_type, + body: input, + }) + } +} + +impl ToPacket for PacketResponse { + fn as_packet(&self) -> GDResult> { + let mut buffer = Vec::with_capacity(std::mem::size_of::() + std::mem::size_of::()); + buffer.extend_from_slice(&self.padding); + buffer.push(self.packet_type as u8); + buffer.extend(self.body.as_packet()?); + Ok(buffer) + } +} + +pub type PacketServerInfo = PacketResponse; + +impl From for PacketServerInfo { + fn from(value: ServerInfo) -> Self { + Self { + padding: [0, 0, 0, 0], + packet_type: PacketKind::ServerInfo, + body: value, + } + } +} + +impl FromPacket for ServerInfo { + fn from_packet(packet: &[u8]) -> GDResult { + let mut buffer = Buffer::new(packet); + Ok(ServerInfo { + server_id: buffer.read()?, + ip: buffer.read_string::(None)?, + game_port: buffer.read()?, + query_port: buffer.read()?, + name: buffer.read_string::(None)?, + map: buffer.read_string::(None)?, + game_type: buffer.read_string::(None)?, + num_players: buffer.read()?, + max_players: buffer.read()?, + }) + } +} + +impl ToPacket for ServerInfo { + fn as_packet(&self) -> GDResult> { + let mut buffer = WriteBuffer::with_capacity( + std::mem::size_of::(), // TODO: Add string lengths + ); + + buffer.write(self.server_id)?; + buffer.write_string::(&self.ip)?; + buffer.write(self.game_port)?; + buffer.write(self.query_port)?; + buffer.write_string::(&self.name)?; + buffer.write_string::(&self.map)?; + buffer.write_string::(&self.game_type)?; + buffer.write(self.num_players)?; + buffer.write(self.max_players)?; + + // Write string + + Ok(buffer.into_data()) + } +} + +impl ExtendFromPacket<'_> for MutatorsAndRules { + type Input = MutatorsAndRules; + type Output = (); + fn extend_from_packet(packet: &[u8], input: &mut Self::Input) -> GDResult { + let mut buffer = Buffer::new(packet); + while buffer.remaining_length() > 0 { + let key = buffer.read_string::(None)?; + let value = buffer.read_string::(None).ok(); + + if key.eq_ignore_ascii_case("mutator") { + if let Some(value) = value { + input.mutators.insert(value); + } + } else { + let rule_vec = input.rules.get_mut(&key); + + let rule_vec = if let Some(rule_vec) = rule_vec { + rule_vec + } else { + input.rules.insert(key.clone(), Vec::default()); + input + .rules + .get_mut(&key) + .expect("Value should be in HashMap after we inserted") + }; + + if let Some(value) = value { + rule_vec.push(value); + } + } + } + Ok(()) + } +} + +impl ToPacket for MutatorsAndRules { + fn as_packet(&self) -> GDResult> { + let mut buffer = WriteBuffer::default(); + + for mutator in self.mutators.iter() { + buffer.write_string::("mutator")?; + buffer.write_string::(mutator)?; + } + + for (key, values) in self.rules.iter() { + for value in values { + buffer.write_string::(key)?; + buffer.write_string::(value)?; + } + } + + Ok(buffer.into_data()) + } +} + +pub type PacketMutatorsAndRules<'a> = PacketResponse<&'a mut MutatorsAndRules>; + +impl From for PacketResponse { + fn from(body: MutatorsAndRules) -> Self { + Self { + padding: [0, 0, 0, 0], + packet_type: PacketKind::MutatorsAndRules, + body, + } + } +} + +impl ExtendFromPacket<'_> for Players { + type Input = Players; + type Output = (); + + /// Parse a raw buffer of players into the current struct. + fn extend_from_packet(packet: &[u8], input: &mut Self::Input) -> GDResult<()> { + let mut buffer = Buffer::new(packet); + while buffer.remaining_length() > 0 { + let player = Player { + id: buffer.read()?, + name: buffer.read_string::(None)?, + ping: buffer.read()?, + score: buffer.read()?, + stats_id: buffer.read()?, + }; + + // If ping is 0 the player is a bot + if player.ping == 0 { + input.bots.push(player); + } else { + input.players.push(player); + } + } + + Ok(()) + } +} + +impl ToPacket for Players { + fn as_packet(&self) -> GDResult> { + let mut buffer = WriteBuffer::with_capacity(self.total_len() * std::mem::size_of::()); + + for player in self.players.iter().chain(self.bots.iter()) { + buffer.write(player.id)?; + buffer.write_string::(&player.name)?; + buffer.write(player.ping)?; + buffer.write(player.score)?; + buffer.write(player.stats_id)?; + } + + Ok(buffer.into_data()) + } +} + +pub type PacketPlayers<'a> = PacketResponse<&'a mut Players>; + +impl From for PacketResponse { + fn from(value: Players) -> Self { + Self { + padding: [0, 0, 0, 0], + packet_type: PacketKind::Players, + body: value, + } + } +} diff --git a/src/protocols/unreal2/protocol.rs b/src/protocols/unreal2/protocol.rs index 0dde6ca8..014fe614 100644 --- a/src/protocols/unreal2/protocol.rs +++ b/src/protocols/unreal2/protocol.rs @@ -1,15 +1,26 @@ -use crate::buffer::{Buffer, StringDecoder}; +use crate::buffer::StringDecoder; use crate::errors::GDErrorKind::PacketBad; -use crate::protocols::types::TimeoutSettings; +use crate::protocols::types::ToPacket; +use crate::protocols::types::{ExtendFromPacket, FromPacket, TimeoutSettings}; use crate::socket::{Socket, UdpSocket}; use crate::utils::retry_on_timeout; use crate::GDResult; -use super::{GatheringSettings, MutatorsAndRules, PacketKind, Players, Response, ServerInfo}; +use super::{ + GatheringSettings, + MutatorsAndRules, + PacketKind, + PacketMutatorsAndRules, + PacketPlayers, + PacketRequest, + PacketServerInfo, + Players, + Response, + ServerInfo, +}; use std::net::SocketAddr; -use byteorder::{ByteOrder, LittleEndian}; use encoding_rs::{UTF_16LE, WINDOWS_1252}; /// Response packets don't seem to exceed 500 bytes, set to 1024 just to be @@ -54,44 +65,24 @@ impl Unreal2Protocol { /// Send a request packet fn get_request_data_impl(&mut self, packet_type: PacketKind) -> GDResult> { - let request = [0x79, 0, 0, 0, packet_type as u8]; - self.socket.send(&request)?; + let request = PacketRequest::from(packet_type); + self.socket.send(&request.as_packet()?)?; let data = self.socket.receive(Some(PACKET_SIZE))?; Ok(data) } - /// Consume the header part of a response packet, validate that the packet - /// type matches what is expected. - fn consume_response_headers( - buffer: &mut Buffer, - expected_packet_type: PacketKind, - ) -> GDResult<()> { - // Skip header - buffer.move_cursor(4)?; - - let packet_type: u8 = buffer.read()?; - - let packet_type: PacketKind = packet_type.try_into()?; - - if packet_type != expected_packet_type { - Err(PacketBad.context(format!( - "Packet response ({:?}) didn't match request ({:?}) packet type", - packet_type, expected_packet_type - ))) - } else { - Ok(()) - } - } - /// Send server info query. pub fn query_server_info(&mut self) -> GDResult { let data = self.get_request_data(PacketKind::ServerInfo)?; - let mut buffer = Buffer::::new(&data); - // TODO: Maybe put consume headers in individual packet parse methods - Self::consume_response_headers(&mut buffer, PacketKind::ServerInfo)?; - ServerInfo::parse(&mut buffer) + + let packet = PacketServerInfo::from_packet(&data)?; + + // TODO: Remove debug logging + println!("{:?}", packet); + + Ok(packet.body) } /// Send mutators and rules query. @@ -104,23 +95,19 @@ impl Unreal2Protocol { let mut mutators_and_rules = MutatorsAndRules::default(); { let data = self.get_request_data(PacketKind::MutatorsAndRules)?; - let mut buffer = Buffer::::new(&data); - // TODO: Maybe put consume headers in individual packet parse methods - Self::consume_response_headers(&mut buffer, PacketKind::MutatorsAndRules)?; - mutators_and_rules.parse(&mut buffer)? + + let packet = PacketMutatorsAndRules::extend_from_packet(&data, &mut mutators_and_rules)?; + + // TODO: Remove debug logging + println!("{:?}", packet); }; // We could receive multiple packets in response while let Ok(data) = self.socket.receive(Some(PACKET_SIZE)) { - let mut buffer = Buffer::::new(&data); + let packet = PacketMutatorsAndRules::extend_from_packet(&data, &mut mutators_and_rules)?; - let r = Self::consume_response_headers(&mut buffer, PacketKind::MutatorsAndRules); - if r.is_err() { - println!("{:?}", r); - break; - } - - mutators_and_rules.parse(&mut buffer)?; + // TODO: Remove debug logging + println!("{:?}", packet); } Ok(mutators_and_rules) @@ -143,11 +130,10 @@ impl Unreal2Protocol { // Players are non required so if we don't get any responses we continue to // return while let Ok(data) = players_data { - let mut buffer = Buffer::::new(&data); + let packet = PacketPlayers::extend_from_packet(&data, &mut players)?; - Self::consume_response_headers(&mut buffer, PacketKind::Players)?; - - players.parse(&mut buffer)?; + // TODO: Remove debug logging + println!("{:?}", packet); if let Some(num_players) = num_players { if players.total_len() >= num_players { @@ -239,6 +225,9 @@ impl StringDecoder for Unreal2StringDecoder { return Err(PacketBad.context("UTF-8 string contained invalid character(s)")); } + // TODO: Remove debug logging + println!("Decoded UTF16 string"); + result } else { // Else the string is null-delimited latin1 diff --git a/src/protocols/unreal2/types.rs b/src/protocols/unreal2/types.rs index 594e76f4..d0778067 100644 --- a/src/protocols/unreal2/types.rs +++ b/src/protocols/unreal2/types.rs @@ -1,15 +1,10 @@ -use crate::buffer::Buffer; use crate::errors::GDErrorKind::PacketBad; use crate::protocols::types::{CommonPlayer, CommonResponse, ExtraRequestSettings, GenericPlayer}; use crate::protocols::GenericResponse; use crate::{GDError, GDResult}; -use super::Unreal2StringDecoder; - use std::collections::{HashMap, HashSet}; -use byteorder::ByteOrder; - /// Unreal 2 packet types. #[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] @@ -47,22 +42,6 @@ pub struct ServerInfo { pub max_players: u32, } -impl ServerInfo { - pub fn parse(buffer: &mut Buffer) -> GDResult { - Ok(ServerInfo { - server_id: buffer.read()?, - ip: buffer.read_string::(None)?, - game_port: buffer.read()?, - query_port: buffer.read()?, - name: buffer.read_string::(None)?, - map: buffer.read_string::(None)?, - game_type: buffer.read_string::(None)?, - num_players: buffer.read()?, - max_players: buffer.read()?, - }) - } -} - /// Unreal 2 mutators and rules. #[derive(Clone, Debug, Default, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] @@ -71,37 +50,6 @@ pub struct MutatorsAndRules { pub rules: HashMap>, } -impl MutatorsAndRules { - pub fn parse(&mut self, buffer: &mut Buffer) -> GDResult<()> { - while buffer.remaining_length() > 0 { - let key = buffer.read_string::(None)?; - let value = buffer.read_string::(None).ok(); - - if key.eq_ignore_ascii_case("mutator") { - if let Some(value) = value { - self.mutators.insert(value); - } - } else { - let rule_vec = self.rules.get_mut(&key); - - let rule_vec = if let Some(rule_vec) = rule_vec { - rule_vec - } else { - self.rules.insert(key.clone(), Vec::default()); - self.rules - .get_mut(&key) - .expect("Value should be in HashMap after we inserted") - }; - - if let Some(value) = value { - rule_vec.push(value); - } - } - } - Ok(()) - } -} - /// Unreal 2 players and bots. #[derive(Clone, Debug, Default, PartialEq, Eq, Hash, PartialOrd, Ord)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] @@ -123,28 +71,6 @@ impl Players { } } - /// Parse a raw buffer of players into the current struct. - pub fn parse(&mut self, buffer: &mut Buffer) -> GDResult<()> { - while buffer.remaining_length() > 0 { - let player = Player { - id: buffer.read()?, - name: buffer.read_string::(None)?, - ping: buffer.read()?, - score: buffer.read()?, - stats_id: buffer.read()?, - }; - - // If ping is 0 the player is a bot - if player.ping == 0 { - self.bots.push(player); - } else { - self.players.push(player); - } - } - - Ok(()) - } - /// Length of both players and bots. pub fn total_len(&self) -> usize { self.players.len() + self.bots.len() } } diff --git a/src/wbuffer.rs b/src/wbuffer.rs new file mode 100644 index 00000000..d6f09556 --- /dev/null +++ b/src/wbuffer.rs @@ -0,0 +1,179 @@ +use std::marker::PhantomData; + +use byteorder::ByteOrder; + +use crate::errors::GDErrorKind::{InvalidInput, OutOfMemory}; +use crate::GDResult; + +/// A buffer to write packets to. +#[derive(Clone, Debug, Default)] +pub struct WriteBuffer { + output: Vec, + _marker: PhantomData, +} + +impl WriteBuffer { + /// Allocate a new write buffer with the provided capacity (to prevent + /// re-allocations). + pub fn with_capacity(capacity: usize) -> Self { + Self { + output: Vec::with_capacity(capacity), + _marker: PhantomData, + } + } + + /// Consume the write buffer and return the bytes represented as Vec. + pub fn into_data(self) -> Vec { self.output } + + /// Append a value to the end of the buffer in it's byte represented form. + pub fn write>(&mut self, data: T) -> GDResult<()> { + // Ensure output buffer has capacity for type. + self.output + .try_reserve(std::mem::size_of::()) + .map_err(|e| OutOfMemory.context(e))?; + + T::write_to_buffer(&mut self.output, data) + } + + /// Append a string to the buffer. + pub fn write_string>(&mut self, string: &str) -> GDResult<()> { + if let Some(capacity_needed) = E::bytes_required(string) { + self.output + .try_reserve(capacity_needed) + .map_err(|e| OutOfMemory.context(e))?; + } + + E::encode_string(self, string) + } +} + +/// A trait defining a protocol for writing values of a certain type to a +/// buffer. +/// +/// Implementors of this trait provide a method for writing their type to a +/// byte buffer with a specific byte order. +pub trait BufferWrite: Sized { + fn write_to_buffer(buffer: &mut Vec, data: Self) -> GDResult<()>; +} + +macro_rules! impl_buffer_write_byte { + ($type:ty, $map_func:expr) => { + impl BufferWrite for $type { + fn write_to_buffer(buffer: &mut Vec, data: Self) -> GDResult<()> { + let map = $map_func; + buffer.push(map(data)); + + Ok(()) + } + } + }; +} + +macro_rules! impl_buffer_write { + ($type:ty, $write_func:ident) => { + impl BufferWrite for $type { + fn write_to_buffer(buffer: &mut Vec, data: Self) -> GDResult<()> { + let mut slice = [0u8; std::mem::size_of::()]; + + B::$write_func(&mut slice, data); + + buffer.extend(slice); + + Ok(()) + } + } + }; +} + +impl_buffer_write_byte!(u8, |b| b); +impl_buffer_write_byte!(i8, |b| b as u8); + +impl_buffer_write!(u16, write_u16); +impl_buffer_write!(i16, write_i16); +impl_buffer_write!(u32, write_u32); +impl_buffer_write!(i32, write_i32); +impl_buffer_write!(u64, write_u64); +impl_buffer_write!(i64, write_i64); +impl_buffer_write!(f32, write_f32); +impl_buffer_write!(f64, write_f64); + +impl BufferWrite for &[u8] { + fn write_to_buffer(buffer: &mut Vec, data: Self) -> GDResult<()> { + buffer.extend_from_slice(data); + + Ok(()) + } +} + +pub trait StringEncoder { + /// Return the number of bytes required to encode a string. This should not + /// be an estimate: if unknown return None. + fn bytes_required(_string: &str) -> Option { None } + + /// Encode a string. + fn encode_string(buffer: &mut WriteBuffer, string: &str) -> GDResult<()>; +} + +pub struct UTF8NullDelimitedEncoder; +impl StringEncoder for UTF8NullDelimitedEncoder { + fn bytes_required(string: &str) -> Option { Some(string.as_bytes().len() + 1) } + + fn encode_string(buffer: &mut WriteBuffer, string: &str) -> GDResult<()> { + buffer.write(string.as_bytes())?; + buffer.write(0u8)?; + + Ok(()) + } +} + +pub struct UTF8LengthPrefixedEncoder { + _marker: PhantomData, +} +impl + BufferWrite> StringEncoder + for UTF8LengthPrefixedEncoder +{ + fn encode_string(buffer: &mut WriteBuffer, string: &str) -> GDResult<()> { + let length: LengthWidth = (string.len() + 1).try_into().map_err(|_| { + InvalidInput.context(format!( + "Tried to encode string that was too long: {}", + string.len() + )) + })?; + + buffer.write(length)?; + buffer.write(string.as_bytes())?; + buffer.write(0u8)?; + + Ok(()) + } +} + +pub struct UCS2Unreal2Encoder; +impl StringEncoder for UCS2Unreal2Encoder { + fn bytes_required(string: &str) -> Option { Some((string.len() * 2) + 1) } + + fn encode_string(buffer: &mut WriteBuffer, string: &str) -> GDResult<()> { + let length = string.len(); + if length >= 0x80 { + return Err(InvalidInput.context(format!( + "Cannot write strings longer than {}, tried to write {}", + 0x80, + string.len() + ))); + } + + let length: u8 = length + .try_into() + .expect("Values <= 0x80 should fit in 1 byte"); + let length = length | 0x80; + + buffer.write(length)?; + + // encoding-rs doesn't have a UTF16 encoder + for byte in string.encode_utf16() { + buffer.write(byte)?; + } + + Ok(()) + } +}