diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..b48948a --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "rust-analyzer.showUnlinkedFileNotification": false +} diff --git a/examples/extract.rs b/examples/extract.rs new file mode 100644 index 0000000..91adae8 --- /dev/null +++ b/examples/extract.rs @@ -0,0 +1,13 @@ +fn main() { + let stages = std::fs::read_dir("game/stages").unwrap(); + for stage in stages { + let stage = stage.unwrap(); + let bytes: Vec = std::fs::read(stage.path()).unwrap(); + let data = roead::byml::Byml::from_binary(bytes).unwrap(); + let map = data.into_map().unwrap(); + let mtx: Vec = + map.get("ProjMatrix").unwrap().as_array().unwrap().into_iter().map(|a| a.as_float().unwrap()).collect(); + let mat4 = glam::Mat4::from_cols_slice(&mtx); + println!("{:?}", mat4); + } +} diff --git a/game/StagePosList.byml b/game/StagePosList.byml new file mode 100644 index 0000000..821c59b Binary files /dev/null and b/game/StagePosList.byml differ diff --git a/game/WorldListFromDb.byml b/game/WorldListFromDb.byml new file mode 100644 index 0000000..668d9e5 Binary files /dev/null and b/game/WorldListFromDb.byml differ diff --git a/game/stages/BossRaidWorldHomeStage.byml b/game/stages/BossRaidWorldHomeStage.byml new file mode 100644 index 0000000..9d541e7 Binary files /dev/null and b/game/stages/BossRaidWorldHomeStage.byml differ diff --git a/game/stages/CapWorldHomeStage.byml b/game/stages/CapWorldHomeStage.byml new file mode 100644 index 0000000..6671b60 Binary files /dev/null and b/game/stages/CapWorldHomeStage.byml differ diff --git a/game/stages/CapWorldHomeStage1.byml b/game/stages/CapWorldHomeStage1.byml new file mode 100644 index 0000000..6671b60 Binary files /dev/null and b/game/stages/CapWorldHomeStage1.byml differ diff --git a/game/stages/CapWorldHomeStage2.byml b/game/stages/CapWorldHomeStage2.byml new file mode 100644 index 0000000..6671b60 Binary files /dev/null and b/game/stages/CapWorldHomeStage2.byml differ diff --git a/game/stages/CityWorldHomeStage.byml b/game/stages/CityWorldHomeStage.byml new file mode 100644 index 0000000..93c241a Binary files /dev/null and b/game/stages/CityWorldHomeStage.byml differ diff --git a/game/stages/ClashWorldHomeStage.byml b/game/stages/ClashWorldHomeStage.byml new file mode 100644 index 0000000..6de7614 Binary files /dev/null and b/game/stages/ClashWorldHomeStage.byml differ diff --git a/game/stages/CloudWorldHomeStage.byml b/game/stages/CloudWorldHomeStage.byml new file mode 100644 index 0000000..8a4710d Binary files /dev/null and b/game/stages/CloudWorldHomeStage.byml differ diff --git a/game/stages/CloudWorldHomeStage4.byml b/game/stages/CloudWorldHomeStage4.byml new file mode 100644 index 0000000..b6f1012 Binary files /dev/null and b/game/stages/CloudWorldHomeStage4.byml differ diff --git a/game/stages/ForestWorldHomeStage.byml b/game/stages/ForestWorldHomeStage.byml new file mode 100644 index 0000000..f4be386 Binary files /dev/null and b/game/stages/ForestWorldHomeStage.byml differ diff --git a/game/stages/LakeWorldHomeStage.byml b/game/stages/LakeWorldHomeStage.byml new file mode 100644 index 0000000..06efb5b Binary files /dev/null and b/game/stages/LakeWorldHomeStage.byml differ diff --git a/game/stages/LavaWorldHomeStage.byml b/game/stages/LavaWorldHomeStage.byml new file mode 100644 index 0000000..d38207b Binary files /dev/null and b/game/stages/LavaWorldHomeStage.byml differ diff --git a/game/stages/LavaWorldHomeStage1.byml b/game/stages/LavaWorldHomeStage1.byml new file mode 100644 index 0000000..d38207b Binary files /dev/null and b/game/stages/LavaWorldHomeStage1.byml differ diff --git a/game/stages/LavaWorldHomeStage2.byml b/game/stages/LavaWorldHomeStage2.byml new file mode 100644 index 0000000..d38207b Binary files /dev/null and b/game/stages/LavaWorldHomeStage2.byml differ diff --git a/game/stages/MoonWorldHomeStage.byml b/game/stages/MoonWorldHomeStage.byml new file mode 100644 index 0000000..6613a2e Binary files /dev/null and b/game/stages/MoonWorldHomeStage.byml differ diff --git a/game/stages/PeachWorldHomeStage.byml b/game/stages/PeachWorldHomeStage.byml new file mode 100644 index 0000000..542fd20 Binary files /dev/null and b/game/stages/PeachWorldHomeStage.byml differ diff --git a/game/stages/SandWorldHomeStage.byml b/game/stages/SandWorldHomeStage.byml new file mode 100644 index 0000000..2350389 Binary files /dev/null and b/game/stages/SandWorldHomeStage.byml differ diff --git a/game/stages/SeaWorldHomeStage.byml b/game/stages/SeaWorldHomeStage.byml new file mode 100644 index 0000000..579a87f Binary files /dev/null and b/game/stages/SeaWorldHomeStage.byml differ diff --git a/game/stages/SkyWorldHomeStage.byml b/game/stages/SkyWorldHomeStage.byml new file mode 100644 index 0000000..d65c5a5 Binary files /dev/null and b/game/stages/SkyWorldHomeStage.byml differ diff --git a/game/stages/SnowWorldHomeStage.byml b/game/stages/SnowWorldHomeStage.byml new file mode 100644 index 0000000..b1794f7 Binary files /dev/null and b/game/stages/SnowWorldHomeStage.byml differ diff --git a/game/stages/Special1WorldHomeStage.byml b/game/stages/Special1WorldHomeStage.byml new file mode 100644 index 0000000..e3b94d1 Binary files /dev/null and b/game/stages/Special1WorldHomeStage.byml differ diff --git a/game/stages/Special2WorldHomeStage.byml b/game/stages/Special2WorldHomeStage.byml new file mode 100644 index 0000000..de706f6 Binary files /dev/null and b/game/stages/Special2WorldHomeStage.byml differ diff --git a/game/stages/WaterfallWorldHomeStage.byml b/game/stages/WaterfallWorldHomeStage.byml new file mode 100644 index 0000000..37686c6 Binary files /dev/null and b/game/stages/WaterfallWorldHomeStage.byml differ diff --git a/p12.nu b/p12.nu new file mode 100644 index 0000000..f144e6e --- /dev/null +++ b/p12.nu @@ -0,0 +1 @@ +openssl pkcs12 -export -in cert.pem -inkey key.pem -out server.p12 diff --git a/src/faker.rs b/src/faker.rs new file mode 100644 index 0000000..16480f4 --- /dev/null +++ b/src/faker.rs @@ -0,0 +1,63 @@ +use glam::Vec3; +use xtra::{Actor, Address, Handler}; + +use crate::{ + broadcast_packet, + packet::{ + Packet, PacketData, + PacketData_variants::{Connect, Player}, + }, + protocol::String, +}; + +pub struct Faker { + pub address: Address, +} + +impl Actor for Faker { + type Stop = (); + + async fn stopped(self) -> Self::Stop {} +} + +impl Handler for Faker { + type Return = (); + + async fn handle(&mut self, message: Packet, _: &mut xtra::Context) -> Self::Return { + // trace!("got packet {message:?}"); + match message.data { + PacketData::Connect(connect) => { + broadcast_packet(Packet { + user_id: 1, + udp: message.udp, + data: PacketData::Connect(Connect { + client_name: String::try_from("Bot").unwrap(), + ..connect + }), + }) + .await; + } + PacketData::Player(player) => { + let pos = Vec3::from_array(player.position); + broadcast_packet(Packet { + user_id: 1, + udp: message.udp, + data: PacketData::Player(Player { + position: (pos + Vec3::new(0., -150., 0.)).into(), + ..player + }), + }) + .await; + } + + data => { + broadcast_packet(Packet { + user_id: 1, + udp: message.udp, + data, + }) + .await; + } + } + } +} diff --git a/src/game/mod.rs b/src/game/mod.rs new file mode 100644 index 0000000..e69de29 diff --git a/src/packet/mod.rs b/src/packet/mod.rs new file mode 100644 index 0000000..340bbd6 --- /dev/null +++ b/src/packet/mod.rs @@ -0,0 +1,143 @@ +pub mod rw; + +use newtype_enum::newtype_enum; +use zerocopy::{FromZeros, Immutable, IntoBytes, KnownLayout}; + +use crate::protocol::{Bool, String}; + +#[derive(Debug, Clone, Copy, FromZeros, IntoBytes, KnownLayout, Immutable)] +#[repr(C, packed)] +pub struct PacketHeader { + pub user_id: u128, + pub kind: PacketKind, + pub size: u16, +} + +#[derive(Debug, Clone, Copy, FromZeros, IntoBytes, KnownLayout, Immutable)] +#[repr(u16)] +pub enum PacketKind { + Unknown = 0, + Init = 1, + Player = 2, + Cap = 3, + Game = 4, + Tag = 5, + Connect = 6, + Disconnect = 7, + Costume = 8, + Shine = 9, + Capture = 10, + ChangeStage = 11, + Command = 12, + UdpInit = 13, + HolePunch = 14, +} + +const COSTUME_NAME_SIZE: usize = 0x20; +const CAP_ANIM_SIZE: usize = 0x30; +pub const STAGE_GAME_NAME_SIZE: usize = 0x40; +const STAGE_CHANGE_NAME_SIZE: usize = 0x30; +const STAGE_ID_SIZE: usize = 0x10; +pub const CLIENT_NAME_SIZE: usize = COSTUME_NAME_SIZE; + +#[derive(Clone, Debug)] +pub struct Packet { + pub user_id: u128, + pub udp: bool, + pub data: PacketData, +} + +#[newtype_enum] +#[derive(Clone, Debug)] +pub enum PacketData { + Unknown(Vec), + #[derive(FromZeros, IntoBytes, KnownLayout, Immutable)] + #[repr(C, packed)] + Init { + pub max_players: u16, + }, + #[derive(FromZeros, IntoBytes, KnownLayout, Immutable)] + #[repr(C, packed)] + Player { + position: [f32; 3], + rotation: [f32; 4], + weights: [f32; 6], + action: u16, + subaction: u16, + }, + #[derive(FromZeros, IntoBytes, KnownLayout, Immutable)] + #[repr(C, packed)] + Cap { + position: [f32; 3], + rotation: [f32; 4], + out: Bool, + anim: String, + }, + #[derive(FromZeros, IntoBytes, KnownLayout, Immutable)] + #[repr(C, packed)] + Game { + is_2d: u8, + scenario_num: u8, + stage: String, + }, + #[derive(FromZeros, IntoBytes, KnownLayout, Immutable)] + #[repr(C, packed)] + Tag { + update_type: u8, + is_it: Bool, + seconds: u8, + minutes: u16, + }, + #[derive(FromZeros, IntoBytes, KnownLayout, Immutable)] + #[repr(C, packed)] + Connect { + kind: ConnectionKind, + max_player: u16, + client_name: String, + }, + Disconnect, + #[derive(FromZeros, IntoBytes, KnownLayout, Immutable)] + #[repr(C, packed)] + Costume { + body_name: String, + cap_name: String, + }, + #[derive(FromZeros, IntoBytes, KnownLayout, Immutable)] + #[repr(C, packed)] + Shine { + shine_id: i32, + is_grand: Bool, + }, + #[derive(FromZeros, IntoBytes, KnownLayout, Immutable)] + #[repr(C, packed)] + Capture { + model: String, + }, + #[derive(FromZeros, IntoBytes, KnownLayout, Immutable)] + #[repr(C, packed)] + ChangeStage { + stage: String, + id: String, + scenario: i8, + sub_scenario: u8, + }, + Command, + #[derive(FromZeros, IntoBytes, KnownLayout, Immutable)] + #[repr(C, packed)] + UdpInit { + port: u16, + }, + HolePunch, +} + +pub enum TagUpdateBit { + Time = 0, + State = 1, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, FromZeros, IntoBytes, KnownLayout, Immutable)] +#[repr(u32)] +pub enum ConnectionKind { + New = 0, + Old = 1, +} diff --git a/src/packet/rw.rs b/src/packet/rw.rs new file mode 100644 index 0000000..8d49b56 --- /dev/null +++ b/src/packet/rw.rs @@ -0,0 +1,114 @@ +use anyhow::{bail, Context}; +use tokio::io::{AsyncReadExt, AsyncWriteExt}; +use zerocopy::{IntoBytes, TryFromBytes}; + +use crate::packet::{ + Packet, PacketData, + PacketData_variants::{Command, Disconnect, HolePunch}, + PacketHeader, PacketKind, +}; + +pub async fn read_packet(reader: &mut R, udp: bool) -> anyhow::Result { + let mut header = [0; size_of::()]; + reader.read_exact(&mut header).await.context("reading data")?; + let Ok(header) = PacketHeader::try_read_from_bytes(&header) else { + bail!("parsing packet buffer") + }; + + macro_rules! read_data { + ($read: expr, $size: expr, $ty: ident $(, $field:ident => $assert: expr)*) => {{ + async fn read_data(reader: &mut R, size: u16) -> anyhow::Result { + type T = crate::packet::PacketData_variants::$ty; + let size = size as usize; + if size < size_of::() { + bail!("buffer too small for packet: expected {}, got {size}", size_of::()) + } + let mut data = [0; u16::MAX as usize]; + reader.read_exact(&mut data[..size]).await.context("reading data")?; + let packet_data = match T::try_read_from_bytes(&data[..size_of::()]) { + Ok(data) => data, + Err(error) => { + bail!(concat!("interpreting ", stringify!($ty), ": {:?}"), error) + } + }; + + $( + { + let $field = packet_data.$field; + $assert + }?; + )* + Ok(PacketData::$ty(packet_data)) + } + type _FixIntellisense = crate::packet::PacketData_variants::$ty; + read_data($read, $size).await? + }}; + } + + let data: PacketData = match header.kind { + PacketKind::Unknown => { + let mut data = vec![0; header.size.into()]; + reader.read_exact(&mut data).await.context("reading unknown data")?; + PacketData::Unknown(data) + } + PacketKind::Init => read_data!(reader, header.size, Init), + PacketKind::Player => read_data!(reader, header.size, Player), + PacketKind::Cap => read_data!(reader, header.size, Cap, anim => anim.assert_valid()), + PacketKind::Game => read_data!(reader, header.size, Game, stage => stage.assert_valid()), + PacketKind::Tag => read_data!(reader, header.size, Tag), + PacketKind::Connect => read_data!(reader, header.size, Connect), + PacketKind::Disconnect => PacketData::Disconnect(Disconnect), + PacketKind::Costume => read_data!(reader, header.size, Costume), + PacketKind::Shine => read_data!(reader, header.size, Shine), + PacketKind::Capture => read_data!(reader, header.size, Capture), + PacketKind::ChangeStage => read_data!(reader, header.size, ChangeStage), + PacketKind::Command => PacketData::Command(Command), + PacketKind::UdpInit => read_data!(reader, header.size, UdpInit), + PacketKind::HolePunch => PacketData::HolePunch(HolePunch), + }; + + Ok(Packet { + user_id: header.user_id, + udp, + data, + }) +} + +pub async fn write_packet(writer: &mut W, id: u128, data: PacketData) -> anyhow::Result<()> { + let (kind, slice) = match &data { + PacketData::Unknown(vec) => { + if vec.len() >= (256 as usize) { + bail!("unknown packet vec too large") + } + (PacketKind::Unknown, vec.as_slice()) + } + PacketData::Init(init) => (PacketKind::Init, init.as_bytes()), + PacketData::Player(player) => (PacketKind::Player, player.as_bytes()), + PacketData::Cap(cap) => (PacketKind::Cap, cap.as_bytes()), + PacketData::Game(game) => (PacketKind::Game, game.as_bytes()), + PacketData::Tag(tag) => (PacketKind::Tag, tag.as_bytes()), + PacketData::Connect(connect) => (PacketKind::Connect, connect.as_bytes()), + PacketData::Disconnect(..) => (PacketKind::Disconnect, [].as_slice()), + PacketData::Costume(costume) => (PacketKind::Costume, costume.as_bytes()), + PacketData::Shine(shine) => (PacketKind::Shine, shine.as_bytes()), + PacketData::Capture(capture) => (PacketKind::Capture, capture.as_bytes()), + PacketData::ChangeStage(change_stage) => (PacketKind::ChangeStage, change_stage.as_bytes()), + PacketData::Command(..) => (PacketKind::Command, [].as_slice()), + PacketData::UdpInit(udp_init) => (PacketKind::UdpInit, udp_init.as_bytes()), + PacketData::HolePunch(..) => (PacketKind::HolePunch, [].as_slice()), + }; + + writer + .write_all( + PacketHeader { + kind, + size: slice.len() as u16, + user_id: id, + } + .as_bytes(), + ) + .await + .context("writing header")?; + writer.write_all(slice).await.context("writing data")?; + writer.flush().await.context("flushing writer") +} diff --git a/src/protocol.rs b/src/protocol.rs new file mode 100644 index 0000000..54aa906 --- /dev/null +++ b/src/protocol.rs @@ -0,0 +1,76 @@ +use std::{ + ffi::CStr, + fmt::{Debug, Display}, +}; + +use anyhow::{bail, Context}; +use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout, Unaligned}; + +#[derive(Clone, Copy, Debug, PartialEq, Eq, FromBytes, IntoBytes, KnownLayout, Immutable, Unaligned)] +#[repr(C)] +pub struct Bool(u8); + +impl Bool { + pub fn new(value: bool) -> Bool { + Bool(if value { 1 } else { 0 }) + } + pub fn get(&self) -> bool { + self.0 != 0 + } +} + +impl From for Bool { + fn from(value: bool) -> Self { + Bool::new(value) + } +} + +impl From for bool { + fn from(value: Bool) -> Self { + value.get() + } +} + +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, FromBytes, IntoBytes, KnownLayout, Immutable, Unaligned)] +#[repr(C)] +pub struct String([u8; N]); + +impl String { + pub fn as_str(&self) -> &str { + self.try_as_str().expect("wasn't a string") + } + pub fn try_as_str(&self) -> anyhow::Result<&str> { + let cstr = CStr::from_bytes_until_nul(&self.0).context("interpreting bytes as c-string")?; + cstr.to_str().context("verifying string has utf-8") + // let str = str::from_utf8(&self.0).context("verifying string has utf-8")?; + // Ok(str.trim_end_matches('\0')) + } + pub fn assert_valid(&self) -> anyhow::Result<()> { + self.try_as_str().map(drop) + } +} + +impl TryFrom<&str> for String { + type Error = anyhow::Error; + fn try_from(value: &str) -> Result { + let mut buf = [0; N]; + if value.len() > N { + bail!("seggs") + } + + value.write_to_prefix(&mut buf).unwrap(); + + Ok(Self(buf)) + } +} + +impl Display for String { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + Display::fmt(self.try_as_str().expect("failed to parse string"), f) + } +} +impl Debug for String { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + Debug::fmt(self.try_as_str().expect("failed to parse string"), f) + } +} diff --git a/src/server/mod.rs b/src/server/mod.rs new file mode 100644 index 0000000..980c08c --- /dev/null +++ b/src/server/mod.rs @@ -0,0 +1,170 @@ +mod packet; +mod prox; + +use std::{collections::HashMap, sync::LazyLock}; + +use glam::Vec3; +use prox::ProximityPlayer; +use tokio::sync::RwLock; +use tracing::{error, info_span, Instrument}; +use uuid::Uuid; +use wtransport::{Endpoint, Identity, ServerConfig}; +use xtra::{Actor, Address, Handler, Mailbox}; +use zerocopy::FromZeros; + +use crate::{ + packet::{CLIENT_NAME_SIZE, STAGE_GAME_NAME_SIZE}, + protocol::String, +}; + +fn listeners() -> &'static RwLock>> { + static LISTENERS: LazyLock>>> = LazyLock::new(Default::default); + + &LISTENERS +} + +pub fn web_main() -> Address { + let span = info_span!("wt"); + + let manager = xtra::spawn_tokio( + Manager { + players: HashMap::new(), + next_id: 0, + }, + Mailbox::bounded(8), + ); + tokio::spawn({ + let manager = manager.clone(); + async move { + if let Err(result) = webtransport_server(manager).await { + error!("{:?}", result); + } + } + .instrument(span) + }); + manager +} + +async fn webtransport_server(manager: Address) -> anyhow::Result<()> { + // let identity = Identity::self_signed(["localhost", "127.0.0.1", "::1"]).unwrap(); + let identity = Identity::load_pemfiles("./cert.pem", "./key.pem").await.unwrap(); + + let config = ServerConfig::builder().with_bind_default(4433).with_identity(identity).build(); + + let endpoint = Endpoint::server(config)?; + + loop { + let connection = endpoint.accept().await; + + ProximityPlayer::spawn(connection, manager.clone()); + } +} + +#[derive(Actor)] +pub struct Manager { + players: HashMap, + next_id: u32, +} + +impl Manager { + pub async fn broadcast(&self, message: M) + where + ProximityPlayer: Handler, + { + for player in listeners().read().await.values() { + let _ = player.send(message).detach().await; + } + } +} + +#[derive(Debug, Clone, Copy)] +struct PlayerInstance { + id: u32, + name: String, + position: Vec3, + stage: String, +} + +struct RequestState; +impl Handler for Manager { + type Return = HashMap; + + async fn handle(&mut self, _: RequestState, _: &mut xtra::Context) -> Self::Return { + self.players.clone() + } +} + +pub struct PlayerConnected { + pub id: u128, + pub name: String, +} + +impl Handler for Manager { + type Return = (); + + async fn handle(&mut self, message: PlayerConnected, _: &mut xtra::Context) -> Self::Return { + self.players.insert( + message.id, + PlayerInstance { + id: { + let id = self.next_id; + self.next_id += 1; + id + }, + name: message.name, + position: Vec3::ZERO, + stage: String::new_zeroed(), + }, + ); + } +} + +#[derive(Clone, Copy)] +pub struct PlayerDisconnected { + pub id: u128, +} + +impl Handler for Manager { + type Return = (); + + async fn handle(&mut self, message: PlayerDisconnected, _: &mut xtra::Context) -> Self::Return { + if self.players.remove(&message.id).is_some() { + self.broadcast(message).await; + } + } +} + +#[derive(Clone, Copy)] +pub struct PlayerMoved { + pub id: u128, + pub position: Vec3, +} + +impl Handler for Manager { + type Return = (); + + async fn handle(&mut self, message: PlayerMoved, _: &mut xtra::Context) -> Self::Return { + if let Some(player) = self.players.get_mut(&message.id) { + player.position = message.position; + + self.broadcast(message).await; + } + } +} + +#[derive(Clone, Copy)] +pub struct ChangedStage { + pub id: u128, + pub stage: String, +} + +impl Handler for Manager { + type Return = (); + + async fn handle(&mut self, message: ChangedStage, _: &mut xtra::Context) -> Self::Return { + if let Some(player) = self.players.get_mut(&message.id) { + player.stage = message.stage; + self.broadcast(message).await; + } + } +} diff --git a/src/server/packet.rs b/src/server/packet.rs new file mode 100644 index 0000000..2d8d627 --- /dev/null +++ b/src/server/packet.rs @@ -0,0 +1,30 @@ +use zerocopy::{Immutable, IntoBytes}; + +use crate::{ + packet::{CLIENT_NAME_SIZE, STAGE_GAME_NAME_SIZE}, + protocol::String, +}; + +use super::PlayerInstance; + +#[allow(unused)] +#[derive(Debug, IntoBytes, Immutable)] +#[repr(C, packed)] +pub struct HelloPlayer { + name: String, + id: u32, + position: [f32; 3], + stage: String, +} + +impl From for HelloPlayer { + fn from(value: PlayerInstance) -> Self { + Self { + id: value.id, + name: value.name, + position: value.position.to_array(), + stage: value.stage, + } + } + +} diff --git a/src/server/prox.rs b/src/server/prox.rs new file mode 100644 index 0000000..51fdd06 --- /dev/null +++ b/src/server/prox.rs @@ -0,0 +1,91 @@ +use core::str; + +use tokio::io::AsyncWriteExt; +use tracing::{info_span, trace, warn, Instrument}; +use uuid::Uuid; +use wtransport::endpoint::IncomingSession; +use xtra::{Actor, Address, Handler, Mailbox}; +use zerocopy::IntoBytes; + +use super::{ + listeners, packet::HelloPlayer, ChangedStage, Manager, PlayerConnected, PlayerDisconnected, PlayerMoved, RequestState, +}; + +pub struct ProximityPlayer { + id: Uuid, +_send: wtransport::SendStream, +_connection: wtransport::Connection, +} + +impl ProximityPlayer { + pub fn spawn(session: IncomingSession, manager: Address) { + tokio::spawn( + async move { + trace!("proximity chat client connected"); + let connection = + session.await.expect("failed to acknowledge session").accept().await.expect("failed to accept session"); + + let (mut send, mut recv) = connection.accept_bi().await.expect("failed to start channel"); + trace!("getting peerjs uuid"); + let mut buffer = [0; 36]; + recv.read_exact(buffer.as_mut_bytes()).await.expect("failed to read uuid"); + let id = Uuid::parse_str(str::from_utf8(&buffer).expect("expected utf8")).expect("failed to parse uuid"); + let span = info_span!("", %id); + span.in_scope(||trace!( "uuid parsed")); + + let state = manager.send(RequestState).await.unwrap(); + + send.write_u8(state.len() as u8).await.expect("failed to write length"); + for player in state.values() { + trace!("sending player {player:?}"); + send.write_all(HelloPlayer::from(*player).as_bytes()).await.expect("failed to write player"); + } + + let (address, mailbox) = Mailbox::unbounded(); + listeners().write().await.insert(id, address); + xtra::run(mailbox, ProximityPlayer { id, _send: send, _connection: connection }).instrument(span).await; + } + .in_current_span(), + ); + } +} + +impl Actor for ProximityPlayer { + type Stop = (); + + async fn stopped(self) -> Self::Stop { + listeners().write().await.remove(&self.id); + } +} + +impl Handler for ProximityPlayer { + type Return = (); + + async fn handle(&mut self, _message: PlayerConnected, _: &mut xtra::Context) -> Self::Return { + warn!("todo: implement player connected") + } +} + +impl Handler for ProximityPlayer { + type Return = (); + + async fn handle(&mut self, _message: PlayerDisconnected, _: &mut xtra::Context) -> Self::Return { + warn!("todo: implement player disconnected") + } +} + +impl Handler for ProximityPlayer { + type Return = (); + + async fn handle(&mut self, _message: PlayerMoved, _: &mut xtra::Context) -> Self::Return { + warn!("todo: implement player moved") + } +} + +impl Handler for ProximityPlayer { + type Return = (); + + async fn handle(&mut self, _message: ChangedStage, _: &mut xtra::Context) -> Self::Return { + warn!("todo: implement changed stage") + } +} diff --git a/todo.txt b/todo.txt new file mode 100644 index 0000000..a9e766d --- /dev/null +++ b/todo.txt @@ -0,0 +1,5 @@ +players -> manager (accumulate player state) +manager -> proximity player (relay state changes) +proximity player -> web (relay state changes, signaling) +web -> proximity player (signaling) +