From 6708cc3e222d70447d8bdbef28a703bc2105ffa9 Mon Sep 17 00:00:00 2001 From: Aubrey Taylor Date: Tue, 17 Dec 2024 18:44:37 -0600 Subject: [PATCH] proximity server+ --- .vscode/settings.json | 3 + examples/extract.rs | 13 ++ game/StagePosList.byml | Bin 0 -> 39992 bytes game/WorldListFromDb.byml | Bin 0 -> 15364 bytes game/stages/BossRaidWorldHomeStage.byml | Bin 0 -> 336 bytes game/stages/CapWorldHomeStage.byml | Bin 0 -> 336 bytes game/stages/CapWorldHomeStage1.byml | Bin 0 -> 336 bytes game/stages/CapWorldHomeStage2.byml | Bin 0 -> 336 bytes game/stages/CityWorldHomeStage.byml | Bin 0 -> 336 bytes game/stages/ClashWorldHomeStage.byml | Bin 0 -> 336 bytes game/stages/CloudWorldHomeStage.byml | Bin 0 -> 340 bytes game/stages/CloudWorldHomeStage4.byml | Bin 0 -> 336 bytes game/stages/ForestWorldHomeStage.byml | Bin 0 -> 336 bytes game/stages/LakeWorldHomeStage.byml | Bin 0 -> 336 bytes game/stages/LavaWorldHomeStage.byml | Bin 0 -> 336 bytes game/stages/LavaWorldHomeStage1.byml | Bin 0 -> 336 bytes game/stages/LavaWorldHomeStage2.byml | Bin 0 -> 336 bytes game/stages/MoonWorldHomeStage.byml | Bin 0 -> 336 bytes game/stages/PeachWorldHomeStage.byml | Bin 0 -> 336 bytes game/stages/SandWorldHomeStage.byml | Bin 0 -> 336 bytes game/stages/SeaWorldHomeStage.byml | Bin 0 -> 336 bytes game/stages/SkyWorldHomeStage.byml | Bin 0 -> 336 bytes game/stages/SnowWorldHomeStage.byml | Bin 0 -> 336 bytes game/stages/Special1WorldHomeStage.byml | Bin 0 -> 336 bytes game/stages/Special2WorldHomeStage.byml | Bin 0 -> 336 bytes game/stages/WaterfallWorldHomeStage.byml | Bin 0 -> 336 bytes p12.nu | 1 + src/faker.rs | 63 +++++++++ src/game/mod.rs | 0 src/packet/mod.rs | 143 +++++++++++++++++++ src/packet/rw.rs | 114 +++++++++++++++ src/protocol.rs | 76 ++++++++++ src/server/mod.rs | 170 +++++++++++++++++++++++ src/server/packet.rs | 30 ++++ src/server/prox.rs | 91 ++++++++++++ todo.txt | 5 + 36 files changed, 709 insertions(+) create mode 100644 .vscode/settings.json create mode 100644 examples/extract.rs create mode 100644 game/StagePosList.byml create mode 100644 game/WorldListFromDb.byml create mode 100644 game/stages/BossRaidWorldHomeStage.byml create mode 100644 game/stages/CapWorldHomeStage.byml create mode 100644 game/stages/CapWorldHomeStage1.byml create mode 100644 game/stages/CapWorldHomeStage2.byml create mode 100644 game/stages/CityWorldHomeStage.byml create mode 100644 game/stages/ClashWorldHomeStage.byml create mode 100644 game/stages/CloudWorldHomeStage.byml create mode 100644 game/stages/CloudWorldHomeStage4.byml create mode 100644 game/stages/ForestWorldHomeStage.byml create mode 100644 game/stages/LakeWorldHomeStage.byml create mode 100644 game/stages/LavaWorldHomeStage.byml create mode 100644 game/stages/LavaWorldHomeStage1.byml create mode 100644 game/stages/LavaWorldHomeStage2.byml create mode 100644 game/stages/MoonWorldHomeStage.byml create mode 100644 game/stages/PeachWorldHomeStage.byml create mode 100644 game/stages/SandWorldHomeStage.byml create mode 100644 game/stages/SeaWorldHomeStage.byml create mode 100644 game/stages/SkyWorldHomeStage.byml create mode 100644 game/stages/SnowWorldHomeStage.byml create mode 100644 game/stages/Special1WorldHomeStage.byml create mode 100644 game/stages/Special2WorldHomeStage.byml create mode 100644 game/stages/WaterfallWorldHomeStage.byml create mode 100644 p12.nu create mode 100644 src/faker.rs create mode 100644 src/game/mod.rs create mode 100644 src/packet/mod.rs create mode 100644 src/packet/rw.rs create mode 100644 src/protocol.rs create mode 100644 src/server/mod.rs create mode 100644 src/server/packet.rs create mode 100644 src/server/prox.rs create mode 100644 todo.txt 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 0000000000000000000000000000000000000000..821c59b347d0b7525c076d2bb4e9c39a12af5ee2 GIT binary patch literal 39992 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 literal 0 HcmV?d00001 diff --git a/game/stages/BossRaidWorldHomeStage.byml b/game/stages/BossRaidWorldHomeStage.byml new file mode 100644 index 0000000000000000000000000000000000000000..9d541e7b9d90a412ce731a4384878b3d328dbe07 GIT binary patch literal 336 zcma!aVrCFvfB+vLb%>dPK?F$41F;qmTLN)FQGS+hVo6bE1w&Y7YB>@UNd~CyAW$z5 z99RJ)7=d&KkY)nXe?Wpf3=Ef$00TpVJy4i|&F$dkKl(S1f@lyQD88S8VZ9ra4OSx} z*>vmx(2+>m!OTBdPK?F$41F;qmTLN)FQGS+hVo6bE1w&Y7YB>@UNd~CyAW$z5 z99RJ)7=d&KkY)nXe?Wpf3=Ef$z!w%%d!R7GCl=HF6_E#BK{SXD6yJaUzjzrQ9W^TRAYynn=L|_y1SJT*8&f5ewAEC6t9-EnBsk_WUYJl|i0vUHZ c5DnvBeOhJ*VuQ>B(e?~RVE2L85YS)`0JCvlm;e9( literal 0 HcmV?d00001 diff --git a/game/stages/CapWorldHomeStage1.byml b/game/stages/CapWorldHomeStage1.byml new file mode 100644 index 0000000000000000000000000000000000000000..6671b6018a31faaf400e5c471f8280ccefdd522a GIT binary patch literal 336 zcma!aVrCFvfB+vLb%>dPK?F$41F;qmTLN)FQGS+hVo6bE1w&Y7YB>@UNd~CyAW$z5 z99RJ)7=d&KkY)nXe?Wpf3=Ef$z!w%%d!R7GCl=HF6_E#BK{SXD6yJaUzjzrQ9W^TRAYynn=L|_y1SJT*8&f5ewAEC6t9-EnBsk_WUYJl|i0vUHZ c5DnvBeOhJ*VuQ>B(e?~RVE2L85YS)`0JCvlm;e9( literal 0 HcmV?d00001 diff --git a/game/stages/CapWorldHomeStage2.byml b/game/stages/CapWorldHomeStage2.byml new file mode 100644 index 0000000000000000000000000000000000000000..6671b6018a31faaf400e5c471f8280ccefdd522a GIT binary patch literal 336 zcma!aVrCFvfB+vLb%>dPK?F$41F;qmTLN)FQGS+hVo6bE1w&Y7YB>@UNd~CyAW$z5 z99RJ)7=d&KkY)nXe?Wpf3=Ef$z!w%%d!R7GCl=HF6_E#BK{SXD6yJaUzjzrQ9W^TRAYynn=L|_y1SJT*8&f5ewAEC6t9-EnBsk_WUYJl|i0vUHZ c5DnvBeOhJ*VuQ>B(e?~RVE2L85YS)`0JCvlm;e9( literal 0 HcmV?d00001 diff --git a/game/stages/CityWorldHomeStage.byml b/game/stages/CityWorldHomeStage.byml new file mode 100644 index 0000000000000000000000000000000000000000..93c241ad3c767a9c516df54421ed682a060c8965 GIT binary patch literal 336 zcma!aVrCFvfB+vLb%>dPK?F$41F;qmTLN)FQGS+hVo6bE1w&Y7YB>@UNd~CyAW$z5 z99RJ)7=d&KkY)nXe?Wpf3=Ef$00TpVJy4i|&F$c3&&Ezm5Dnr3#rHEbSi3^mU^S~N zW*<8MbR?2?Fw;BE%mShgL1Gj0)7rj6U#@XKLKTSBU{AozhVnDoMj(YC)j)H1Y@EPq V2V#TF1JPZZtibLAu_2(r9ssOxSd#z% literal 0 HcmV?d00001 diff --git a/game/stages/ClashWorldHomeStage.byml b/game/stages/ClashWorldHomeStage.byml new file mode 100644 index 0000000000000000000000000000000000000000..6de76140ed0233edadc275d5dbbe19d9113d72f7 GIT binary patch literal 336 zcma!aVrCFvfB+vLb%>dPK?F$41F;qmTLN)FQGS+hVo6bE1w&Y7YB>@UNd~CyAW$z5 z99RJ)7=d&KkY)nXe?Wpf3=Ef$00TpVJy4i|&F!EuGsCnaAR5F6itlG|NOy&@!D<*x z<{di#bR?2?FtcuJnFT~0g2X1~r?q{DzFgyegenlL!Jd$rlP65r3)2HMcgMYZadsd+ S$UG4Jve^mZJ`e{C8teg3gj#3- literal 0 HcmV?d00001 diff --git a/game/stages/CloudWorldHomeStage.byml b/game/stages/CloudWorldHomeStage.byml new file mode 100644 index 0000000000000000000000000000000000000000..8a4710dcf8fc120a81e1c2be96fba0fc4a5fe66f GIT binary patch literal 340 zcma!aVrCFvfPerXb%>dPK?F$41F;qmTLN)FQGS+hVo6bE1w&Y7YB>@UNd~A61P%iA z1HpkDAi)Tv4*+QdfmbfJ#sRHnGdgPVV%7)3P5$Awz@x0UlH{8GvFS zHEeDNH#5Gq-U*^1dK&gKC@>s_vcYN?9Q=+R!0)Dp@-y0-fO;B0`hf1*ajxj!UJx7P Q9uR%lFUJla02?MdPK?F$41F;qmTLN)FQGS+hVo6bE1w&Y7YB>@UNd~CyAW$z5 z99RJ)7=d&KkY)nXe?Wpf3=Ef$00TpVJy4i|&F$c3#<$ixK{SXD6yMLFz;G1G2CHFk z@H=_{=tv~(U}l|podrZ4g2X0vdD+RG{%>0LBUFJ{4fX`gY$!jYy$Pfcq#9`Mj&nu- U_JY_T^FZ`rzZ^RdjQ|by0LKDe0{{R3 literal 0 HcmV?d00001 diff --git a/game/stages/ForestWorldHomeStage.byml b/game/stages/ForestWorldHomeStage.byml new file mode 100644 index 0000000000000000000000000000000000000000..f4be3868363f4436b53daffab69daf03c0b328a3 GIT binary patch literal 336 zcma!aVrCFvfB+vLb%>dPK?F$41F;qmTLN)FQGS+hVo6bE1w&Y7YB>@UNd~CyAW$z5 z99RJ)7=d&KkY)nXe?Wpf3=Ef$00TpVJy4i|&F$dk-}ZA{Ks1OC6yN_(|K?FB8?1)m z#I$1vfR04c4rV$wN?1VDAxLauFM_7+c=oDcKSC9V)nHG+%!XDyD_f94kZPd0JIYfV U_kq|T^FXxrh68pW8UY&Y0TX;(fdBvi literal 0 HcmV?d00001 diff --git a/game/stages/LakeWorldHomeStage.byml b/game/stages/LakeWorldHomeStage.byml new file mode 100644 index 0000000000000000000000000000000000000000..06efb5b3856dcd0e3d540fcd5a8cdc529911252b GIT binary patch literal 336 zcma!aVrCFvfB+vLb%>dPK?F$41F;qmTLN)FQGS+hVo6bE1w&Y7YB>@UNd~CyAW$z5 z99RJ)7=d&KkY)nXe?Wpf3=Ef$00TpVJy4i|&F!EuLj{u?m<9;^%TJiGdy&xI^8tef~$6I9p literal 0 HcmV?d00001 diff --git a/game/stages/LavaWorldHomeStage.byml b/game/stages/LavaWorldHomeStage.byml new file mode 100644 index 0000000000000000000000000000000000000000..d38207b0692490190f6b878a044e5282ff25acff GIT binary patch literal 336 zcma!aVrCFvfB+vLb%>dPK?F$41F;qmTLN)FQGS+hVo6bE1w&Y7YB>@UNd~CyAW$z5 z99RJ)7=d&KkY)nXe?Wpf3=Ef$K=~PMW1uh)H0)H85n@#oB#Yj?hKW)2h$2E z#}5D++3purxSnJ^VVb9TJgb966=hKe5Ai2FbsFlev`0NRgT&Hw-a literal 0 HcmV?d00001 diff --git a/game/stages/LavaWorldHomeStage1.byml b/game/stages/LavaWorldHomeStage1.byml new file mode 100644 index 0000000000000000000000000000000000000000..d38207b0692490190f6b878a044e5282ff25acff GIT binary patch literal 336 zcma!aVrCFvfB+vLb%>dPK?F$41F;qmTLN)FQGS+hVo6bE1w&Y7YB>@UNd~CyAW$z5 z99RJ)7=d&KkY)nXe?Wpf3=Ef$K=~PMW1uh)H0)H85n@#oB#Yj?hKW)2h$2E z#}5D++3purxSnJ^VVb9TJgb966=hKe5Ai2FbsFlev`0NRgT&Hw-a literal 0 HcmV?d00001 diff --git a/game/stages/LavaWorldHomeStage2.byml b/game/stages/LavaWorldHomeStage2.byml new file mode 100644 index 0000000000000000000000000000000000000000..d38207b0692490190f6b878a044e5282ff25acff GIT binary patch literal 336 zcma!aVrCFvfB+vLb%>dPK?F$41F;qmTLN)FQGS+hVo6bE1w&Y7YB>@UNd~CyAW$z5 z99RJ)7=d&KkY)nXe?Wpf3=Ef$K=~PMW1uh)H0)H85n@#oB#Yj?hKW)2h$2E z#}5D++3purxSnJ^VVb9TJgb966=hKe5Ai2FbsFlev`0NRgT&Hw-a literal 0 HcmV?d00001 diff --git a/game/stages/MoonWorldHomeStage.byml b/game/stages/MoonWorldHomeStage.byml new file mode 100644 index 0000000000000000000000000000000000000000..6613a2efbc35b4ae3b88d7f3d74162b32f05b9bf GIT binary patch literal 336 zcma!aVrCFvfB+vLb%>dPK?F$41F;qmTLN)FQGS+hVo6bE1w&Y7YB>@UNd~CyAW$z5 z99RJ)7=d&KkY)nXe?Wpf3=Ef$00TpVJy4i|&F!EuLxQ|3m<9dPK?F$41F;qmTLN)FQGS+hVo6bE1w&Y7YB>@UNd~CyAW$z5 z99RJ)7=d&KkY)nXe?Wpf3=Ef$00TpVJy4i|&F!EuBZHVLhz9Y2;`{&Ca~_4V!D<*3 zypJ6KIuc1cn0fIuw*^EUg2X1~r?q{DzFgyegenlL!Jd$rT-O)d!t?;m-El&H+FlSJ RWFCliUCU?(q7k6M9smzKSl<8u literal 0 HcmV?d00001 diff --git a/game/stages/SandWorldHomeStage.byml b/game/stages/SandWorldHomeStage.byml new file mode 100644 index 0000000000000000000000000000000000000000..23503899dec5008beaa21bec12d314fe0f82c77c GIT binary patch literal 336 zcma!aVrCFvfB+vLb%>dPK?F$41F;qmTLN)FQGS+hVo6bE1w&Y7YB>@UNd~CyAW$z5 z99RJ)7=d&KkY)nXe?Wpf3=Ef$K=~PMW1uh)H0)=nFgXUMfjpr2W)_F7N5E1H4fY_q zGC1Sd0iYw1w1b(`b{{u~s6&w0#J=b~+p!|OaX&&8h}B?^%}kgYpgG&mp6S^KqM>39 ZH#2zmg4iJQK=kQfa$xs?*bvZQ4*){WV)+07 literal 0 HcmV?d00001 diff --git a/game/stages/SeaWorldHomeStage.byml b/game/stages/SeaWorldHomeStage.byml new file mode 100644 index 0000000000000000000000000000000000000000..579a87f6fb44ce152f7d4aa7bcbabe4b76374009 GIT binary patch literal 336 zcma!aVrCFvfB+vLb%>dPK?F$41F;qmTLN)FQGS+hVo6bE1w&Y7YB>@UNd~CyAW$z5 z99RJ)7=d&KkY)nXe?Wpf3=Ef$00TpVJy4i|&F!G^&W#tXKs1OC6yMMAqwg4$4OX+T zGUwOdPK?F$41F;qmTLN)FQGS+hVo6bE1w&Y7YB>@UNd~CyAW$z5 z99RJ)7=d&KkY)nXe?Wpf3=Ef$00TpVJy4i|&F$dkfBf8TAR5F6itlG==sX5xgViv^ ziJv$CbR?2?FjIVcpgBYxg2X0f%;vYB(L%S=AnpTkz@Wh%0B~MdbN~PV literal 0 HcmV?d00001 diff --git a/game/stages/SnowWorldHomeStage.byml b/game/stages/SnowWorldHomeStage.byml new file mode 100644 index 0000000000000000000000000000000000000000..b1794f7e63cec27b1b2efe72956e750fae8d7c67 GIT binary patch literal 336 zcma!aVrCFvfB+vLb%>dPK?F$41F;qmTLN)FQGS+hVo6bE1w&Y7YB>@UNd~CyAW$z5 z99RJ)7=d&KkY)nXe?Wpf3=Ef$00TpVJy4i|&F$dk-~SVifM^gOD88RT!P^ze2CJzO za6EPZ=tv~(VCI~a`4$j$2ojsvCu@V9JJp)@BUFJ{4fX`gY}mIfXfH@1NHx&h9S^nb W>_BXgc_7+)Wjok?AT|Uv*aHB@Y+dsJ literal 0 HcmV?d00001 diff --git a/game/stages/Special1WorldHomeStage.byml b/game/stages/Special1WorldHomeStage.byml new file mode 100644 index 0000000000000000000000000000000000000000..e3b94d12582af03463166c013ab6a52269dfa235 GIT binary patch literal 336 zcma!aVrCFvfB+vLb%>dPK?F$41F;qmTLN)FQGS+hVo6bE1w&Y7YB>@UNd~CyAW$z5 z99RJ)7=d&KkY)nXe?Wpf3=Ef$00TpVJy4i|&F$dkfBDB;K{SXD6yHCI!Q&8=4OYXz z!E*cn(2+>m!OV-Nxh){-5F|FS8=cNOj?8G-k5C0-HP{m{vq5QpgdIpBNHx&h9oI^3 W?EdPK?F$41F;qmTLN)FQGS+hVo6bE1w&Y7YB>@UNd~CyAW$z5 z99RJ)7=d&KkY)nXe?Wpf3=Ef$00TpVJy4i|&F$c3)(b61K{SXD6yN`!`NlCQ8>}Yz zv&-=VKu02J2Q$UDUo?lPLy*|SPGp_h&gb8-AE64wYOp6@X2YVHH}`@Rf>Z;|-G1<^ U0oV+Xc_1-v*=RcujQ|by01UKXqW}N^ literal 0 HcmV?d00001 diff --git a/game/stages/WaterfallWorldHomeStage.byml b/game/stages/WaterfallWorldHomeStage.byml new file mode 100644 index 0000000000000000000000000000000000000000..37686c6cf4a8a058c924acc6df35282984120c8b GIT binary patch literal 336 zcma!aVrCFvfB+vLb%>dPK?F$41F;qmTLN)FQGS+hVo6bE1w&Y7YB>@UNd~CyAW$z5 z99RJ)7=d&KkY)nXe?Wpf3=Ef$z!w%%d!R4_klugo*ajC64dVxu9C87%85kPCYQmgs zjvW9x5=lFlX}H7H0-_ERhZ5F2D3i0, +} + +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) +