Compare commits

...
Sign in to create a new pull request.

23 commits

Author SHA1 Message Date
f66ecf4d96 fix not disconnecting when backend kicks player
All checks were successful
Build and Test / splatoon (push) Successful in 4m4s
Build and Test / wii-u-chat (push) Successful in 4m4s
Build and Test / super-mario-maker (push) Successful in 4m55s
Build and Test / fast-racing-neo (push) Successful in 5m12s
Build and Test / splatoon-testfire (push) Successful in 5m55s
Build and Test / puyopuyo (push) Successful in 5m55s
Build and Test / minecraft-wiiu (push) Successful in 7m13s
Build and Test / friends (push) Successful in 7m34s
Build and Test / wii-sports-club (push) Successful in 8m7s
Build and Test / mario-tennis (push) Successful in 8m34s
2026-05-10 00:21:12 +02:00
9ebf88d8eb add handeling for empty bounds string
All checks were successful
Build and Test / minecraft-wiiu (push) Successful in 2m8s
Build and Test / wii-u-chat (push) Successful in 2m18s
Build and Test / super-mario-maker (push) Successful in 3m28s
Build and Test / splatoon-testfire (push) Successful in 4m45s
Build and Test / mario-tennis (push) Successful in 5m0s
Build and Test / friends (push) Successful in 5m12s
Build and Test / splatoon (push) Successful in 5m51s
Build and Test / puyopuyo (push) Successful in 5m59s
Build and Test / fast-racing-neo (push) Successful in 6m10s
Build and Test / wii-sports-club (push) Successful in 1m15s
2026-05-05 21:55:55 +02:00
f678bcc929 Merge branch 'v0' of ssh://ssh.spbr.net:51122/spacebar/rust-nex into v0
All checks were successful
Build and Test / fast-racing-neo (push) Successful in 2m13s
Build and Test / splatoon (push) Successful in 2m38s
Build and Test / wii-u-chat (push) Successful in 2m59s
Build and Test / wii-sports-club (push) Successful in 3m17s
Build and Test / mario-tennis (push) Successful in 3m36s
Build and Test / friends (push) Successful in 3m52s
Build and Test / splatoon-testfire (push) Successful in 4m16s
Build and Test / super-mario-maker (push) Successful in 5m42s
Build and Test / puyopuyo (push) Successful in 6m7s
Build and Test / minecraft-wiiu (push) Successful in 6m26s
2026-05-05 18:03:20 +02:00
e8f5ff3c24 fix stringset 2026-05-05 18:03:00 +02:00
d63fe663de fix attribs
All checks were successful
Build and Test / wii-u-chat (push) Successful in 2m24s
Build and Test / splatoon-testfire (push) Successful in 2m34s
Build and Test / friends (push) Successful in 3m6s
Build and Test / minecraft-wiiu (push) Successful in 3m30s
Build and Test / mario-tennis (push) Successful in 4m13s
Build and Test / super-mario-maker (push) Successful in 5m3s
Build and Test / wii-sports-club (push) Successful in 5m20s
Build and Test / fast-racing-neo (push) Successful in 5m44s
Build and Test / puyopuyo (push) Successful in 6m5s
Build and Test / splatoon (push) Successful in 6m20s
2026-05-05 14:48:25 +02:00
de20212d1e redo macros
All checks were successful
Build and Test / splatoon-testfire (push) Successful in 4m29s
Build and Test / puyopuyo (push) Successful in 5m3s
Build and Test / splatoon (push) Successful in 5m40s
Build and Test / wii-sports-club (push) Successful in 5m46s
Build and Test / fast-racing-neo (push) Successful in 6m30s
Build and Test / wii-u-chat (push) Successful in 7m18s
Build and Test / friends (push) Successful in 8m19s
Build and Test / super-mario-maker (push) Successful in 13m10s
Build and Test / mario-tennis (push) Successful in 13m34s
Build and Test / minecraft-wiiu (push) Successful in 14m8s
2026-05-04 16:06:25 +02:00
d10e0cb596 Add prelimiary support for Wii Sports Club and MTUS
All checks were successful
Build and Test / splatoon-testfire (push) Successful in 2m21s
Build and Test / super-mario-maker (push) Successful in 2m48s
Build and Test / minecraft-wiiu (push) Successful in 3m22s
Build and Test / fast-racing-neo (push) Successful in 4m8s
Build and Test / wii-u-chat (push) Successful in 4m36s
Build and Test / puyopuyo (push) Successful in 5m9s
Build and Test / friends (push) Successful in 5m27s
Build and Test / mario-tennis (push) Successful in 6m13s
Build and Test / splatoon (push) Successful in 6m13s
Build and Test / wii-sports-club (push) Successful in 6m51s
2026-05-03 23:56:10 +02:00
d39758d748 disable version mismatch in editions without header
All checks were successful
Build and Test / super-mario-maker (push) Successful in 2m17s
Build and Test / wii-u-chat (push) Successful in 3m1s
Build and Test / minecraft-wiiu (push) Successful in 3m17s
Build and Test / friends (push) Successful in 3m42s
Build and Test / splatoon-testfire (push) Successful in 3m59s
Build and Test / fast-racing-neo (push) Successful in 4m30s
Build and Test / puyopuyo (push) Successful in 4m59s
Build and Test / splatoon (push) Successful in 5m11s
2026-05-02 22:55:12 +02:00
97d0a1f553 Initial support for Puyo Puyo Tetris
All checks were successful
Build and Test / super-mario-maker (push) Successful in 2m9s
Build and Test / splatoon (push) Successful in 2m29s
Build and Test / fast-racing-neo (push) Successful in 3m1s
Build and Test / splatoon-testfire (push) Successful in 3m27s
Build and Test / wii-u-chat (push) Successful in 4m0s
Build and Test / puyopuyo (push) Successful in 4m43s
Build and Test / minecraft-wiiu (push) Successful in 4m50s
Build and Test / friends (push) Successful in 5m12s
2026-05-02 22:07:26 +02:00
9f2ab87f6a Update CI
All checks were successful
Build and Test / super-mario-maker (push) Successful in 2m9s
Build and Test / friends (push) Successful in 2m23s
Build and Test / splatoon-testfire (push) Successful in 2m57s
Build and Test / splatoon (push) Successful in 3m18s
Build and Test / fast-racing-neo (push) Successful in 3m52s
Build and Test / wii-u-chat (push) Successful in 4m20s
Build and Test / minecraft-wiiu (push) Successful in 5m12s
2026-05-02 17:14:50 +02:00
0df281cd61 Add support for NEX v3.10.22
All checks were successful
Build and Test / splatoon-testfire (push) Successful in 1m39s
Build and Test / splatoon (push) Successful in 1m59s
Build and Test / wii-u-chat (push) Successful in 2m26s
Build and Test / fast-racing-neo (push) Successful in 3m2s
Build and Test / friends (push) Successful in 7m24s
Build and Test / super-mario-maker (push) Successful in 8m54s
2026-05-02 13:19:06 +02:00
0f89601f2e Start work on Minecraft Wii U
All checks were successful
Build and Test / splatoon (push) Successful in 1m43s
Build and Test / wii-u-chat (push) Successful in 2m7s
Build and Test / splatoon-testfire (push) Successful in 2m32s
Build and Test / fast-racing-neo (push) Successful in 3m5s
Build and Test / super-mario-maker (push) Successful in 4m12s
Build and Test / friends (push) Successful in 4m36s
2026-05-02 12:39:25 +02:00
28911c801a Clarify repo state
All checks were successful
Build and Test / super-mario-maker (push) Successful in 2m19s
Build and Test / wii-u-chat (push) Successful in 3m4s
Build and Test / splatoon-testfire (push) Successful in 3m53s
Build and Test / splatoon (push) Successful in 4m30s
Build and Test / friends (push) Successful in 5m0s
Build and Test / fast-racing-neo (push) Successful in 5m48s
2026-04-29 09:59:18 +02:00
96f0e6c652 Add new games to CI
All checks were successful
Build and Test / super-mario-maker (push) Successful in 2m0s
Build and Test / splatoon (push) Successful in 2m31s
Build and Test / wii-u-chat (push) Successful in 3m11s
Build and Test / friends (push) Successful in 3m27s
Build and Test / fast-racing-neo (push) Successful in 4m8s
Build and Test / splatoon-testfire (push) Successful in 4m30s
2026-04-29 07:14:16 +02:00
43e526c834 Update CI
All checks were successful
Build and Test / friends (push) Successful in 1m9s
Build and Test / super-mario-maker (push) Successful in 2m10s
Build and Test / wii-u-chat (push) Successful in 2m41s
Build and Test / splatoon (push) Successful in 3m8s
2026-04-29 01:23:18 +02:00
b486e5e590 add Fast Racing NEO edition
All checks were successful
Build and Test / splatoon (push) Successful in 3m25s
Build and Test / wii-u-chat (push) Successful in 3m25s
Build and Test / friends (push) Successful in 4m9s
Build and Test / super-mario-maker (push) Successful in 9m18s
2026-04-29 00:08:00 +02:00
7565501c19 make attrib matching game specific
Some checks failed
Build and Test / wii-u-chat (push) Successful in 4m18s
Build and Test / splatoon (push) Successful in 4m17s
Build and Test / friends (push) Successful in 4m19s
Build and Test / super-mario-maker (push) Has been cancelled
2026-04-29 00:03:53 +02:00
5a69d2acf5 add singular values for bounds values
Some checks failed
Build and Test / splatoon (push) Successful in 4m35s
Build and Test / friends (push) Successful in 4m35s
Build and Test / wii-u-chat (push) Successful in 4m38s
Build and Test / super-mario-maker (push) Has been cancelled
2026-04-28 23:54:34 +02:00
e06ea4c0cb add logging
Some checks failed
Build and Test / friends (push) Successful in 4m42s
Build and Test / wii-u-chat (push) Successful in 4m48s
Build and Test / splatoon (push) Successful in 4m50s
Build and Test / super-mario-maker (push) Has been cancelled
2026-04-28 23:44:43 +02:00
9d3a25fd95 remove one test
All checks were successful
Build and Test / splatoon (push) Successful in 4m18s
Build and Test / wii-u-chat (push) Successful in 4m19s
Build and Test / friends (push) Successful in 4m38s
Build and Test / super-mario-maker (push) Successful in 9m20s
2026-04-28 23:33:12 +02:00
e6ec245b87 add auto_matchmake_with_search_criteria_postpone
Some checks failed
Build and Test / splatoon (push) Has been cancelled
Build and Test / friends (push) Has been cancelled
Build and Test / super-mario-maker (push) Has been cancelled
Build and Test / wii-u-chat (push) Has been cancelled
2026-04-28 23:31:29 +02:00
5972833136 add utility protocol
Some checks failed
Build and Test / friends (push) Successful in 4m54s
Build and Test / super-mario-maker (push) Failing after 14m33s
Build and Test / splatoon (push) Failing after 5m11s
Build and Test / wii-u-chat (push) Successful in 4m24s
2026-04-28 22:56:36 +02:00
28670c09ef torture, get outta here bucko
Some checks failed
Build and Test / wii-u-chat (push) Successful in 8m23s
Build and Test / splatoon (push) Failing after 11m54s
Build and Test / super-mario-maker (push) Failing after 21m10s
Build and Test / friends (push) Successful in 10m17s
2026-04-28 21:53:40 +02:00
27 changed files with 1644 additions and 1128 deletions

View file

@ -1,20 +1,15 @@
#!/usr/bin/env bash #!/usr/bin/env bash
export EDITION=$1 export EDITION=$1
export BA="--network=host --build-arg EDITION=$1 --build-arg DATABASE_URL="$DATABASE_URL""
source /etc/environment source /etc/environment
: "${RNEX_CONTAINER_PLATFORM:=podman}" : "${RNEX_CONTAINER_PLATFORM:=podman}"
export BUILDKIT_PROGRESS=plain
# $RNEX_CONTAINER_PLATFORM build $BA -t "$CI_REGISTRY_IMAGE/$EDITION/dev-container:latest" --target=dev-container . for TARGET in node-holder proxy-secure proxy-insecure backend-auth backend-secure; do
# $RNEX_CONTAINER_PLATFORM push "$CI_REGISTRY_IMAGE/$EDITION/dev-container:latest" $RNEX_CONTAINER_PLATFORM build \
$RNEX_CONTAINER_PLATFORM build $BA -t "$CI_REGISTRY_IMAGE/$EDITION/node-holder:$CI_COMMIT_SHORT_SHA" --target=node-holder . --network=host \
$RNEX_CONTAINER_PLATFORM build $BA -t "$CI_REGISTRY_IMAGE/$EDITION/proxy-secure:$CI_COMMIT_SHORT_SHA" --target=proxy-secure . --build-arg EDITION="$EDITION" \
$RNEX_CONTAINER_PLATFORM build $BA -t "$CI_REGISTRY_IMAGE/$EDITION/proxy-insecure:$CI_COMMIT_SHORT_SHA" --target=proxy-insecure . --build-arg DATABASE_URL="$DATABASE_URL" \
$RNEX_CONTAINER_PLATFORM build $BA -t "$CI_REGISTRY_IMAGE/$EDITION/backend-auth:$CI_COMMIT_SHORT_SHA" --target=backend-auth . -t "$CI_REGISTRY_IMAGE/$EDITION/$TARGET:$CI_COMMIT_SHORT_SHA" \
$RNEX_CONTAINER_PLATFORM build $BA -t "$CI_REGISTRY_IMAGE/$EDITION/backend-secure:$CI_COMMIT_SHORT_SHA" --target=backend-secure . --target="$TARGET" .
$RNEX_CONTAINER_PLATFORM push "$CI_REGISTRY_IMAGE/$EDITION/node-holder:$CI_COMMIT_SHORT_SHA"
$RNEX_CONTAINER_PLATFORM push "$CI_REGISTRY_IMAGE/$EDITION/proxy-secure:$CI_COMMIT_SHORT_SHA" $RNEX_CONTAINER_PLATFORM push "$CI_REGISTRY_IMAGE/$EDITION/$TARGET:$CI_COMMIT_SHORT_SHA"
$RNEX_CONTAINER_PLATFORM push "$CI_REGISTRY_IMAGE/$EDITION/proxy-insecure:$CI_COMMIT_SHORT_SHA" done
$RNEX_CONTAINER_PLATFORM push "$CI_REGISTRY_IMAGE/$EDITION/backend-auth:$CI_COMMIT_SHORT_SHA"
$RNEX_CONTAINER_PLATFORM push "$CI_REGISTRY_IMAGE/$EDITION/backend-secure:$CI_COMMIT_SHORT_SHA"

View file

@ -11,6 +11,186 @@ env:
SHORT_SHA: ${{ github.sha }} SHORT_SHA: ${{ github.sha }}
jobs: jobs:
mario-tennis:
runs-on: debian-trixie
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
submodules: recursive
- name: Cache container storage
uses: actions/cache@v4
with:
path: |
/var/lib/containers/storage
/run/containers/storage
~/.local/share/containers/storage
key: image-cache
- name: Login to registry
run: docker login -u ${{ secrets.PACKAGE_USER }} -p ${{ secrets.PACKAGE_PWD }} git.spbr.net
- name: Set short SHA
run: echo "SHORT_SHA=${GITHUB_SHA::6}" >> $GITHUB_ENV
- name: Build MTUS tetris edition
env:
CI_REGISTRY_IMAGE: git.spbr.net/spacebar/rust-nex
CI_COMMIT_SHORT_SHA: ${{ env.SHORT_SHA }}
run: ./.ci-scripts/make-edition.sh mario-tennis
wii-sports-club:
runs-on: debian-trixie
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
submodules: recursive
- name: Cache container storage
uses: actions/cache@v4
with:
path: |
/var/lib/containers/storage
/run/containers/storage
~/.local/share/containers/storage
key: image-cache
- name: Login to registry
run: docker login -u ${{ secrets.PACKAGE_USER }} -p ${{ secrets.PACKAGE_PWD }} git.spbr.net
- name: Set short SHA
run: echo "SHORT_SHA=${GITHUB_SHA::6}" >> $GITHUB_ENV
- name: Build Wii Sports Club edition
env:
CI_REGISTRY_IMAGE: git.spbr.net/spacebar/rust-nex
CI_COMMIT_SHORT_SHA: ${{ env.SHORT_SHA }}
run: ./.ci-scripts/make-edition.sh wii-sports-club
puyopuyo:
runs-on: debian-trixie
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
submodules: recursive
- name: Cache container storage
uses: actions/cache@v4
with:
path: |
/var/lib/containers/storage
/run/containers/storage
~/.local/share/containers/storage
key: image-cache
- name: Login to registry
run: docker login -u ${{ secrets.PACKAGE_USER }} -p ${{ secrets.PACKAGE_PWD }} git.spbr.net
- name: Set short SHA
run: echo "SHORT_SHA=${GITHUB_SHA::6}" >> $GITHUB_ENV
- name: Build puyo puyo tetris edition
env:
CI_REGISTRY_IMAGE: git.spbr.net/spacebar/rust-nex
CI_COMMIT_SHORT_SHA: ${{ env.SHORT_SHA }}
run: ./.ci-scripts/make-edition.sh puyopuyo
minecraft-wiiu:
runs-on: debian-trixie
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
submodules: recursive
- name: Cache container storage
uses: actions/cache@v4
with:
path: |
/var/lib/containers/storage
/run/containers/storage
~/.local/share/containers/storage
key: image-cache
- name: Login to registry
run: docker login -u ${{ secrets.PACKAGE_USER }} -p ${{ secrets.PACKAGE_PWD }} git.spbr.net
- name: Set short SHA
run: echo "SHORT_SHA=${GITHUB_SHA::6}" >> $GITHUB_ENV
- name: Build Minecraft Wii U edition
env:
CI_REGISTRY_IMAGE: git.spbr.net/spacebar/rust-nex
CI_COMMIT_SHORT_SHA: ${{ env.SHORT_SHA }}
run: ./.ci-scripts/make-edition.sh minecraft-wiiu
splatoon-testfire:
runs-on: debian-trixie
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
submodules: recursive
- name: Cache container storage
uses: actions/cache@v4
with:
path: |
/var/lib/containers/storage
/run/containers/storage
~/.local/share/containers/storage
key: image-cache
- name: Login to registry
run: docker login -u ${{ secrets.PACKAGE_USER }} -p ${{ secrets.PACKAGE_PWD }} git.spbr.net
- name: Set short SHA
run: echo "SHORT_SHA=${GITHUB_SHA::6}" >> $GITHUB_ENV
- name: Build Splatoon Testfire edition
env:
CI_REGISTRY_IMAGE: git.spbr.net/spacebar/rust-nex
CI_COMMIT_SHORT_SHA: ${{ env.SHORT_SHA }}
run: ./.ci-scripts/make-edition.sh splatoon-testfire
fast-racing-neo:
runs-on: debian-trixie
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
submodules: recursive
- name: Cache container storage
uses: actions/cache@v4
with:
path: |
/var/lib/containers/storage
/run/containers/storage
~/.local/share/containers/storage
key: image-cache
- name: Login to registry
run: docker login -u ${{ secrets.PACKAGE_USER }} -p ${{ secrets.PACKAGE_PWD }} git.spbr.net
- name: Set short SHA
run: echo "SHORT_SHA=${GITHUB_SHA::6}" >> $GITHUB_ENV
- name: Build Fast Racing NEO edition
env:
CI_REGISTRY_IMAGE: git.spbr.net/spacebar/rust-nex
CI_COMMIT_SHORT_SHA: ${{ env.SHORT_SHA }}
run: ./.ci-scripts/make-edition.sh fast-racing-neo
wii-u-chat: wii-u-chat:
runs-on: debian-trixie runs-on: debian-trixie

531
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -1,43 +1,57 @@
FROM rust:alpine AS build-container # syntax=docker/dockerfile:1
FROM rust:alpine AS chef
RUN apk add --no-cache protobuf-dev git musl-dev lld openssl-dev openssl-libs-static yq bash RUN apk add --no-cache musl-dev lld g++ make
RUN cargo install cargo-chef
FROM build-container AS builder
WORKDIR /app WORKDIR /app
FROM chef AS planner
COPY . . COPY . .
RUN cargo chef prepare --recipe-path recipe.json
FROM chef AS builder
RUN apk add --no-cache protobuf-dev git openssl-dev openssl-libs-static bash yq
COPY --from=planner /app/recipe.json recipe.json
ARG EDITION ARG EDITION
ARG DATABASE_URL ARG DATABASE_URL
RUN git submodule update --init --recursive RUN --mount=type=cache,target=/usr/local/cargo/registry \
--mount=type=cache,target=/app/target \
cargo chef cook --release --recipe-path recipe.json --target x86_64-unknown-linux-musl && \
cargo chef cook --tests --target x86_64-unknown-linux-musl --recipe-path recipe.json
COPY . .
RUN --mount=type=cache,target=/usr/local/cargo/registry \
--mount=type=cache,target=/app/target \
./test-edition.sh && ./build-edition.sh && \
mkdir -p /app/dist && \
cp /app/target/x86_64-unknown-linux-musl/release/edge_node_holder_server /app/dist/ && \
cp /app/target/x86_64-unknown-linux-musl/release/proxy_insecure /app/dist/ && \
cp /app/target/x86_64-unknown-linux-musl/release/proxy_secure /app/dist/ && \
cp /app/target/x86_64-unknown-linux-musl/release/backend_server_insecure /app/dist/ && \
cp /app/target/x86_64-unknown-linux-musl/release/backend_server_secure /app/dist/
RUN ./test-edition.sh
RUN ./build-edition.sh
FROM scratch AS node-holder FROM scratch AS node-holder
COPY --from=builder /app/target/x86_64-unknown-linux-musl/release/edge_node_holder_server /edge_node_holder_server COPY --from=builder /app/dist/edge_node_holder_server /edge_node_holder_server
ENTRYPOINT ["/edge_node_holder_server"] ENTRYPOINT ["/edge_node_holder_server"]
FROM scratch AS proxy-insecure FROM scratch AS proxy-insecure
COPY --from=builder /app/target/x86_64-unknown-linux-musl/release/proxy_insecure /proxy_insecure COPY --from=builder /app/dist/proxy_insecure /proxy_insecure
ENTRYPOINT ["/proxy_insecure"] ENTRYPOINT ["/proxy_insecure"]
FROM scratch AS proxy-secure FROM scratch AS proxy-secure
COPY --from=builder /app/target/x86_64-unknown-linux-musl/release/proxy_secure /proxy_secure COPY --from=builder /app/dist/proxy_secure /proxy_secure
ENTRYPOINT ["/proxy_secure"] ENTRYPOINT ["/proxy_secure"]
FROM scratch AS backend-auth FROM scratch AS backend-auth
COPY --from=builder /app/target/x86_64-unknown-linux-musl/release/backend_server_insecure /backend_server_insecure COPY --from=builder /app/dist/backend_server_insecure /backend_server_insecure
ENTRYPOINT ["/backend_server_insecure"] ENTRYPOINT ["/backend_server_insecure"]
FROM scratch AS backend-secure FROM scratch AS backend-secure
COPY --from=builder /app/target/x86_64-unknown-linux-musl/release/backend_server_secure /backend_server_secure COPY --from=builder /app/dist/backend_server_secure /backend_server_secure
ENTRYPOINT ["/backend_server_secure"] ENTRYPOINT ["/backend_server_secure"]
# make sure the final output container is the dev container so that we can use it from the devcontainer.json FROM chef AS dev-container
FROM build-container AS dev-container RUN apk add --no-cache openjdk21-jdk gcompat git bash protobuf-dev
RUN apk add openjdk21-jdk gcompat COPY --from=builder /app/dist/* /usr/local/bin/

View file

@ -1,20 +1,16 @@
# Splatoon NEX Server in Rust # Rust NEX monorepo
This repo contains the code for all game servers using RNEX.
## Credits: ## Credits:
- Pretendo team for the rest of the Servers and Reverse engineering efforts - Pretendo team for their reverse engineering efforts
- Kinnay for his huge work on reversing nex servers and documentation(https://github.com/Kinnay/NintendoClients/) - Kinnay for his huge work on reversing nex servers and documentation(https://github.com/Kinnay/NintendoClients/)
- Splatfestival testing team for helping us test our messes of code - Splatfestival testing team for helping us test our messes of code
- The SPFN team(Bloxer, Ceantix and RusticMaple) - The SPFN team(RusticMaple, BloxerHD, Ceantix, RedBinder0526)
This nex implementation was not created and is not intended to compete with pretendo, This NEX implementation was not created to rival Pretendo, we don't want any bad blood between anyone.
we wholeheartedly support pretendo.
This project would never have been possible without their reverse engineering efforts. This project would never have been possible without their reverse engineering efforts.
As such if you want to respect the Authors wishes(Maple(Me) is pretty much the sole author of Rust-Nex), do not As such if you want to respect the Authors wishes, do not use it if you mean any harm to Pretendo. (harm falls under e.g. using this software while also sabotaging pretendo) If you do show intent to harm them you will be blocked from ever contributing and will be refused support.
use it if you mean any harm to pretendo. If you do show intent to harm them you will be blocked from ever contributing
and will be refused support, as such please also refrain from asking for support if you have been blocked by pretendo.
We felt like this needed to be said as there are far too many Pretendo copycats who blatantly copy their code and use their reversal efforts with no credits in sight in an attempt to harm them for some grudge or stupid reason.
I felt like this needed to be said as there are far too many pretendo copycats who blatantly copy their code and use We feel that by working together and not against each other we can reach a better and healthier future for the community, health of developers and numerous more reasons.
their reversal efforts with no credits in sight in an attempt to harm them for some grudge or stupid reason.
I feel that by working together and not against each other we can reach a better and healthier future for the community,
health of developers and numerous more reasons.

View file

@ -1,3 +1,51 @@
wii-sports-club:
include-in-checkall: true
features:
- prudpv1
- third-notif-param
- v3-8-15
settings:
AUTH_REPORT_VERSION: "branch:origin/project/appsp build:3_4_24_4_0"
RNEX_VIRTUAL_PORT_INSECURE: "1:10"
RNEX_VIRTUAL_PORT_SECURE: "1:10"
RNEX_DEFAULT_PORT: 10000
RNEX_ACCESS_KEY: "4d324052"
puyopuyo:
include-in-checkall: true
features:
- prudpv1
- third-notif-param
- v3-8-15
settings:
AUTH_REPORT_VERSION: "branch:origin/release/ngs/3.5.x.1000 build:3_5_16_1000_0"
RNEX_VIRTUAL_PORT_INSECURE: "1:10"
RNEX_VIRTUAL_PORT_SECURE: "1:10"
RNEX_DEFAULT_PORT: 10000
RNEX_ACCESS_KEY: "4eb0ca36"
minecraft-wiiu:
include-in-checkall: true
features:
- prudpv1
- third-notif-param
- v3-10-22
settings:
AUTH_REPORT_VERSION: "branch:origin/release/ngs/3.10.x.200x build:3_10_22_2006_0"
RNEX_VIRTUAL_PORT_INSECURE: "1:10"
RNEX_VIRTUAL_PORT_SECURE: "1:10"
RNEX_DEFAULT_PORT: 13000
RNEX_ACCESS_KEY: "f1b61c8e"
mario-tennis:
include-in-checkall: true
features:
- prudpv1
- third-notif-param
- v3-8-15
settings:
AUTH_REPORT_VERSION: "branch:origin/release/ngs/3.9.x.200x build:3_9_19_2005_0"
RNEX_VIRTUAL_PORT_INSECURE: "1:10"
RNEX_VIRTUAL_PORT_SECURE: "1:10"
RNEX_DEFAULT_PORT: 10000
RNEX_ACCESS_KEY: "c69b92a0"
wii-u-chat: wii-u-chat:
include-in-checkall: true include-in-checkall: true
features: features:
@ -10,17 +58,41 @@ wii-u-chat:
RNEX_VIRTUAL_PORT_SECURE: "1:10" RNEX_VIRTUAL_PORT_SECURE: "1:10"
RNEX_DEFAULT_PORT: 10000 RNEX_DEFAULT_PORT: 10000
RNEX_ACCESS_KEY: "e7a47214" RNEX_ACCESS_KEY: "e7a47214"
fast-racing-neo:
include-in-checkall: true
features:
- prudpv1
- v3-8-15
settings:
AUTH_REPORT_VERSION: "branch:origin/release/ngs/3.9.x.200x build:3_9_19_2005_0"
RNEX_VIRTUAL_PORT_INSECURE: "1:10"
RNEX_VIRTUAL_PORT_SECURE: "1:10"
RNEX_DEFAULT_PORT: 10000
RNEX_ACCESS_KEY: "811aa39f"
splatoon: splatoon:
include-in-checkall: true include-in-checkall: true
features: features:
- prudpv1 - prudpv1
- v3-8-15 - v3-8-15
- splatoon
settings: settings:
AUTH_REPORT_VERSION: "branch:origin/project/wup-agmj build:3_8_15_2004_0" AUTH_REPORT_VERSION: "branch:origin/project/wup-agmj build:3_8_15_2004_0"
RNEX_VIRTUAL_PORT_INSECURE: "1:10" RNEX_VIRTUAL_PORT_INSECURE: "1:10"
RNEX_VIRTUAL_PORT_SECURE: "1:10" RNEX_VIRTUAL_PORT_SECURE: "1:10"
RNEX_DEFAULT_PORT: 6000 RNEX_DEFAULT_PORT: 6000
RNEX_ACCESS_KEY: "6f599f81" RNEX_ACCESS_KEY: "6f599f81"
splatoon-testfire:
include-in-checkall: true
features:
- prudpv1
- v3-8-15
- splatoon
settings:
AUTH_REPORT_VERSION: "branch:origin/project/wup-agmj build:3_8_15_2004_0"
RNEX_VIRTUAL_PORT_INSECURE: "1:10"
RNEX_VIRTUAL_PORT_SECURE: "1:10"
RNEX_DEFAULT_PORT: 10000
RNEX_ACCESS_KEY: "da693ee5"
friends: friends:
include-in-checkall: false include-in-checkall: false
features: features:

View file

@ -1,400 +1,27 @@
#![allow(dead_code)]
mod protos; mod protos;
mod rmc_struct;
mod util;
extern crate proc_macro; extern crate proc_macro;
use crate::protos::{ProtoMethodData, RmcProtocolData}; use crate::protos::{ProtoInputParams, RmcProtocolData};
use crate::rmc_struct::{rmc_serialize_enum, rmc_serialize_struct};
use proc_macro::TokenStream; use proc_macro::TokenStream;
use proc_macro2::{Ident, Literal, Span}; use proc_macro2::Ident;
use quote::{quote, TokenStreamExt}; use quote::quote;
use syn::parse::{Parse, ParseStream};
use syn::punctuated::Punctuated;
use syn::spanned::Spanned; use syn::spanned::Spanned;
use syn::{ use syn::{parse_macro_input, Data, DeriveInput, Lit, LitStr};
parse_macro_input, Attribute, Data, DataStruct, DeriveInput, Fields, FnArg, Lit, LitInt,
LitStr, Pat, Token, TraitItem,
};
struct ProtoInputParams {
proto_num: LitInt,
properties: Option<(Token![,], Punctuated<Ident, Token![,]>)>,
}
impl Parse for ProtoInputParams {
fn parse(input: ParseStream) -> syn::Result<Self> {
let proto_num = input.parse()?;
if let Some(seperator) = input.parse()? {
let mut punctuated = Punctuated::new();
loop {
punctuated.push_value(input.parse()?);
if let Some(punct) = input.parse()? {
punctuated.push_punct(punct);
} else {
return Ok(Self {
proto_num,
properties: Some((seperator, punctuated)),
});
}
}
} else {
Ok(Self {
proto_num,
properties: None,
})
}
}
}
fn gen_serialize_data_struct(
s: DataStruct,
struct_attr: Option<&Attribute>,
) -> (
proc_macro2::TokenStream,
proc_macro2::TokenStream,
proc_macro2::TokenStream,
) {
let serialize_base_content = {
let mut serialize_content = quote! {};
for f in &s.fields {
if f.attrs.iter().any(|a| {
a.path().segments.len() == 1
&& a.path()
.segments
.first()
.is_some_and(|p| p.ident.to_string() == "extends")
}) {
continue;
}
let ident = f.ident.as_ref().unwrap();
serialize_content.append_all(quote! {
rnex_core::rmc::structures::RmcSerialize::serialize(&self.#ident, writer)?;
})
}
quote! {
#serialize_content
Ok(())
}
};
let struct_ctor = {
let mut structure_content = quote! {};
for f in &s.fields {
let ident = f.ident.as_ref().unwrap();
structure_content.append_all(quote! {#ident, });
}
quote! {
Ok(Self{
#structure_content
})
}
};
let deserialize_base_content = {
let mut deserialize_content = quote! {};
for f in &s.fields {
if f.attrs.iter().any(|a| {
a.path().segments.len() == 1
&& a.path()
.segments
.first()
.is_some_and(|p| p.ident.to_string() == "extends")
}) {
continue;
}
let ident = f.ident.as_ref().unwrap();
let ty = &f.ty;
deserialize_content.append_all(quote! {
let #ident = <#ty> :: deserialize(reader)?;
})
}
quote! {
#deserialize_content
#struct_ctor
}
};
let write_size = {
let mut size_content = quote! { 0 };
for f in &s.fields {
let ident = f.ident.as_ref().unwrap();
size_content.append_all(quote! {
+ rnex_core::rmc::structures::RmcSerialize::serialize_write_size(&self.#ident)?
})
}
size_content
};
let write_size = if let Some(_) = struct_attr {
quote! { #write_size + if rnex_core::config::FEATURE_HAS_STRUCT_HEADER{ 5 } else { 0 } }
} else {
write_size
};
// generate base with extends stuff
let serialize_base_content = if let Some(attr) = struct_attr {
let version: Literal = attr.parse_args().expect("has to be a literal");
let pre_inner = if let Some(f) = s.fields.iter().find(|f| {
f.attrs.iter().any(|a| {
a.path().segments.len() == 1
&& a.path()
.segments
.first()
.is_some_and(|p| p.ident.to_string() == "extends")
})
}) {
let ident = f.ident.as_ref().unwrap();
quote! {
self.#ident.serialize(writer)?;
}
} else {
quote! {}
};
quote! {
#pre_inner
rnex_core::rmc::structures::rmc_struct::write_struct(
writer,
#version,
rnex_core::rmc::structures::helpers::len_of_write(
|writer|{
#serialize_base_content
}
),
|writer|{
#serialize_base_content
}
)?;
Ok(())
}
} else {
serialize_base_content
};
let deserialize_base_content = if let Some(attr) = struct_attr {
let version: Literal = attr.parse_args().expect("has to be a literal");
let pre_inner = if let Some(f) = s.fields.iter().find(|f| {
f.attrs.iter().any(|a| {
a.path().segments.len() == 1
&& a.path()
.segments
.first()
.is_some_and(|p| p.ident.to_string() == "extends")
})
}) {
let ident = f.ident.as_ref().unwrap();
let ty = &f.ty;
quote! {
let #ident = <#ty> :: deserialize(reader)?;
}
} else {
quote! {}
};
quote! {
#pre_inner
Ok(rnex_core::rmc::structures::rmc_struct::read_struct(reader, #version, move |mut reader|{
#deserialize_base_content
})?)
}
} else {
deserialize_base_content
};
let write_size = quote! {
fn serialize_write_size(&self) -> rnex_core::rmc::structures::Result<u32>{
Ok(#write_size)
}
};
(serialize_base_content, deserialize_base_content, write_size)
}
#[proc_macro_derive(RmcSerialize, attributes(extends, rmc_struct))] #[proc_macro_derive(RmcSerialize, attributes(extends, rmc_struct))]
pub fn rmc_serialize(input: TokenStream) -> TokenStream { pub fn rmc_serialize(input: TokenStream) -> TokenStream {
let derive_input = parse_macro_input!(input as DeriveInput); let derive_input = parse_macro_input!(input as DeriveInput);
let struct_attr = derive_input.attrs.iter().find(|a| { let (serialize, deserialize, write_size, version) = match &derive_input.data {
a.path().segments.len() == 1 Data::Struct(s) => rmc_serialize_struct(s, &derive_input),
&& a.path() Data::Enum(e) => rmc_serialize_enum(e, &derive_input),
.segments
.first()
.is_some_and(|p| p.ident.to_string() == "rmc_struct")
});
let repr_attr = derive_input.attrs.iter().find(|a| {
a.path().segments.len() == 1
&& a.path()
.segments
.first()
.is_some_and(|p| p.ident.to_string() == "repr")
});
/*let Data::Struct(s) = derive_input.data else {
panic!("rmc struct type MUST be a struct");
};*/
let (serialize_base_content, deserialize_base_content, write_size) = match derive_input.data {
Data::Struct(s) => gen_serialize_data_struct(s, struct_attr),
Data::Enum(e) => {
let Some(repr_attr) = repr_attr else {
panic!("missing repr attribute");
};
let ty: Ident = repr_attr.parse_args().unwrap();
let mut inner_match_de = quote! {};
let mut inner_match_se = quote! {};
//let mut inner_match_len = quote!{};
for variant in e.variants {
let Some((_, val)) = variant.discriminant else {
panic!("missing discriminant");
};
let field_data_de = match &variant.fields {
Fields::Named(v) => {
let mut base = quote! {};
for field in v.named.iter() {
let ty = &field.ty;
let name = &field.ident;
base.append_all(quote!{
#name: <#ty as rnex_core::rmc::structures::RmcSerialize>::deserialize(reader)?,
});
}
quote! {{#base}}
}
Fields::Unnamed(n) => {
let mut base = quote! {};
for field in n.unnamed.iter() {
let ty = &field.ty;
base.append_all(quote!{
<#ty as rnex_core::rmc::structures::RmcSerialize>::deserialize(reader)?,
});
}
quote! {(#base)}
}
Fields::Unit => {
quote! {}
}
};
let mut se_with_fields = quote! {
<#ty as rnex_core::rmc::structures::RmcSerialize>::serialize(&#val, writer)?;
};
match &variant.fields {
Fields::Named(v) => {
for field in v.named.iter() {
let ty = &field.ty;
let name = &field.ident;
se_with_fields.append_all(quote!{
<#ty as rnex_core::rmc::structures::RmcSerialize>::serialize(#name ,writer)?;
});
}
}
Fields::Unnamed(n) => {
for (i, field) in n.unnamed.iter().enumerate() {
let ty = &field.ty;
let ident = Ident::new(&format!("val_{}", i), Span::call_site());
se_with_fields.append_all(quote!{
<#ty as rnex_core::rmc::structures::RmcSerialize>::serialize(#ident, writer)?;
});
}
}
Fields::Unit => {}
};
let field_match_se = match &variant.fields {
Fields::Named(v) => {
let mut base = quote! {};
for field in v.named.iter() {
let name = &field.ident;
base.append_all(quote! {
#name,
});
}
quote! {{#base}}
}
Fields::Unnamed(n) => {
let mut base = quote! {};
for (i, _field) in n.unnamed.iter().enumerate() {
let ident = Ident::new(&format!("val_{}", i), Span::call_site());
base.append_all(quote! {
#ident,
});
}
quote! {(#base)}
}
Fields::Unit => {
quote! {}
}
};
let name = variant.ident;
inner_match_de.append_all(quote! {
#val => Self::#name #field_data_de,
});
inner_match_se.append_all(quote! {
Self::#name #field_match_se => {
#se_with_fields
},
});
}
let serialize_base_content = quote! {
match self{
#inner_match_se
};
Ok(())
};
let deserialize_base_content = quote! {
let val: Self = match <#ty as rnex_core::rmc::structures::RmcSerialize>::deserialize(reader)?{
#inner_match_de
v => return Err(rnex_core::rmc::structures::Error::UnexpectedValue(v as _))
};
Ok(val)
};
(serialize_base_content, deserialize_base_content, quote! {})
}
Data::Union(_) => { Data::Union(_) => {
unimplemented!() unimplemented!("serialize a union is not allowed");
} }
}; };
@ -406,19 +33,41 @@ pub fn rmc_serialize(input: TokenStream) -> TokenStream {
)); ));
let ident = derive_input.ident; let ident = derive_input.ident;
let write_size = if let Some(v) = write_size {
quote! {
fn serialize_write_size(&self) -> rnex_core::rmc::structures::Result<u32>{
#v
}
}
} else {
quote! {}
};
let version = if let Some(v) = version {
quote! {
fn version() -> Option<u8>{
#v
}
}
} else {
quote! {}
};
let tokens = quote! { let tokens = quote! {
impl rnex_core::rmc::structures::RmcSerialize for #ident{ impl rnex_core::rmc::structures::RmcSerialize for #ident{
#[inline(always)] #[inline(always)]
fn serialize(&self, writer: &mut impl ::std::io::Write) -> rnex_core::rmc::structures::Result<()>{ fn serialize(&self, writer: &mut impl ::std::io::Write) -> rnex_core::rmc::structures::Result<()>{
#serialize_base_content #serialize
} }
#[inline(always)] #[inline(always)]
fn deserialize(reader: &mut impl ::std::io::Read) -> rnex_core::rmc::structures::Result<Self>{ fn deserialize(reader: &mut impl ::std::io::Read) -> rnex_core::rmc::structures::Result<Self>{
#deserialize_base_content #deserialize
} }
#write_size #write_size
#version
fn name() -> &'static str{ fn name() -> &'static str{
#str_name #str_name
} }
@ -455,71 +104,9 @@ pub fn rmc_serialize(input: TokenStream) -> TokenStream {
#[proc_macro_attribute] #[proc_macro_attribute]
pub fn rmc_proto(attr: TokenStream, input: TokenStream) -> TokenStream { pub fn rmc_proto(attr: TokenStream, input: TokenStream) -> TokenStream {
let params = parse_macro_input!(attr as ProtoInputParams); let params = parse_macro_input!(attr as ProtoInputParams);
let ProtoInputParams {
proto_num,
properties,
} = params;
let no_return_data =
properties.is_some_and(|p| p.1.iter().any(|i| i.to_string() == "NoReturn"));
let input = parse_macro_input!(input as syn::ItemTrait); let input = parse_macro_input!(input as syn::ItemTrait);
// gigantic ass struct initializer (to summarize this gets all of the data) let raw_data = RmcProtocolData::new(params, &input);
let raw_data = RmcProtocolData {
has_returns: !no_return_data,
name: input.ident.clone(),
id: proto_num,
methods: input
.items
.iter()
.filter_map(|v| match v {
TraitItem::Fn(v) => Some(v),
_ => None,
})
.map(|func| {
let Some(attr) = func.attrs.iter().find(|a| {
a.path()
.segments
.last()
.is_some_and(|s| s.ident.to_string() == "method_id")
}) else {
panic!("every function inside of an rmc protocol must have a method id");
};
let Ok(id): Result<LitInt, _> = attr.parse_args() else {
panic!("todo: put a propper error message here");
};
let funcs = func
.sig
.inputs
.iter()
.skip(1)
.map(|f| {
let FnArg::Typed(t) = f else {
panic!("what");
};
let Pat::Ident(i) = &*t.pat else {
panic!(
"unable to handle non identifier patterns as parameter bindings"
);
};
(i.ident.clone(), t.ty.as_ref().clone())
})
.collect();
ProtoMethodData {
id,
name: func.sig.ident.clone(),
parameters: funcs,
ret_val: func.sig.output.clone(),
}
})
.collect(),
};
quote! { quote! {
#input #input

View file

@ -1,12 +1,48 @@
use proc_macro2::{Ident, Span, TokenStream}; use proc_macro2::{Ident, Span, TokenStream};
use quote::{quote, ToTokens}; use quote::{quote, ToTokens};
use syn::token::{Brace, Paren, Semi}; use syn::{
use syn::{LitInt, LitStr, ReturnType, Type}; parse::{Parse, ParseStream},
punctuated::Punctuated,
Attribute, FnArg, ItemTrait, LitInt, LitStr, Meta, Pat, ReturnType, Token, TraitItem, Type,
};
use crate::util::fold_tokenable;
pub struct ProtoInputParams {
proto_num: LitInt,
properties: Option<(Token![,], Punctuated<Ident, Token![,]>)>,
}
impl Parse for ProtoInputParams {
fn parse(input: ParseStream) -> syn::Result<Self> {
let proto_num = input.parse()?;
if let Some(seperator) = input.parse()? {
let mut punctuated = Punctuated::new();
loop {
punctuated.push_value(input.parse()?);
if let Some(punct) = input.parse()? {
punctuated.push_punct(punct);
} else {
return Ok(Self {
proto_num,
properties: Some((seperator, punctuated)),
});
}
}
} else {
Ok(Self {
proto_num,
properties: None,
})
}
}
}
pub struct ProtoMethodData { pub struct ProtoMethodData {
pub id: LitInt, pub id: LitInt,
pub name: Ident, pub name: Ident,
pub parameters: Vec<(Ident, Type)>, pub attributes: Vec<Attribute>,
pub parameters: Vec<(Ident, Type, Vec<Attribute>)>,
pub ret_val: ReturnType, pub ret_val: ReturnType,
} }
@ -23,161 +59,209 @@ pub struct RmcProtocolData {
} }
impl RmcProtocolData { impl RmcProtocolData {
fn generate_raw_trait(&self, tokens: &mut TokenStream) { pub fn new(params: ProtoInputParams, input: &ItemTrait) -> Self {
let ProtoInputParams {
proto_num,
properties,
} = params;
let no_return_data =
properties.is_some_and(|p| p.1.iter().any(|i| i.to_string() == "NoReturn"));
// gigantic ass struct initializer (to summarize this gets all of the data)
RmcProtocolData {
has_returns: !no_return_data,
name: input.ident.clone(),
id: proto_num,
methods: input
.items
.iter()
.filter_map(|v| match v {
TraitItem::Fn(v) => Some(v),
_ => None,
})
.map(|func| {
let Some(attr) = func.attrs.iter().find(|a| {
a.path()
.segments
.last()
.is_some_and(|s| s.ident.to_string() == "method_id")
}) else {
panic!("every function inside of an rmc protocol must have a method id");
};
let Ok(id): Result<LitInt, _> = attr.parse_args() else {
panic!("todo: put a propper error message here");
};
let funcs = func
.sig
.inputs
.iter()
.skip(1)
.map(|f| {
let FnArg::Typed(t) = f else {
panic!("what");
};
let Pat::Ident(i) = &*t.pat else {
panic!(
"unable to handle non identifier patterns as parameter bindings"
);
};
(i.ident.clone(), t.ty.as_ref().clone(), t.attrs.clone())
})
.collect();
ProtoMethodData {
id,
name: func.sig.ident.clone(),
parameters: funcs,
ret_val: func.sig.output.clone(),
attributes: func
.attrs
.iter()
.filter(|a| match &a.meta {
Meta::NameValue(v) => {
if let Some(i) = v.path.get_ident() {
i.to_string() != "doc"
} else {
true
}
}
Meta::List(l) => {
if let Some(seg) = l.path.segments.last() {
seg.ident.to_string() != "method_id"
} else {
true
}
}
_ => true,
})
.cloned()
.collect(),
}
})
.collect(),
}
}
fn generate_raw_trait(&self) -> TokenStream {
let Self { let Self {
has_returns, has_returns,
name, name,
id, id,
methods, methods,
} = self; } = self;
let generate_raw_method = |method: &ProtoMethodData| -> TokenStream {
// this gives us the name which the identifier of the corresponding Raw trait
let raw_name = Ident::new(&format!("Raw{}", name), name.span());
// boilerplate tokens which all raw traits need
quote! {
#[doc(hidden)]
#[allow(unused_must_use)]
pub trait #raw_name: #name
}
.to_tokens(tokens);
// generate the body of the raw protocol trait
Brace::default().surround(tokens, |tokens|{
//generate each raw method
for method in methods{
let ProtoMethodData { let ProtoMethodData {
name, name,
parameters, parameters,
attributes,
.. ..
} = method; } = method;
let attribs = fold_tokenable(attributes.iter());
let raw_name = Ident::new(&format!("raw_{}", name), name.span()); let raw_name = Ident::new(&format!("raw_{}", name), name.span());
quote!{
#[inline(always)]
async fn #raw_name
}.to_tokens(tokens);
Paren::default().surround(tokens, |tokens|{ let optional_return = if self.has_returns {
quote!{ &self, data: ::std::vec::Vec<u8> }.to_tokens(tokens);
});
if self.has_returns {
quote! { quote! {
-> ::core::result::Result<Vec<u8>, ErrorCode> -> ::core::result::Result<Vec<u8>, ::rnex_core::rmc::response::ErrorCode>
}.to_tokens(tokens);
} }
} else {
quote! {}
}
.into_token_stream();
Brace::default().surround(tokens, |tokens|{ let deser_params =
quote! { let mut cursor = ::std::io::Cursor::new(data); }.to_tokens(tokens); fold_tokenable(parameters.iter().map(|(param_name, param_type, attribs)| {
let error_msg = LitStr::new(
for (param_name, param_type) in parameters{ &format!("an error occurred whilest deserializing {}", param_name),
quote!{ Span::call_site(),
);
let return_from_deser_error = if self.has_returns {
quote! {
return Err(::rnex_core::rmc::response::ErrorCode::Core_InvalidArgument);
}
} else {
quote! {
return;
}
};
let attribs = fold_tokenable(attribs.iter());
quote! {
#attribs
let Ok(#param_name) = let Ok(#param_name) =
<#param_type as rnex_core::rmc::structures::RmcSerialize>::deserialize( <#param_type as rnex_core::rmc::structures::RmcSerialize>::deserialize(
&mut cursor &mut cursor
) else ) else{
}.to_tokens(tokens);
let error_msg = LitStr::new(&format!("an error occurred whilest deserializing {}", param_name), Span::call_site());
if self.has_returns {
quote! {
{
log::error!(#error_msg); log::error!(#error_msg);
return Err(rnex_core::rmc::response::ErrorCode::Core_InvalidArgument); #return_from_deser_error
}; };
}.to_tokens(tokens) }
} else { }));
let call_params = fold_tokenable(parameters.iter().map(|(param_name, _, attribs)| {
let attribs = fold_tokenable(attribs.iter());
quote! { quote! {
{ #attribs
log::error!(#error_msg); #param_name,
return;
};
}.to_tokens(tokens)
} }
}));
} let optional_method_return = if *has_returns {
quote! {
quote!{
let retval = self.#name
}.to_tokens(tokens);
Paren::default().surround(tokens, |tokens|{
for (paren_name, _) in parameters{
quote!{#paren_name,}.to_tokens(tokens);
}
});
quote!{
.await;
}.to_tokens(tokens);
if *has_returns{
quote!{
let retval = retval?; let retval = retval?;
let mut vec = Vec::new(); let mut vec = Vec::new();
rnex_core::rmc::structures::RmcSerialize::serialize(&retval, &mut vec).ok(); rnex_core::rmc::structures::RmcSerialize::serialize(&retval, &mut vec).ok();
Ok(vec) Ok(vec)
}.to_tokens(tokens);
}
})
} }
} else {
quote! {}
};
quote!{
#[inline(always)]
async fn rmc_call_proto(
&self,
remote_response_connection: &rnex_core::util::SendingBufferConnection,
method_id: u32,
call_id: u32,
data: Vec<u8>,
)
}.to_tokens(tokens);
Brace::default().surround(tokens, |tokens|{
quote! { quote! {
let ret = match method_id #[inline(always)]
}.to_tokens(tokens); #attribs
async fn #raw_name (&self, data: ::std::vec::Vec<u8>) #optional_return{
let mut cursor = ::std::io::Cursor::new(data);
#deser_params
let retval = self.#name(#call_params).await;
#optional_method_return
}
Brace::default().surround(tokens, |tokens|{ }
for method in methods{ };
let ProtoMethodData{
let generate_rmc_call_proto = || {
let method_entries = fold_tokenable(methods.iter().map(|m| {
let ProtoMethodData {
id, id,
name, name,
attributes,
.. ..
} = method; } = m;
let attribs = fold_tokenable(attributes.iter());
let raw_name = Ident::new(&format!("raw_{}", name), name.span()); let raw_name = Ident::new(&format!("raw_{}", name), name.span());
quote! {
quote!{ #attribs
#id => self.#raw_name(data).await, #id => self.#raw_name(data).await,
}.to_tokens(tokens);
} }
quote!{ }));
v =>
}.to_tokens(tokens);
let optional_notimpl_return = if self.has_returns {
Brace::default().surround(tokens, |tokens|{
quote!{
log::error!("(protocol {})unimplemented method id called on protocol: {}", #id, v);
}.to_tokens(tokens);
if self.has_returns {
quote! { quote! {
Err(rnex_core::rmc::response::ErrorCode::Core_NotImplemented) Err(rnex_core::rmc::response::ErrorCode::Core_NotImplemented)
}.to_tokens(tokens);
} }
}); } else {
quote! {}
};
}); let optional_result_sendback = if *has_returns {
quote! {
Semi::default().to_tokens(tokens);
if *has_returns{
quote!{
rnex_core::rmc::response::send_result( rnex_core::rmc::response::send_result(
remote_response_connection, remote_response_connection,
ret, ret,
@ -185,18 +269,51 @@ impl RmcProtocolData {
method_id, method_id,
call_id, call_id,
).await ).await
}.to_tokens(tokens);
} }
}); } else {
}); quote! {}
};
quote! { quote! {
#[inline(always)]
async fn rmc_call_proto(
&self,
remote_response_connection: &rnex_core::util::SendingBufferConnection,
method_id: u32,
call_id: u32,
data: Vec<u8>,
){
let ret = match method_id{
#method_entries
v => {
log::error!("(protocol {})unimplemented method id called on protocol: {}", #id, v);
#optional_notimpl_return
}
};
#optional_result_sendback
}
}
};
// this gives us the name which the identifier of the corresponding Raw trait
let raw_name = Ident::new(&format!("Raw{}", name), name.span());
let proto_raw_methods = fold_tokenable(self.methods.iter().map(|m| generate_raw_method(m)));
let rmc_call_proto = generate_rmc_call_proto();
// boilerplate tokens which all raw traits need
quote! {
#[doc(hidden)]
#[allow(unused_must_use)]
pub trait #raw_name: #name{
#proto_raw_methods
#rmc_call_proto
}
impl<T: #name> #raw_name for T{} impl<T: #name> #raw_name for T{}
} }
.to_tokens(tokens); .to_token_stream()
} }
fn generate_raw_remote_trait(&self, tokens: &mut TokenStream) { fn generate_raw_remote_trait(&self) -> TokenStream {
let Self { let Self {
has_returns, has_returns,
name, name,
@ -207,69 +324,64 @@ impl RmcProtocolData {
// this gives us the name which the identifier of the corresponding Raw trait // this gives us the name which the identifier of the corresponding Raw trait
let remote_name = Ident::new(&format!("Remote{}", name), name.span()); let remote_name = Ident::new(&format!("Remote{}", name), name.span());
let generate_remote_method = |m: &ProtoMethodData| -> TokenStream {
// boilerplate tokens which all raw traits need
quote! {
#[doc(hidden)]
#[allow(unused_must_use)]
pub trait #remote_name: rnex_core::rmc::protocols::HasRmcConnection
}
.to_tokens(tokens);
// generate the body of the raw protocol trait
Brace::default().surround(tokens, |tokens|{
//generate each raw method
for method in methods{
let ProtoMethodData { let ProtoMethodData {
name, name,
parameters, parameters,
ret_val, ret_val,
attributes,
id: method_id, id: method_id,
.. } = m;
} = method;
quote!{ let params = fold_tokenable(parameters.iter().map(|(ident, ty, attr)| {
async fn #name let attrs = fold_tokenable(attr.iter());
}.to_tokens(tokens); quote! { #attrs #ident: #ty, }
}));
Paren::default().surround(tokens, |tokens|{ let optional_questionmark_operator = if self.has_returns {
quote!{ &self, }.to_tokens(tokens);
for (param_ident, param_type) in parameters{
quote!{ #param_ident: #param_type, }.to_tokens(tokens);
}
});
quote!{
#ret_val
}.to_tokens(tokens);
Brace::default().surround(tokens, |tokens|{
quote! { quote! {
let mut send_data = Vec::new(); ?
let mut cursor = ::std::io::Cursor::new(&mut send_data); }
}.to_tokens(tokens); } else {
quote! {}
};
for (param_name, param_type) in parameters{ let param_serialize = fold_tokenable(parameters.iter().map(|(name, ty, attrs)|{
let attrs = fold_tokenable(attrs.iter());
quote!{ quote!{
#attrs
rnex_core::result::ResultExtension::display_err_or_some( rnex_core::result::ResultExtension::display_err_or_some(
<#param_type as rnex_core::rmc::structures::RmcSerialize>::serialize( <#ty as rnex_core::rmc::structures::RmcSerialize>::serialize(
&#param_name, &#name,
&mut cursor &mut cursor
) )
).ok_or(rnex_core::rmc::response::ErrorCode::Core_InvalidArgument) ).ok_or(rnex_core::rmc::response::ErrorCode::Core_InvalidArgument)#optional_questionmark_operator ;
}.to_tokens(tokens); }
if self.has_returns { }));
let make_call = if *has_returns {
quote! { quote! {
?; rnex_core::result::ResultExtension::display_err_or_some(
}.to_tokens(tokens) rmc_conn.make_raw_call(&message).await
).ok_or(rnex_core::rmc::response::ErrorCode::Core_Exception)
}
} else { } else {
quote! { quote! {
; rnex_core::result::ResultExtension::display_err_or_some(
}.to_tokens(tokens) rmc_conn.make_raw_call_no_response(&message).await
} );
} }
};
let attribs = fold_tokenable(attributes.iter());
quote! {
#attribs
async fn #name(&self, #params) #ret_val{
let mut send_data = ::std::vec::Vec::new();
let mut cursor = ::std::io::Cursor::new(&mut send_data);
#param_serialize
quote!{
let call_id = rand::random(); let call_id = rand::random();
let message = rnex_core::rmc::message::RMCMessage{ let message = rnex_core::rmc::message::RMCMessage{
@ -280,28 +392,24 @@ impl RmcProtocolData {
}; };
let rmc_conn = <Self as rnex_core::rmc::protocols::HasRmcConnection>::get_connection(self); let rmc_conn = <Self as rnex_core::rmc::protocols::HasRmcConnection>::get_connection(self);
}.to_tokens(tokens);
if *has_returns{ #make_call
quote!{ }
rnex_core::result::ResultExtension::display_err_or_some( }
rmc_conn.make_raw_call(&message).await };
).ok_or(rnex_core::rmc::response::ErrorCode::Core_Exception)
}.to_tokens(tokens); let remote_methods = fold_tokenable(methods.iter().map(|m| generate_remote_method(m)));
} else {
quote!{ quote! {
rnex_core::result::ResultExtension::display_err_or_some( #[doc(hidden)]
rmc_conn.make_raw_call_no_response(&message).await #[allow(unused_must_use)]
); pub trait #remote_name: rnex_core::rmc::protocols::HasRmcConnection{
}.to_tokens(tokens); #remote_methods
}
}
} }
}) fn generate_raw_info(&self) -> TokenStream {
}
});
}
fn generate_raw_info(&self, tokens: &mut TokenStream) {
let Self { name, id, .. } = self; let Self { name, id, .. } = self;
let raw_info_name = Ident::new(&format!("Raw{}Info", name), Span::call_site()); let raw_info_name = Ident::new(&format!("Raw{}Info", name), Span::call_site());
@ -314,14 +422,13 @@ impl RmcProtocolData {
pub const PROTOCOL_ID: u16 = #id; pub const PROTOCOL_ID: u16 = #id;
} }
} }
.to_tokens(tokens);
} }
} }
impl ToTokens for RmcProtocolData { impl ToTokens for RmcProtocolData {
fn to_tokens(&self, tokens: &mut TokenStream) { fn to_tokens(&self, tokens: &mut TokenStream) {
self.generate_raw_trait(tokens); self.generate_raw_trait().to_tokens(tokens);
self.generate_raw_info(tokens); self.generate_raw_info().to_tokens(tokens);
self.generate_raw_remote_trait(tokens); self.generate_raw_remote_trait().to_tokens(tokens);
} }
} }

426
macros/src/rmc_struct.rs Normal file
View file

@ -0,0 +1,426 @@
use proc_macro2::{Literal, Span, TokenStream};
use quote::quote;
use syn::{
bracketed, parse::Parse, punctuated::Punctuated, token::Bracket, DataEnum, DataStruct,
DeriveInput, Field, Fields, Ident, Meta, Token, Variant,
};
use crate::util::fold_tokenable;
struct RmcStructAttrVersion {
bracket: Bracket,
delim: Token![,],
feature_name: Literal,
struct_version: Literal,
}
struct RmcStructAttr {
base_ver: Literal,
versions: Option<(Token![,], Punctuated<RmcStructAttrVersion, Token![,]>)>,
}
impl Parse for RmcStructAttr {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
let base_ver = input.parse()?;
if let Some(seperator) = input.parse()? {
let mut punctuated = Punctuated::new();
loop {
punctuated.push_value(input.parse()?);
if let Some(punct) = input.parse()? {
punctuated.push_punct(punct);
} else {
return Ok(Self {
base_ver,
versions: Some((seperator, punctuated)),
});
}
}
} else {
Ok(Self {
base_ver,
versions: None,
})
}
}
}
impl RmcStructAttr {
fn versions(&self) -> impl Iterator<Item = &RmcStructAttrVersion> {
self.versions.iter().flat_map(|v| v.1.iter())
}
}
impl Parse for RmcStructAttrVersion {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
let content;
let bracket = bracketed!(content in input);
let (feature_name, delim, struct_version) =
content.call(|s| Ok((s.parse()?, s.parse()?, s.parse()?)))?;
Ok(Self {
bracket,
delim,
feature_name,
struct_version,
})
}
}
pub fn generate_write_size_struct(
s: &DataStruct,
with_potential_header: bool,
) -> proc_macro2::TokenStream {
// this is fine and works because of a quirk where the sizes of the structs dont change
// if we ignore wether or not a struct extends the other struct or has it as a field
let base_size = fold_tokenable(s.fields.iter().map(|f| {
let ident = f.ident.as_ref().unwrap();
let attrs = fold_tokenable(f.attrs.iter().filter(|a| {
if let Some(i) = a.meta.path().get_ident() {
i.to_string() != "extends"
} else {
true
}
}));
quote! {
#attrs
sum += rnex_core::rmc::structures::RmcSerialize::serialize_write_size(&self.#ident)?;
}
}));
let optional_struct_header_calc = if with_potential_header {
quote! { sum += (if rnex_core::config::FEATURE_HAS_STRUCT_HEADER{ 5 } else { 0 }); }
} else {
quote! {}
};
quote! {
let mut sum = 0;
#base_size
#optional_struct_header_calc
Ok(sum)
}
}
pub fn generate_serialize_struct(
extended_struct: Option<&Field>,
elems: &[&Field],
with_header: bool,
) -> proc_macro2::TokenStream {
fn gen_elem_serialize(f: &Field) -> TokenStream {
let ident = f.ident.as_ref().unwrap();
let attrs = fold_tokenable(f.attrs.iter().filter(|a| {
if let Some(i) = a.meta.path().get_ident() {
i.to_string() != "extends"
} else {
true
}
}));
quote! {
#attrs
rnex_core::rmc::structures::RmcSerialize::serialize(&self.#ident, writer)?;
}
}
let optional_extended_struct = if let Some(f) = extended_struct {
gen_elem_serialize(f)
} else {
quote! {}
};
let elems = fold_tokenable(elems.iter().map(|e| gen_elem_serialize(e)));
let ser_body = if with_header {
quote! {
rnex_core::rmc::structures::rmc_struct::write_struct(
writer,
Self::version().unwrap(),
rnex_core::rmc::structures::helpers::len_of_write(
|writer|{
#elems
Ok(())
}
),
|writer|{
#elems
Ok(())
}
)?;
}
} else {
elems
};
quote! {
#optional_extended_struct
#ser_body
Ok(())
}
}
pub fn generate_deserialize_struct(
s: &DataStruct,
extended_struct: Option<&Field>,
elems: &[&Field],
with_header: bool,
) -> proc_macro2::TokenStream {
fn gen_elem_serialize(f: &Field) -> TokenStream {
let ident = f.ident.as_ref().unwrap();
let ty = &f.ty;
let attrs = fold_tokenable(f.attrs.iter().filter(|a| {
if let Some(i) = a.meta.path().get_ident() {
i.to_string() != "extends"
} else {
true
}
}));
quote! {
#attrs
let #ident: #ty = rnex_core::rmc::structures::RmcSerialize::deserialize(reader)?;
}
}
let optional_extended_struct = if let Some(f) = extended_struct {
gen_elem_serialize(f)
} else {
quote! {}
};
let elems = fold_tokenable(elems.iter().map(|e| gen_elem_serialize(e)));
let struct_ctor_content = fold_tokenable(s.fields.iter().map(|f| {
let ident = f.ident.as_ref().unwrap();
let attrs = fold_tokenable(f.attrs.iter().filter(|a| {
if let Some(i) = a.meta.path().get_ident() {
i.to_string() != "extends"
} else {
true
}
}));
quote! { #attrs #ident, }
}));
let de_body_inner = quote! {
#elems
Ok(Self{
#struct_ctor_content
})
};
let de_body = if with_header {
quote! {
Ok(rnex_core::rmc::structures::rmc_struct::read_struct(reader, Self::version().unwrap(), move |mut reader|{
#de_body_inner
})?)
}
} else {
de_body_inner
};
quote! {
#optional_extended_struct
#de_body
}
}
fn generate_struct_version(attr: Option<&RmcStructAttr>) -> proc_macro2::TokenStream {
if let Some(attr) = attr {
let base_ver = &attr.base_ver;
let if_else_chain = fold_tokenable(attr.versions().map(|v| {
let version_val = &v.struct_version;
let feature = &v.feature_name;
quote! {
if cfg!(feature = #feature){
#version_val
} else
}
}));
quote! {
Some(#if_else_chain {
#base_ver
})
}
} else {
quote! { None }
}
}
pub fn rmc_serialize_struct(
s: &DataStruct,
derive_input: &DeriveInput,
) -> (
proc_macro2::TokenStream,
proc_macro2::TokenStream,
Option<proc_macro2::TokenStream>,
Option<proc_macro2::TokenStream>,
) {
let struct_attr = derive_input.attrs.iter().find(|a| {
a.path().segments.len() == 1
&& a.path()
.segments
.first()
.is_some_and(|p| p.ident.to_string() == "rmc_struct")
&& matches!(a.meta, Meta::List(_))
});
let struct_attr: Option<RmcStructAttr> = struct_attr.map(|a| a.parse_args().unwrap());
let struct_attr = struct_attr.as_ref();
let extended_struct = s.fields.iter().find(|f| {
f.attrs.iter().any(|a| {
a.path().segments.len() == 1
&& a.path()
.segments
.first()
.is_some_and(|p| p.ident.to_string() == "extends")
})
});
let elements: Vec<_> = s
.fields
.iter()
.filter(|f| {
!f.attrs.iter().any(|a| {
a.path().segments.len() == 1
&& a.path()
.segments
.first()
.is_some_and(|p| p.ident.to_string() == "extends")
})
})
.collect();
let elements = &elements[..];
let serialize = generate_serialize_struct(extended_struct, elements, struct_attr.is_some());
let deserialize =
generate_deserialize_struct(s, extended_struct, elements, struct_attr.is_some());
let write_size = generate_write_size_struct(s, struct_attr.is_some());
let version = generate_struct_version(struct_attr);
(serialize, deserialize, Some(write_size), Some(version))
}
fn field_to_ident(field: &Field, idx: usize) -> Ident {
if let Some(i) = &field.ident {
i.clone()
} else {
Ident::new(&format!("field_{}", idx), Span::call_site())
}
}
fn variant_to_pattern_and_fields(variant: &Variant) -> (proc_macro2::TokenStream, Vec<Field>) {
match &variant.fields {
Fields::Named(n) => {
let inner = n
.named
.iter()
.map(|f| {
let attrs = fold_tokenable(f.attrs.iter());
let ident = f.ident.as_ref().unwrap();
quote! { #attrs #ident }
})
.reduce(|a, b| quote! {#a, #b});
(quote! {{#inner}}, n.named.iter().cloned().collect())
}
Fields::Unnamed(n) => {
let inner = n
.unnamed
.iter()
.enumerate()
.map(|(i, f)| {
let attrs = fold_tokenable(f.attrs.iter());
let name = field_to_ident(f, i);
quote! { #attrs #name }
})
.reduce(|a, b| quote! {#a, #b});
(quote! {(#inner)}, n.unnamed.iter().cloned().collect())
}
Fields::Unit => (quote! {}, vec![]),
}
}
pub fn rmc_generate_serialize_enum(
enum_data: &DataEnum,
repr_ty: &Ident,
) -> proc_macro2::TokenStream {
let match_content = fold_tokenable(enum_data.variants.iter().map(|v|{
let ident = &v.ident;
let descriminant = &v.discriminant.as_ref().expect("every variant must have a descriminant to be a valid rmc struct").1;
let (pattern, fields) = variant_to_pattern_and_fields(v);
let inner = fold_tokenable(fields.iter().enumerate().map(|(i, f)|{
let ty = &f.ty;
let name = field_to_ident(&f, i);
quote! {<#ty as rnex_core::rmc::structures::RmcSerialize>::serialize(#name, writer)?;}
}));
quote!{
Self::#ident #pattern => {
<#repr_ty as rnex_core::rmc::structures::RmcSerialize>::serialize(&#descriminant, writer)?;
#inner
}
}
}));
quote! {
match self{
#match_content
}
Ok(())
}
}
pub fn rmc_generate_deserialize_enum(
enum_data: &DataEnum,
repr_ty: &Ident,
) -> proc_macro2::TokenStream {
let match_content = fold_tokenable(enum_data.variants.iter().map(|v| {
let ident = &v.ident;
let descriminant = &v
.discriminant
.as_ref()
.expect("every variant must have a descriminant to be a valid rmc struct")
.1;
let (pattern, fields) = variant_to_pattern_and_fields(v);
let inner = fold_tokenable(fields.iter().enumerate().map(|(i, f)| {
let ty = &f.ty;
let name = field_to_ident(&f, i);
quote! {let #name = <#ty as rnex_core::rmc::structures::RmcSerialize>::deserialize(reader)?;}
}));
quote! {
#descriminant => {
#inner
Self::#ident #pattern
}
}
}));
quote! {
let discriminant = <#repr_ty as rnex_core::rmc::structures::RmcSerialize>::deserialize(reader)?;
Ok(match discriminant{
#match_content
v => {
return Err(rnex_core::rmc::structures::Error::UnexpectedValue(v as u64))
}
})
}
}
pub fn rmc_serialize_enum(
enum_data: &DataEnum,
derive_input: &DeriveInput,
) -> (
proc_macro2::TokenStream,
proc_macro2::TokenStream,
Option<proc_macro2::TokenStream>,
Option<proc_macro2::TokenStream>,
) {
let repr_attr = derive_input.attrs.iter().find(|a| {
a.path().segments.len() == 1
&& a.path()
.segments
.first()
.is_some_and(|p| p.ident.to_string() == "repr")
});
let Some(repr_attr) = repr_attr else {
panic!("missing repr attribute");
};
let ty: Ident = repr_attr.parse_args().unwrap();
let serialize = rmc_generate_serialize_enum(&enum_data, &ty);
let deserialize = rmc_generate_deserialize_enum(&enum_data, &ty);
(serialize, deserialize, None, None)
}

10
macros/src/util.rs Normal file
View file

@ -0,0 +1,10 @@
use proc_macro2::TokenStream;
use quote::ToTokens;
// todo: return a wrapper struct implementing ToTokens over the iterator instead as to avoid unnescesary allocations with the token stream
pub fn fold_tokenable<T: ToTokens>(list: impl Iterator<Item = T>) -> TokenStream {
list.fold(TokenStream::new(), |mut s, i| {
i.to_tokens(&mut s);
s
})
}

View file

@ -210,6 +210,7 @@ pub async fn new_backend_connection(
mod test { mod test {
use crate::{VIRTUAL_PORT_INSECURE, VIRTUAL_PORT_SECURE}; use crate::{VIRTUAL_PORT_INSECURE, VIRTUAL_PORT_SECURE};
#[test]
fn test_virtual_port_correct() { fn test_virtual_port_correct() {
println!("{:?}", VIRTUAL_PORT_INSECURE); println!("{:?}", VIRTUAL_PORT_INSECURE);
println!("{:?}", VIRTUAL_PORT_SECURE); println!("{:?}", VIRTUAL_PORT_SECURE);

View file

@ -75,16 +75,16 @@ pub async fn start(param: ProxyStartupParam) {
return; return;
}; };
loop { 'a: loop {
tokio::select! { tokio::select! {
data = conn.recv() => { data = conn.recv() => {
let Some(data) = data else { let Some(data) = data else {
return; break 'a;
}; };
if let Err(e) = stream.send_buffer(&data[..]).await{ if let Err(e) = stream.send_buffer(&data[..]).await{
error!("error sending data to backend: {}", e); error!("error sending data to backend: {}", e);
return; break 'a;
} }
}, },
data = stream.read_buffer() => { data = stream.read_buffer() => {
@ -92,12 +92,12 @@ pub async fn start(param: ProxyStartupParam) {
Ok(d) => d, Ok(d) => d,
Err(e) => { Err(e) => {
error!("error reveiving data from backend: {}", e); error!("error reveiving data from backend: {}", e);
return; break 'a;
} }
}; };
if conn.send(data).await == None{ if conn.send(data).await == None{
return; break 'a;
} }
}, },
_ = sleep(Duration::from_secs(10)) => { _ = sleep(Duration::from_secs(10)) => {
@ -105,6 +105,7 @@ pub async fn start(param: ProxyStartupParam) {
} }
} }
} }
conn.close_connection().await;
}); });
} }
} }

View file

@ -447,7 +447,7 @@ mod test {
let bytes = &bytes[0x6..]; let bytes = &bytes[0x6..];
let header_data: [u8; 8] = bytes.try_into().unwrap(); let _: [u8; 8] = bytes.try_into().unwrap();
} }
#[test] #[test]

View file

@ -1,14 +0,0 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": ["config:recommended"],
"packageRules": [
{
"matchUpdateTypes": ["minor", "patch"],
"automerge": true
},
{
"matchUpdateTypes": ["major"],
"automerge": false
}
]
}

View file

@ -49,8 +49,10 @@ third-notif-param = []
v3-4-0 = ["v3-3-2", "third-notif-param", "rmc_struct_header"] v3-4-0 = ["v3-3-2", "third-notif-param", "rmc_struct_header"]
v3-5-0 = ["v3-4-0"] v3-5-0 = ["v3-4-0"]
v3-8-15 = ["v3-5-0"] v3-8-15 = ["v3-5-0"]
v3-10-22 = ["v3-8-15"]
v4-3-11 = ["v3-8-15"] v4-3-11 = ["v3-8-15"]
nx = ["big_pid"] nx = ["big_pid"]
splatoon = []
datastore = ["database-support", "v3-8-15", "dep:aws-sdk-s3", "dep:aws-config"] datastore = ["database-support", "v3-8-15", "dep:aws-sdk-s3", "dep:aws-config"]
database-support = ["dep:sqlx"] database-support = ["dep:sqlx"]

View file

@ -115,9 +115,16 @@ fn read_bounds_string<T: FromStr>(str: &str) -> Option<(T, T)> {
} }
fn check_bounds_str<T: FromStr + PartialOrd>(compare: T, str: &str) -> Option<bool> { fn check_bounds_str<T: FromStr + PartialOrd>(compare: T, str: &str) -> Option<bool> {
let bounds: (T, T) = read_bounds_string(str)?; if let Some(bounds) = read_bounds_string::<T>(str) {
return Some(bounds.0 <= compare && compare <= bounds.1);
Some(bounds.0 <= compare && compare <= bounds.1) }
if let Ok(val) = T::from_str(str) {
return Some(val == compare);
}
if str.is_empty() {
return Some(true);
}
None
} }
pub async fn broadcast_notification<T: AsRef<User>>( pub async fn broadcast_notification<T: AsRef<User>>(
@ -393,33 +400,33 @@ impl ExtendedMatchmakeSession {
return Ok(false); return Ok(false);
} }
if search_criteria #[cfg(feature = "splatoon")]
.attribs {
if search_criteria.attribs.get(0).is_some_and(|s| {
self.session
.attributes
.get(0) .get(0)
.map(|str| str.parse().ok()) .is_some_and(|a| s.0.contains(a))
.flatten() }) {
!= self.session.attributes.get(0).map(|v| *v)
{
return Ok(false); return Ok(false);
} }
if search_criteria if search_criteria.attribs.get(2).is_some_and(|s| {
.attribs self.session
.attributes
.get(2) .get(2)
.map(|str| str.parse().ok()) .is_some_and(|a| s.0.contains(a))
.flatten() }) {
!= self.session.attributes.get(2).map(|v| *v)
{
return Ok(false); return Ok(false);
} }
if search_criteria if search_criteria.attribs.get(3).is_some_and(|s| {
.attribs self.session
.attributes
.get(3) .get(3)
.map(|str| str.parse().ok()) .is_some_and(|a| s.0.contains(a))
.flatten() }) {
!= self.session.attributes.get(3).map(|v| *v)
{
return Ok(false); return Ok(false);
} }
}
Ok(true) Ok(true)
} }

View file

@ -26,6 +26,7 @@ use rnex_core::rmc::protocols::notifications::notification_types::{
}; };
use rnex_core::rmc::protocols::ranking::{Ranking, RawRanking, RawRankingInfo, RemoteRanking}; use rnex_core::rmc::protocols::ranking::{Ranking, RawRanking, RawRankingInfo, RemoteRanking};
use rnex_core::rmc::protocols::secure::{RawSecure, RawSecureInfo, RemoteSecure, Secure}; use rnex_core::rmc::protocols::secure::{RawSecure, RawSecureInfo, RemoteSecure, Secure};
use rnex_core::rmc::protocols::util::{RawUtility, RawUtilityInfo, RemoteUtility, Utility};
use rnex_core::rmc::response::ErrorCode; use rnex_core::rmc::response::ErrorCode;
use rnex_core::rmc::structures::any::Any; use rnex_core::rmc::structures::any::Any;
use rnex_core::rmc::structures::matchmake::{ use rnex_core::rmc::structures::matchmake::{
@ -39,9 +40,7 @@ use cfg_if::cfg_if;
use log::{error, info}; use log::{error, info};
use macros::rmc_struct; use macros::rmc_struct;
use rnex_core::prudp::socket_addr::PRUDPSockAddr; use rnex_core::prudp::socket_addr::PRUDPSockAddr;
use rnex_core::rmc::protocols::notifications::{ use rnex_core::rmc::protocols::notifications::{NotificationEvent, RemoteNotification};
self, Notification, NotificationEvent, RemoteNotification,
};
use rnex_core::rmc::protocols::ranking::{ use rnex_core::rmc::protocols::ranking::{
CompetitionRankingGetParam, CompetitionRankingScoreData, CompetitionRankingScoreInfo, CompetitionRankingGetParam, CompetitionRankingScoreData, CompetitionRankingScoreInfo,
}; };
@ -52,7 +51,7 @@ use rnex_core::rmc::structures::ranking::UploadCompetitionData;
use std::sync::{Arc, Weak}; use std::sync::{Arc, Weak};
use tokio::sync::{Mutex, RwLock}; use tokio::sync::{Mutex, RwLock};
use crate::rmc::structures::Error; use crate::rmc::structures::matchmake::MatchmakeSessionSearchCriteria;
cfg_if! { cfg_if! {
if #[cfg(feature = "datastore")] { if #[cfg(feature = "datastore")] {
@ -65,6 +64,7 @@ cfg_if! {
Matchmake, Matchmake,
NatTraversal, NatTraversal,
Ranking, Ranking,
Utility,
DataStore DataStore
} }
); );
@ -76,6 +76,7 @@ cfg_if! {
MatchmakeExt, MatchmakeExt,
Matchmake, Matchmake,
NatTraversal, NatTraversal,
Utility,
Ranking Ranking
} }
); );
@ -181,15 +182,13 @@ impl MatchmakeExtension for User {
Ok(Vec::new()) Ok(Vec::new())
} }
async fn update_progress_score(&self, gid: u32, progress: u8) -> Result<(), ErrorCode> {
#[cfg(feature = "v3-5-0")] #[cfg(feature = "v3-5-0")]
{ async fn update_progress_score(&self, gid: u32, progress: u8) -> Result<(), ErrorCode> {
let session = self.matchmake_manager.get_session(gid).await?; let session = self.matchmake_manager.get_session(gid).await?;
let mut session = session.lock().await; let mut session = session.lock().await;
session.session.progress_score = progress; session.session.progress_score = progress;
}
Ok(()) Ok(())
} }
@ -438,7 +437,7 @@ impl MatchmakeExtension for User {
async fn get_friend_notification_data( async fn get_friend_notification_data(
&self, &self,
ty: i32, _ty: i32,
) -> Result<Vec<NotificationEvent>, ErrorCode> { ) -> Result<Vec<NotificationEvent>, ErrorCode> {
Ok(vec![]) Ok(vec![])
} }
@ -499,7 +498,7 @@ impl MatchmakeExtension for User {
&self, &self,
gid: u32, gid: u32,
message: String, message: String,
dont_care_block_list: bool, _dont_care_block_list: bool,
//participation_count: u16, //participation_count: u16,
) -> Result<Vec<u8>, ErrorCode> { ) -> Result<Vec<u8>, ErrorCode> {
let sess = self.matchmake_manager.get_session(gid).await?; let sess = self.matchmake_manager.get_session(gid).await?;
@ -508,6 +507,38 @@ impl MatchmakeExtension for User {
Ok(sess.session.session_key.clone()) Ok(sess.session.session_key.clone())
} }
async fn auto_matchmake_with_search_criteria_postpone(
&self,
criteria: Vec<MatchmakeSessionSearchCriteria>,
gathering: Any,
join_message: String,
) -> Result<Any, ErrorCode> {
let session: MatchmakeSession = gathering
.try_get()
.map(|v| v.ok())
.flatten()
.ok_or(ErrorCode::Core_InvalidArgument)?;
println!("{:?}", criteria);
let session = self
.auto_matchmake_with_param_postpone(AutoMatchmakeParam {
matchmake_session: session,
additional_participants: vec![],
gid_for_participation_check: 0,
auto_matchmake_option: 0,
join_message,
participation_count: 0,
search_criteria: criteria,
target_gids: vec![],
})
.await?;
let any = Any::new(&session).map_err(|_| ErrorCode::Core_SystemError)?;
Ok(any)
}
} }
impl Matchmake for User { impl Matchmake for User {
@ -767,6 +798,12 @@ fn fetch_team_votes(fest_id: u32) -> Result<Vec<u32>, ErrorCode> {
}) })
} }
impl Utility for User {
async fn acquire_nex_unique_id(&self) -> Result<u64, ErrorCode> {
return Ok(rand::random());
}
}
impl Ranking for User { impl Ranking for User {
async fn competition_ranking_get_param( async fn competition_ranking_get_param(
&self, &self,

View file

@ -6,6 +6,7 @@ use rnex_core::rmc::structures::matchmake::{
use crate::rmc::protocols::notifications::NotificationEvent; use crate::rmc::protocols::notifications::NotificationEvent;
use crate::rmc::structures::any::Any; use crate::rmc::structures::any::Any;
use crate::rmc::structures::matchmake::MatchmakeSessionSearchCriteria;
#[rmc_proto(109)] #[rmc_proto(109)]
pub trait MatchmakeExtension { pub trait MatchmakeExtension {
@ -35,6 +36,13 @@ pub trait MatchmakeExtension {
&self, &self,
ty: i32, ty: i32,
) -> Result<Vec<NotificationEvent>, ErrorCode>; ) -> Result<Vec<NotificationEvent>, ErrorCode>;
#[method_id(15)]
async fn auto_matchmake_with_search_criteria_postpone(
&self,
criteria: Vec<MatchmakeSessionSearchCriteria>,
gathering: Any,
join_msg: String,
) -> Result<Any, ErrorCode>;
#[method_id(30)] #[method_id(30)]
async fn join_matchmake_session_ex( async fn join_matchmake_session_ex(
@ -58,6 +66,7 @@ pub trait MatchmakeExtension {
async fn get_playing_session(&self, pids: Vec<u32>) -> Result<Vec<()>, ErrorCode>; async fn get_playing_session(&self, pids: Vec<u32>) -> Result<Vec<()>, ErrorCode>;
#[method_id(34)] #[method_id(34)]
#[cfg(feature = "v3-5-0")]
async fn update_progress_score(&self, gid: u32, progress: u8) -> Result<(), ErrorCode>; async fn update_progress_score(&self, gid: u32, progress: u8) -> Result<(), ErrorCode>;
#[method_id(38)] #[method_id(38)]
async fn create_matchmake_session_with_param( async fn create_matchmake_session_with_param(

View file

@ -12,6 +12,7 @@ pub mod nintendo_notification;
pub mod notifications; pub mod notifications;
pub mod ranking; pub mod ranking;
pub mod secure; pub mod secure;
pub mod util;
use crate::result::ResultExtension; use crate::result::ResultExtension;
use crate::rmc::message::RMCMessage; use crate::rmc::message::RMCMessage;
@ -20,7 +21,6 @@ use crate::rmc::response::{ErrorCode, RMCResponse, RMCResponseResult};
use crate::rmc::structures; use crate::rmc::structures;
use crate::rmc::structures::RmcSerialize; use crate::rmc::structures::RmcSerialize;
use crate::util::{SendingBufferConnection, SplittableBufferConnection}; use crate::util::{SendingBufferConnection, SplittableBufferConnection};
use futures::FutureExt;
use log::{error, info}; use log::{error, info};
use std::collections::HashMap; use std::collections::HashMap;
use std::future::Future; use std::future::Future;

View file

@ -0,0 +1,9 @@
use macros::{method_id, rmc_proto};
use rnex_core::rmc::response::ErrorCode;
#[rmc_proto(110)]
pub trait Utility {
#[method_id(1)]
async fn acquire_nex_unique_id(&self) -> Result<u64, ErrorCode>;
}

View file

@ -43,60 +43,3 @@ impl Any {
}); });
} }
} }
#[cfg(test)]
mod test {
use crate::rmc::structures::{
RmcSerialize,
any::Any,
matchmake::{Gathering, MatchmakeSession},
};
#[test]
fn test() {
let any = Any {
name: "MatchmakeSession".into(),
data: [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 2, 0, 98, 0, 0, 0, 0, 0, 0, 0, 32, 0, 0,
0, 0, 0, 0, 0, 20, 0, 68, 111, 111, 114, 115, 32, 70, 114, 105, 101, 110, 100, 32,
73, 110, 118, 105, 116, 101, 0, 0, 0, 0, 0, 6, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0, 1, 2, 3, 0,
0, 0, 0, 0, 0, 0, 0, 0,
]
.into(),
};
println!("{}", hex::encode(&any.data));
let _: MatchmakeSession = any.try_get().unwrap().unwrap();
let sess = MatchmakeSession {
gathering: Gathering {
self_gid: 0,
owner_pid: 0,
host_pid: 0,
minimum_participants: 2,
maximum_participants: 2,
participant_policy: 98,
policy_argument: 0,
flags: 32,
state: 0,
description: "Doors Friend Invite".into(),
},
gamemode: 0,
attributes: [2, 3, 0, 0, 0, 0].into(),
open_participation: false,
matchmake_system_type: 2,
application_buffer: [1, 2, 3].into(),
participation_count: 0,
#[cfg(feature = "v3-5-0")]
progress_score: 0,
session_key: [].into(),
};
let any = Any::new(&sess).unwrap();
let sess2: MatchmakeSession = any.try_get().unwrap().unwrap();
assert_eq!(sess, sess2)
}
}

View file

@ -5,6 +5,8 @@ use rnex_core::rmc::structures::variant::Variant;
use rnex_core::PID; use rnex_core::PID;
use crate::rmc::structures::string_set::StringSet;
// rmc structure // rmc structure
#[derive(RmcSerialize, Debug, Clone, Default, PartialEq)] #[derive(RmcSerialize, Debug, Clone, Default, PartialEq)]
#[rmc_struct(0)] #[rmc_struct(0)]
@ -22,7 +24,7 @@ pub struct Gathering {
} }
// rmc structure // rmc structure
#[derive(RmcSerialize, Debug, Clone, Default)] #[derive(RmcSerialize, Debug, Clone, Default, PartialEq)]
#[rmc_struct(0)] #[rmc_struct(0)]
pub struct MatchmakeParam { pub struct MatchmakeParam {
pub params: Vec<(String, Variant)>, pub params: Vec<(String, Variant)>,
@ -30,7 +32,7 @@ pub struct MatchmakeParam {
cfg_if! { cfg_if! {
if #[cfg(feature = "v3-5-0")]{ if #[cfg(feature = "v3-5-0")]{
#[derive(RmcSerialize, Debug, Clone, Default)] #[derive(RmcSerialize, Debug, Clone, Default, PartialEq)]
#[rmc_struct(3)] #[rmc_struct(3)]
pub struct MatchmakeSession { pub struct MatchmakeSession {
//inherits from //inherits from
@ -75,7 +77,7 @@ cfg_if! {
#[derive(RmcSerialize, Debug, Clone)] #[derive(RmcSerialize, Debug, Clone)]
#[rmc_struct(3)] #[rmc_struct(3)]
pub struct MatchmakeSessionSearchCriteria { pub struct MatchmakeSessionSearchCriteria {
pub attribs: Vec<String>, pub attribs: Vec<StringSet<u32>>,
pub game_mode: String, pub game_mode: String,
pub minimum_participants: String, pub minimum_participants: String,
pub maximum_participants: String, pub maximum_participants: String,
@ -121,9 +123,27 @@ pub struct MatchmakeBlockListParam {
option_flag: u32, option_flag: u32,
} }
#[derive(RmcSerialize, Debug, Clone)] cfg_if! {
#[rmc_struct(0)] if #[cfg(feature = "v3-10-22")] {
pub struct JoinMatchmakeSessionParam { #[derive(RmcSerialize, Debug, Clone)]
#[rmc_struct(1)]
pub struct JoinMatchmakeSessionParam {
pub gid: u32,
pub additional_participants: Vec<PID>,
pub gid_for_participation_check: u32,
pub join_matchmake_session_open: u32,
pub join_matchmake_session_behavior: u8,
pub user_password: String,
pub system_password: String,
pub join_message: String,
pub participation_count: u16,
pub extra_participant: u16,
//pub block_list_param: MatchmakeBlockListParam
}
} else {
#[derive(RmcSerialize, Debug, Clone)]
#[rmc_struct(0)]
pub struct JoinMatchmakeSessionParam {
pub gid: u32, pub gid: u32,
pub additional_participants: Vec<PID>, pub additional_participants: Vec<PID>,
pub gid_for_participation_check: u32, pub gid_for_participation_check: u32,
@ -135,6 +155,8 @@ pub struct JoinMatchmakeSessionParam {
pub participation_count: u16, pub participation_count: u16,
//pub extra_participant: u16, //pub extra_participant: u16,
//pub block_list_param: MatchmakeBlockListParam //pub block_list_param: MatchmakeBlockListParam
}
}
} }
pub mod gathering_flags { pub mod gathering_flags {

View file

@ -13,12 +13,15 @@ pub enum Error {
Utf8(#[from] FromUtf8Error), Utf8(#[from] FromUtf8Error),
#[error("unexpected value: {0}")] #[error("unexpected value: {0}")]
UnexpectedValue(u64), UnexpectedValue(u64),
#[cfg(feature = "rmc_struct_header")]
#[error("version mismatch: {0}")] #[error("version mismatch: {0}")]
VersionMismatch(u8), VersionMismatch(u8),
#[error("an error occurred reading the station url")] #[error("an error occurred reading the station url")]
StationUrlInvalid, StationUrlInvalid,
#[error("error formatting text: {0}")] #[error("error formatting text: {0}")]
FormatError(#[from] fmt::Error), FormatError(#[from] fmt::Error),
#[error("uncategorized rmc error occurred: {0}")]
Other(Box<dyn std::error::Error + Send + Sync>),
} }
pub type Result<T> = std::result::Result<T, Error>; pub type Result<T> = std::result::Result<T, Error>;
@ -35,10 +38,11 @@ pub mod primitives;
pub mod qbuffer; pub mod qbuffer;
pub mod qresult; pub mod qresult;
pub mod ranking; pub mod ranking;
pub mod resultsrange;
pub mod rmc_struct; pub mod rmc_struct;
pub mod string; pub mod string;
pub mod string_set;
pub mod variant; pub mod variant;
pub mod resultsrange;
pub trait RmcSerialize { pub trait RmcSerialize {
fn serialize(&self, writer: &mut impl Write) -> Result<()>; fn serialize(&self, writer: &mut impl Write) -> Result<()>;
@ -66,6 +70,9 @@ pub trait RmcSerialize {
fn name() -> &'static str { fn name() -> &'static str {
"NoNameSpecified" "NoNameSpecified"
} }
fn version() -> Option<u8> {
None
}
} }
impl RmcSerialize for () { impl RmcSerialize for () {

View file

@ -0,0 +1,95 @@
use std::{collections::HashSet, hash::Hash, str::FromStr, string::ToString};
use rnex_core::rmc::structures::RmcSerialize;
#[derive(Debug, Clone)]
pub struct StringSet<T: FromStr + ToString + Eq>(pub HashSet<T>)
where
<T as FromStr>::Err: std::error::Error + Send + Sync + 'static;
impl<T: FromStr + ToString + Eq + Hash> PartialEq for StringSet<T>
where
<T as FromStr>::Err: std::error::Error + Send + Sync + 'static,
{
fn eq(&self, other: &Self) -> bool {
self.0.iter().eq(&other.0)
}
}
impl<T: FromStr + ToString + Eq + Hash> ToString for StringSet<T>
where
<T as FromStr>::Err: std::error::Error + Send + Sync + 'static,
{
fn to_string(&self) -> String {
self.0
.iter()
.map(ToString::to_string)
.reduce(|a, b| format!("{}|{}", a, b))
.unwrap_or(String::new())
}
}
impl<T: FromStr + ToString + Eq + Hash> FromStr for StringSet<T>
where
<T as FromStr>::Err: std::error::Error + Send + Sync + 'static,
{
type Err = Box<dyn std::error::Error + Send + Sync>;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(Self(
s.split("|")
.filter(|v| !v.is_empty())
.map(T::from_str)
.try_fold(
HashSet::new(),
|mut a, b| -> Result<HashSet<T>, Self::Err> {
a.insert(b.map_err(Box::new)?);
Ok(a)
},
)?,
))
}
}
impl<T: FromStr + ToString + Eq + Hash> RmcSerialize for StringSet<T>
where
<T as FromStr>::Err: std::error::Error + Send + Sync + 'static,
{
fn deserialize(reader: &mut impl std::io::prelude::Read) -> super::Result<Self>
where
Self: Sized,
{
Self::from_str(&String::deserialize(reader)?).map_err(super::Error::Other)
}
fn serialize(&self, writer: &mut impl std::io::prelude::Write) -> super::Result<()> {
self.to_string().serialize(writer)
}
fn serialize_write_size(&self) -> super::Result<u32> {
self.to_string().serialize_write_size()
}
}
#[cfg(test)]
mod test {
use std::str::FromStr;
use crate::rmc::structures::string_set::StringSet;
#[test]
fn test() {
let str_val = "0|100|200|10|110|210|20|120|220|30|130|230";
let set: StringSet<u32> = StringSet::from_str(str_val).unwrap();
let string_2 = set.to_string();
let reset: StringSet<u32> = StringSet::from_str(&string_2).unwrap();
for val in &set.0 {
if !reset.0.contains(&val) {
panic!("sets arent equivalent");
}
}
let _: StringSet<u32> = StringSet::from_str("").unwrap();
let _: StringSet<u32> = StringSet::from_str("10").unwrap();
}
}

View file

@ -3,7 +3,7 @@ use rnex_core::rmc::structures;
use rnex_core::rmc::structures::{Result, RmcSerialize}; use rnex_core::rmc::structures::{Result, RmcSerialize};
use std::io::{Read, Write}; use std::io::{Read, Write};
#[derive(Debug, Clone, Default)] #[derive(Debug, Clone, Default, PartialEq)]
pub enum Variant { pub enum Variant {
#[default] #[default]
None, None,

View file

@ -12,7 +12,7 @@ pub struct ConnectionInitData {
mod test { mod test {
use std::{ use std::{
io::Cursor, io::Cursor,
net::{IpAddr, Ipv4Addr, SocketAddr, SocketAddrV4}, net::{Ipv4Addr, SocketAddr, SocketAddrV4},
}; };
use crate::{ use crate::{

13
test-all.sh Executable file
View file

@ -0,0 +1,13 @@
#!/usr/bin/env bash
set -euo pipefail
EDITIONS=$(yq ea "." editions.yaml | yq 'keys[]')
IFS=$'\n'
while IFS=$'\n' read -r EDITION; do
if [[ $(yq ea ".$EDITION.include-in-checkall" editions.yaml) == "true" ]]
then
export EDITION
./test-edition.sh $EDITION
fi
done <<< "$EDITIONS"