proximity server+
This commit is contained in:
parent
1963549b45
commit
6708cc3e22
3
.vscode/settings.json
vendored
Normal file
3
.vscode/settings.json
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"rust-analyzer.showUnlinkedFileNotification": false
|
||||||
|
}
|
13
examples/extract.rs
Normal file
13
examples/extract.rs
Normal file
|
@ -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<u8> = std::fs::read(stage.path()).unwrap();
|
||||||
|
let data = roead::byml::Byml::from_binary(bytes).unwrap();
|
||||||
|
let map = data.into_map().unwrap();
|
||||||
|
let mtx: Vec<f32> =
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
BIN
game/StagePosList.byml
Normal file
BIN
game/StagePosList.byml
Normal file
Binary file not shown.
BIN
game/WorldListFromDb.byml
Normal file
BIN
game/WorldListFromDb.byml
Normal file
Binary file not shown.
BIN
game/stages/BossRaidWorldHomeStage.byml
Normal file
BIN
game/stages/BossRaidWorldHomeStage.byml
Normal file
Binary file not shown.
BIN
game/stages/CapWorldHomeStage.byml
Normal file
BIN
game/stages/CapWorldHomeStage.byml
Normal file
Binary file not shown.
BIN
game/stages/CapWorldHomeStage1.byml
Normal file
BIN
game/stages/CapWorldHomeStage1.byml
Normal file
Binary file not shown.
BIN
game/stages/CapWorldHomeStage2.byml
Normal file
BIN
game/stages/CapWorldHomeStage2.byml
Normal file
Binary file not shown.
BIN
game/stages/CityWorldHomeStage.byml
Normal file
BIN
game/stages/CityWorldHomeStage.byml
Normal file
Binary file not shown.
BIN
game/stages/ClashWorldHomeStage.byml
Normal file
BIN
game/stages/ClashWorldHomeStage.byml
Normal file
Binary file not shown.
BIN
game/stages/CloudWorldHomeStage.byml
Normal file
BIN
game/stages/CloudWorldHomeStage.byml
Normal file
Binary file not shown.
BIN
game/stages/CloudWorldHomeStage4.byml
Normal file
BIN
game/stages/CloudWorldHomeStage4.byml
Normal file
Binary file not shown.
BIN
game/stages/ForestWorldHomeStage.byml
Normal file
BIN
game/stages/ForestWorldHomeStage.byml
Normal file
Binary file not shown.
BIN
game/stages/LakeWorldHomeStage.byml
Normal file
BIN
game/stages/LakeWorldHomeStage.byml
Normal file
Binary file not shown.
BIN
game/stages/LavaWorldHomeStage.byml
Normal file
BIN
game/stages/LavaWorldHomeStage.byml
Normal file
Binary file not shown.
BIN
game/stages/LavaWorldHomeStage1.byml
Normal file
BIN
game/stages/LavaWorldHomeStage1.byml
Normal file
Binary file not shown.
BIN
game/stages/LavaWorldHomeStage2.byml
Normal file
BIN
game/stages/LavaWorldHomeStage2.byml
Normal file
Binary file not shown.
BIN
game/stages/MoonWorldHomeStage.byml
Normal file
BIN
game/stages/MoonWorldHomeStage.byml
Normal file
Binary file not shown.
BIN
game/stages/PeachWorldHomeStage.byml
Normal file
BIN
game/stages/PeachWorldHomeStage.byml
Normal file
Binary file not shown.
BIN
game/stages/SandWorldHomeStage.byml
Normal file
BIN
game/stages/SandWorldHomeStage.byml
Normal file
Binary file not shown.
BIN
game/stages/SeaWorldHomeStage.byml
Normal file
BIN
game/stages/SeaWorldHomeStage.byml
Normal file
Binary file not shown.
BIN
game/stages/SkyWorldHomeStage.byml
Normal file
BIN
game/stages/SkyWorldHomeStage.byml
Normal file
Binary file not shown.
BIN
game/stages/SnowWorldHomeStage.byml
Normal file
BIN
game/stages/SnowWorldHomeStage.byml
Normal file
Binary file not shown.
BIN
game/stages/Special1WorldHomeStage.byml
Normal file
BIN
game/stages/Special1WorldHomeStage.byml
Normal file
Binary file not shown.
BIN
game/stages/Special2WorldHomeStage.byml
Normal file
BIN
game/stages/Special2WorldHomeStage.byml
Normal file
Binary file not shown.
BIN
game/stages/WaterfallWorldHomeStage.byml
Normal file
BIN
game/stages/WaterfallWorldHomeStage.byml
Normal file
Binary file not shown.
1
p12.nu
Normal file
1
p12.nu
Normal file
|
@ -0,0 +1 @@
|
||||||
|
openssl pkcs12 -export -in cert.pem -inkey key.pem -out server.p12
|
63
src/faker.rs
Normal file
63
src/faker.rs
Normal file
|
@ -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<Faker>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Actor for Faker {
|
||||||
|
type Stop = ();
|
||||||
|
|
||||||
|
async fn stopped(self) -> Self::Stop {}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Handler<Packet> for Faker {
|
||||||
|
type Return = ();
|
||||||
|
|
||||||
|
async fn handle(&mut self, message: Packet, _: &mut xtra::Context<Self>) -> 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
0
src/game/mod.rs
Normal file
0
src/game/mod.rs
Normal file
143
src/packet/mod.rs
Normal file
143
src/packet/mod.rs
Normal file
|
@ -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<u8>),
|
||||||
|
#[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<CAP_ANIM_SIZE>,
|
||||||
|
},
|
||||||
|
#[derive(FromZeros, IntoBytes, KnownLayout, Immutable)]
|
||||||
|
#[repr(C, packed)]
|
||||||
|
Game {
|
||||||
|
is_2d: u8,
|
||||||
|
scenario_num: u8,
|
||||||
|
stage: String<STAGE_GAME_NAME_SIZE>,
|
||||||
|
},
|
||||||
|
#[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<CLIENT_NAME_SIZE>,
|
||||||
|
},
|
||||||
|
Disconnect,
|
||||||
|
#[derive(FromZeros, IntoBytes, KnownLayout, Immutable)]
|
||||||
|
#[repr(C, packed)]
|
||||||
|
Costume {
|
||||||
|
body_name: String<COSTUME_NAME_SIZE>,
|
||||||
|
cap_name: String<COSTUME_NAME_SIZE>,
|
||||||
|
},
|
||||||
|
#[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<COSTUME_NAME_SIZE>,
|
||||||
|
},
|
||||||
|
#[derive(FromZeros, IntoBytes, KnownLayout, Immutable)]
|
||||||
|
#[repr(C, packed)]
|
||||||
|
ChangeStage {
|
||||||
|
stage: String<STAGE_CHANGE_NAME_SIZE>,
|
||||||
|
id: String<STAGE_ID_SIZE>,
|
||||||
|
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,
|
||||||
|
}
|
114
src/packet/rw.rs
Normal file
114
src/packet/rw.rs
Normal file
|
@ -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<R: AsyncReadExt + Unpin>(reader: &mut R, udp: bool) -> anyhow::Result<Packet> {
|
||||||
|
let mut header = [0; size_of::<PacketHeader>()];
|
||||||
|
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<R: AsyncReadExt + Unpin>(reader: &mut R, size: u16) -> anyhow::Result<PacketData> {
|
||||||
|
type T = crate::packet::PacketData_variants::$ty;
|
||||||
|
let size = size as usize;
|
||||||
|
if size < size_of::<T>() {
|
||||||
|
bail!("buffer too small for packet: expected {}, got {size}", size_of::<T>())
|
||||||
|
}
|
||||||
|
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::<T>()]) {
|
||||||
|
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<W: AsyncWriteExt + Unpin>(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")
|
||||||
|
}
|
76
src/protocol.rs
Normal file
76
src/protocol.rs
Normal file
|
@ -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<bool> for Bool {
|
||||||
|
fn from(value: bool) -> Self {
|
||||||
|
Bool::new(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Bool> 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<const N: usize>([u8; N]);
|
||||||
|
|
||||||
|
impl<const N: usize> String<N> {
|
||||||
|
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<const N: usize> TryFrom<&str> for String<N> {
|
||||||
|
type Error = anyhow::Error;
|
||||||
|
fn try_from(value: &str) -> Result<Self, Self::Error> {
|
||||||
|
let mut buf = [0; N];
|
||||||
|
if value.len() > N {
|
||||||
|
bail!("seggs")
|
||||||
|
}
|
||||||
|
|
||||||
|
value.write_to_prefix(&mut buf).unwrap();
|
||||||
|
|
||||||
|
Ok(Self(buf))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const N: usize> Display for String<N> {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
Display::fmt(self.try_as_str().expect("failed to parse string"), f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<const N: usize> Debug for String<N> {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
Debug::fmt(self.try_as_str().expect("failed to parse string"), f)
|
||||||
|
}
|
||||||
|
}
|
170
src/server/mod.rs
Normal file
170
src/server/mod.rs
Normal file
|
@ -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<HashMap<Uuid, Address<ProximityPlayer>>> {
|
||||||
|
static LISTENERS: LazyLock<RwLock<HashMap<Uuid, Address<ProximityPlayer>>>> = LazyLock::new(Default::default);
|
||||||
|
|
||||||
|
&LISTENERS
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn web_main() -> Address<Manager> {
|
||||||
|
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<Manager>) -> 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<u128, PlayerInstance>,
|
||||||
|
next_id: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Manager {
|
||||||
|
pub async fn broadcast<M: Copy + Send + 'static>(&self, message: M)
|
||||||
|
where
|
||||||
|
ProximityPlayer: Handler<M>,
|
||||||
|
{
|
||||||
|
for player in listeners().read().await.values() {
|
||||||
|
let _ = player.send(message).detach().await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
struct PlayerInstance {
|
||||||
|
id: u32,
|
||||||
|
name: String<CLIENT_NAME_SIZE>,
|
||||||
|
position: Vec3,
|
||||||
|
stage: String<STAGE_GAME_NAME_SIZE>,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct RequestState;
|
||||||
|
impl Handler<RequestState> for Manager {
|
||||||
|
type Return = HashMap<u128, PlayerInstance>;
|
||||||
|
|
||||||
|
async fn handle(&mut self, _: RequestState, _: &mut xtra::Context<Self>) -> Self::Return {
|
||||||
|
self.players.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct PlayerConnected {
|
||||||
|
pub id: u128,
|
||||||
|
pub name: String<CLIENT_NAME_SIZE>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Handler<PlayerConnected> for Manager {
|
||||||
|
type Return = ();
|
||||||
|
|
||||||
|
async fn handle(&mut self, message: PlayerConnected, _: &mut xtra::Context<Self>) -> 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<PlayerDisconnected> for Manager {
|
||||||
|
type Return = ();
|
||||||
|
|
||||||
|
async fn handle(&mut self, message: PlayerDisconnected, _: &mut xtra::Context<Self>) -> 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<PlayerMoved> for Manager {
|
||||||
|
type Return = ();
|
||||||
|
|
||||||
|
async fn handle(&mut self, message: PlayerMoved, _: &mut xtra::Context<Self>) -> 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<STAGE_GAME_NAME_SIZE>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Handler<ChangedStage> for Manager {
|
||||||
|
type Return = ();
|
||||||
|
|
||||||
|
async fn handle(&mut self, message: ChangedStage, _: &mut xtra::Context<Self>) -> Self::Return {
|
||||||
|
if let Some(player) = self.players.get_mut(&message.id) {
|
||||||
|
player.stage = message.stage;
|
||||||
|
self.broadcast(message).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
30
src/server/packet.rs
Normal file
30
src/server/packet.rs
Normal file
|
@ -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<CLIENT_NAME_SIZE>,
|
||||||
|
id: u32,
|
||||||
|
position: [f32; 3],
|
||||||
|
stage: String<STAGE_GAME_NAME_SIZE>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<PlayerInstance> for HelloPlayer {
|
||||||
|
fn from(value: PlayerInstance) -> Self {
|
||||||
|
Self {
|
||||||
|
id: value.id,
|
||||||
|
name: value.name,
|
||||||
|
position: value.position.to_array(),
|
||||||
|
stage: value.stage,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
91
src/server/prox.rs
Normal file
91
src/server/prox.rs
Normal file
|
@ -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<Manager>) {
|
||||||
|
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<PlayerConnected> for ProximityPlayer {
|
||||||
|
type Return = ();
|
||||||
|
|
||||||
|
async fn handle(&mut self, _message: PlayerConnected, _: &mut xtra::Context<Self>) -> Self::Return {
|
||||||
|
warn!("todo: implement player connected")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Handler<PlayerDisconnected> for ProximityPlayer {
|
||||||
|
type Return = ();
|
||||||
|
|
||||||
|
async fn handle(&mut self, _message: PlayerDisconnected, _: &mut xtra::Context<Self>) -> Self::Return {
|
||||||
|
warn!("todo: implement player disconnected")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Handler<PlayerMoved> for ProximityPlayer {
|
||||||
|
type Return = ();
|
||||||
|
|
||||||
|
async fn handle(&mut self, _message: PlayerMoved, _: &mut xtra::Context<Self>) -> Self::Return {
|
||||||
|
warn!("todo: implement player moved")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Handler<ChangedStage> for ProximityPlayer {
|
||||||
|
type Return = ();
|
||||||
|
|
||||||
|
async fn handle(&mut self, _message: ChangedStage, _: &mut xtra::Context<Self>) -> Self::Return {
|
||||||
|
warn!("todo: implement changed stage")
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue