Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature Request: Be able to restrict access to server if not connecting with a brand new save file. #43

Open
Clutz450 opened this issue Nov 11, 2022 · 12 comments

Comments

@Clutz450
Copy link

Clutz450 commented Nov 11, 2022

The short summary is I would like it if I could have an option in the settings to make it so that only people with 0 power moon will be able to connect.

Long story is I am planning on doing a stream on Twitch where I try to collect all 999 power moons and would like it if random people could join me and help me in my quest. What I don't want is for people to connect to my server with a save file that is later in the game than I am currently at and have them sync power moon to me in areas that i shouldn't have access to yet. My idea for this is to have the server check how many power moons a person has and if it's a number greater than 0 then it will refuse the connection. This way when random people join me, once they connect with their brand new save file, all power moons will be synced to them. The only thing a new person has to do to quickly catch up to where I am at is to enter each kingdom and throw cappy on the odyssey.

I know nothing about coding or how any of this works so this is just an idea on how I think it could work. If anyone else has any better ideas or the knowhow to make it happen, I would really appreciate it. Thanks.

@Istador
Copy link
Contributor

Istador commented Nov 12, 2022

What I don't want is for people to connect to my server with a save file that is later in the game than I am currently at and have them sync power moon to me in areas that i shouldn't have access to yet.

Is that how it works? Have you tested this?
AFAIK the client software only sends moons to the server that were collected while being connected to the server, so someone that has a 100% save already should not be triggering moon collect packets for you. So when you host your own fresh server, it should have and keep an empty moon list, even when someone with a 100% file connects. (I'm not sure about this and haven't tested this.)

Though someone with a non-100% savefile could collect moons in later kingdoms while connected to your server. Preventing this is likely very difficult, because the client or server would need to know which moons are collectible under which conditions and you would need to tell the server somehow to limit it on your specific players progress and not that of other players.


Limiting connections based on existing moon count seems impossible, without changing the SMOO communication protocol between client and server, because the server afaik doesn't get to know the total moon count of the players, but only what they collect on it (and what the server sends to them from others).

Even if it would keep track of it, you'd also need to persist it somehow, otherwise a server crash/restart would prevent even you from rejoining the server again once you collected a single moon.

Another approach could be to require all clients to start in Cap Kingdom with a brand new save (starting with the first scenario). This is something that is already detected by the server and kept in the metadata for each player, to prevent sending them moons before reaching Cascade Kingdom. But the metadata doesn't survive server restarts currently, so you shouldn't make block decisions based on it (same reason as in the last paragraph).


Maybe a kingdom block feature could be feasible for your use case. Before your stream you block all kingdoms but Cap Kingdom. When someone sends a game packet for another kingdom (tries to load into it), they get kicked of the server (with a game crash). Before YOU enter the next kingdom, you can manually execute a server command to unlock the next kingdom for everyone.

@Clutz450
Copy link
Author

Clutz450 commented Mar 19, 2023

Maybe a kingdom block feature could be feasible for your use case. Before your stream you block all kingdoms but Cap Kingdom. When someone sends a game packet for another kingdom (tries to load into it), they get kicked of the server (with a game crash). Before YOU enter the next kingdom, you can manually execute a server command to unlock the next kingdom for everyone.

Sorry for the very late reply as I kinda fell out of SMOO but am looking to get back in and revisit my stream idea. That Kingdom Block feature sounds like something I could use. Is this something that I can already do or were you just suggesting it as a feature request that would satisfy my needs? Thank you.

@Clutz450 Clutz450 reopened this Mar 19, 2023
@Clutz450
Copy link
Author

Sorry, I fat fingered the close issue button on my phone. Lol.

@Istador
Copy link
Contributor

Istador commented Mar 19, 2023

Is this something that I can already do or were you just suggesting it as a feature request that would satisfy my needs? Thank you.

That was a suggestion for a simpler/smaller feature request that would be easier to implement:

  • Add one new server command to add/remove the blocked stages.
  • [Optional] load & save the blocked stages from & to the settings.json (to survive server crashes/restarts).
  • Check in the packet handler for the GamePacket that if gamePacket.Stage is in the list of blocked stages, then kick the player out (with the existing crash functionality).

If we want to add this feature to the server permanently and not just for a one-time event, I'd suggest changing/extending the ban command for this:

  • Changing ban <player-name> to ban player <player-name> (which currently adds the profile ID and the IPv4 address to the ban list).
  • Adding ban profile <profile-id>.
  • Adding ban ip <ip-address>.
  • Adding ban stage <stage-name>.
  • Adding unban profile <profile-id>.
  • Adding unban ip <ip-address>.
  • Adding unban stage <stage-name>
  • (unban player <player-name> should not be added, because the player name isn't added to the ban lists currently.)

@Crazalu
Copy link

Crazalu commented Mar 22, 2023

Would stage name be its alias, and how would it work if someone connected to the server with the reconnect button(if the reconnect button miraculously)were in a subarea of a kingdom? (ex. CapWorldHomeStage is banned, but they connect inside CapWorldTowerStage)

@Istador
Copy link
Contributor

Istador commented Mar 22, 2023

The saved stage name should be the exact name of the stage and not an alias. But the server commands should support both forms like the send and sendall commands.

Blocking only based on the overworld stages should be enough to block most possible abuses. Because loading into the game, traveling with the odyssey or using a picture teleport will all load you into an overworld stage.

{ "cap", "CapWorldHomeStage" },
{ "cascade", "WaterfallWorldHomeStage" },
{ "sand", "SandWorldHomeStage" },
{ "lake", "LakeWorldHomeStage" },
{ "wooded", "ForestWorldHomeStage" },
{ "cloud", "CloudWorldHomeStage" },
{ "lost", "ClashWorldHomeStage" },
{ "metro", "CityWorldHomeStage" },
{ "sea", "SeaWorldHomeStage" },
{ "snow", "SnowWorldHomeStage" },
{ "lunch", "LavaWorldHomeStage" },
{ "ruined", "BossRaidWorldHomeStage" },
{ "bowser", "SkyWorldHomeStage" },
{ "moon", "MoonWorldHomeStage" },
{ "mush", "PeachWorldHomeStage" },
{ "dark", "Special1WorldHomeStage" },
{ "darker", "Special2WorldHomeStage" },
{ "odyssey", "HomeShipInsideStage" },

But as you mentioned only-connecting when already in a sub-area of a blocked kingdom could be a way around it (but they can't get out without disconnecting first). Though we could also block/unblock all sub-areas when blocking a kingdom based on an alias. The data which stage is part of which kingdom is already there (in my PR):

{ "CapWorldHomeStage" , "cap" },
{ "CapWorldTowerStage" , "cap" },
{ "FrogSearchExStage" , "cap" },
{ "PoisonWaveExStage" , "cap" },
{ "PushBlockExStage" , "cap" },
{ "RollingExStage" , "cap" },
{ "WaterfallWorldHomeStage" , "cascade" },
{ "TrexPoppunExStage" , "cascade" },
{ "Lift2DExStage" , "cascade" },
{ "WanwanClashExStage" , "cascade" },
{ "CapAppearExStage" , "cascade" },
{ "WindBlowExStage" , "cascade" },
{ "SandWorldHomeStage" , "sand" },
{ "SandWorldShopStage" , "sand" },
{ "SandWorldSlotStage" , "sand" },
{ "SandWorldVibrationStage" , "sand" },
{ "SandWorldSecretStage" , "sand" },
{ "SandWorldMeganeExStage" , "sand" },
{ "SandWorldKillerExStage" , "sand" },
{ "SandWorldPressExStage" , "sand" },
{ "SandWorldSphinxExStage" , "sand" },
{ "SandWorldCostumeStage" , "sand" },
{ "SandWorldPyramid000Stage" , "sand" },
{ "SandWorldPyramid001Stage" , "sand" },
{ "SandWorldUnderground000Stage" , "sand" },
{ "SandWorldUnderground001Stage" , "sand" },
{ "SandWorldRotateExStage" , "sand" },
{ "MeganeLiftExStage" , "sand" },
{ "RocketFlowerExStage" , "sand" },
{ "WaterTubeExStage" , "sand" },
{ "LakeWorldHomeStage" , "lake" },
{ "LakeWorldShopStage" , "lake" },
{ "FastenerExStage" , "lake" },
{ "TrampolineWallCatchExStage" , "lake" },
{ "GotogotonExStage" , "lake" },
{ "FrogPoisonExStage" , "lake" },
{ "ForestWorldHomeStage" , "wooded" },
{ "ForestWorldWaterExStage" , "wooded" },
{ "ForestWorldTowerStage" , "wooded" },
{ "ForestWorldBossStage" , "wooded" },
{ "ForestWorldBonusStage" , "wooded" },
{ "ForestWorldCloudBonusExStage" , "wooded" },
{ "FogMountainExStage" , "wooded" },
{ "RailCollisionExStage" , "wooded" },
{ "ShootingElevatorExStage" , "wooded" },
{ "ForestWorldWoodsStage" , "wooded" },
{ "ForestWorldWoodsTreasureStage" , "wooded" },
{ "ForestWorldWoodsCostumeStage" , "wooded" },
{ "PackunPoisonExStage" , "wooded" },
{ "AnimalChaseExStage" , "wooded" },
{ "KillerRoadExStage" , "wooded" },
{ "CloudWorldHomeStage" , "cloud" },
{ "FukuwaraiKuriboStage" , "cloud" },
{ "Cube2DExStage" , "cloud" },
{ "ClashWorldHomeStage" , "lost" },
{ "ClashWorldShopStage" , "lost" },
{ "ImomuPoisonExStage" , "lost" },
{ "JangoExStage" , "lost" },
{ "CityWorldHomeStage" , "metro" },
{ "CityWorldMainTowerStage" , "metro" },
{ "CityWorldFactoryStage" , "metro" },
{ "CityWorldShop01Stage" , "metro" },
{ "CityWorldSandSlotStage" , "metro" },
{ "CityPeopleRoadStage" , "metro" },
{ "PoleGrabCeilExStage" , "metro" },
{ "TrexBikeExStage" , "metro" },
{ "PoleKillerExStage" , "metro" },
{ "Note2D3DRoomExStage" , "metro" },
{ "ShootingCityExStage" , "metro" },
{ "CapRotatePackunExStage" , "metro" },
{ "RadioControlExStage" , "metro" },
{ "ElectricWireExStage" , "metro" },
{ "Theater2DExStage" , "metro" },
{ "DonsukeExStage" , "metro" },
{ "SwingSteelExStage" , "metro" },
{ "BikeSteelExStage" , "metro" },
{ "SnowWorldHomeStage" , "snow" },
{ "SnowWorldTownStage" , "snow" },
{ "SnowWorldShopStage" , "snow" },
{ "SnowWorldLobby000Stage" , "snow" },
{ "SnowWorldLobby001Stage" , "snow" },
{ "SnowWorldRaceTutorialStage" , "snow" },
{ "SnowWorldRace000Stage" , "snow" },
{ "SnowWorldRace001Stage" , "snow" },
{ "SnowWorldCostumeStage" , "snow" },
{ "SnowWorldCloudBonusExStage" , "snow" },
{ "IceWalkerExStage" , "snow" },
{ "IceWaterBlockExStage" , "snow" },
{ "ByugoPuzzleExStage" , "snow" },
{ "IceWaterDashExStage" , "snow" },
{ "SnowWorldLobbyExStage" , "snow" },
{ "SnowWorldRaceExStage" , "snow" },
{ "SnowWorldRaceHardExStage" , "snow" },
{ "KillerRailCollisionExStage" , "snow" },
{ "SeaWorldHomeStage" , "sea" },
{ "SeaWorldUtsuboCaveStage" , "sea" },
{ "SeaWorldVibrationStage" , "sea" },
{ "SeaWorldSecretStage" , "sea" },
{ "SeaWorldCostumeStage" , "sea" },
{ "SeaWorldSneakingManStage" , "sea" },
{ "SenobiTowerExStage" , "sea" },
{ "CloudExStage" , "sea" },
{ "WaterValleyExStage" , "sea" },
{ "ReflectBombExStage" , "sea" },
{ "TogezoRotateExStage" , "sea" },
{ "LavaWorldHomeStage" , "lunch" },
{ "LavaWorldUpDownExStage" , "lunch" },
{ "LavaBonus1Zone" , "lunch" },
{ "LavaWorldShopStage" , "lunch" },
{ "LavaWorldCostumeStage" , "lunch" },
{ "ForkExStage" , "lunch" },
{ "LavaWorldExcavationExStage" , "lunch" },
{ "LavaWorldClockExStage" , "lunch" },
{ "LavaWorldBubbleLaneExStage" , "lunch" },
{ "LavaWorldTreasureStage" , "lunch" },
{ "GabuzouClockExStage" , "lunch" },
{ "CapAppearLavaLiftExStage" , "lunch" },
{ "LavaWorldFenceLiftExStage" , "lunch" },
{ "BossRaidWorldHomeStage" , "ruined" },
{ "DotTowerExStage" , "ruined" },
{ "BullRunExStage" , "ruined" },
{ "SkyWorldHomeStage" , "bowser" },
{ "SkyWorldShopStage" , "bowser" },
{ "SkyWorldCostumeStage" , "bowser" },
{ "SkyWorldCloudBonusExStage" , "bowser" },
{ "SkyWorldTreasureStage" , "bowser" },
{ "JizoSwitchExStage" , "bowser" },
{ "TsukkunRotateExStage" , "bowser" },
{ "KaronWingTowerStage" , "bowser" },
{ "TsukkunClimbExStage" , "bowser" },
{ "MoonWorldHomeStage" , "moon" },
{ "MoonWorldCaptureParadeStage" , "moon" },
{ "MoonWorldWeddingRoomStage" , "moon" },
{ "MoonWorldKoopa1Stage" , "moon" },
{ "MoonWorldBasementStage" , "moon" },
{ "MoonWorldWeddingRoom2Stage" , "moon" },
{ "MoonWorldKoopa2Stage" , "moon" },
{ "MoonWorldShopRoom" , "moon" },
{ "MoonWorldSphinxRoom" , "moon" },
{ "MoonAthleticExStage" , "moon" },
{ "Galaxy2DExStage" , "moon" },
{ "PeachWorldHomeStage" , "mush" },
{ "PeachWorldShopStage" , "mush" },
{ "PeachWorldCastleStage" , "mush" },
{ "PeachWorldCostumeStage" , "mush" },
{ "FukuwaraiMarioStage" , "mush" },
{ "DotHardExStage" , "mush" },
{ "YoshiCloudExStage" , "mush" },
{ "PeachWorldPictureBossMagmaStage" , "mush" },
{ "RevengeBossMagmaStage" , "mush" },
{ "PeachWorldPictureGiantWanderBossStage" , "mush" },
{ "RevengeGiantWanderBossStage" , "mush" },
{ "PeachWorldPictureBossKnuckleStage" , "mush" },
{ "RevengeBossKnuckleStage" , "mush" },
{ "PeachWorldPictureBossForestStage" , "mush" },
{ "RevengeForestBossStage" , "mush" },
{ "PeachWorldPictureMofumofuStage" , "mush" },
{ "RevengeMofumofuStage" , "mush" },
{ "PeachWorldPictureBossRaidStage" , "mush" },
{ "RevengeBossRaidStage" , "mush" },
{ "Special1WorldHomeStage" , "dark" },
{ "Special1WorldTowerStackerStage" , "dark" },
{ "Special1WorldTowerBombTailStage" , "dark" },
{ "Special1WorldTowerFireBlowerStage" , "dark" },
{ "Special1WorldTowerCapThrowerStage" , "dark" },
{ "KillerRoadNoCapExStage" , "dark" },
{ "PackunPoisonNoCapExStage" , "dark" },
{ "BikeSteelNoCapExStage" , "dark" },
{ "ShootingCityYoshiExStage" , "dark" },
{ "SenobiTowerYoshiExStage" , "dark" },
{ "LavaWorldUpDownYoshiExStage" , "dark" },
{ "Special2WorldHomeStage" , "darker" },
{ "Special2WorldLavaStage" , "darker" },
{ "Special2WorldCloudStage" , "darker" },
{ "Special2WorldKoopaStage" , "darker" },

Istador added a commit to Istador/SmoOnlineServer that referenced this issue Mar 24, 2023
To kick players from the server when they enter a banned stage.

<stage-name> can also be a kingdom alias, which bans/unbans all stages in that kingdom.

Because we aren't banning the player, d/c them would be no good, because of the client auto reconnect.
Instead send them the crash and ignore all packets by them until they d/c on their own.

This is an alternative solution for issue Sanae6#43.
@Istador
Copy link
Contributor

Istador commented Mar 24, 2023

I've implemented a solution for this in PR #48.

A new release w/ docker image and binary files based on master including all my other unmerged changes: https://github.com/Istador/SmoOnlineServer/releases/tag/1.0.4-rcl.6

@Clutz450
Copy link
Author

I've implemented a solution for this in PR #48.

A new release w/ docker image and binary files based on master including all my other unmerged changes: https://github.com/Istador/SmoOnlineServer/releases/tag/1.0.4-rcl.6

This is so amazing of you to do. Thank you. I'll try and test it out here soon. What do you think the likelihood is that your changes will be put into the main server?

@Istador
Copy link
Contributor

Istador commented Apr 12, 2023

What do you think the likelihood is that your changes will be put into the main server?

IDK.

Except for the ban stage command I think my changes and bugfixes are reasonable enough on their own to include in this repo. Regarding the bug that I fix with 11c291c I briefly messaged with Sanae on Discord about it.

The ban stage command is a bit of a special use case, which I can understand not wanting to merge (which is why I added the commit bf0bfae at last, to easily not merge it).

I'm not even sure if there ever will be an official 1.0.4 release that will contain this, even if merged. Version 1.0.3 was released more than a half year ago despite 5 of my pull requests being merged since then.

The sporadic development focus currently seems to be on SMOO 2.0, which will likely make this repository obsolete.
(But because from my perspective there's no time plan for it and it's not an open development, I find it still important to maintain the current public SMOO software stack.)

Sanae6 added a commit that referenced this issue Sep 5, 2023
* rename command: `ban ...` => `ban player ...`

To enable adding other subcommands starting with `ban`.

Moving ban list and crash related code into its own class to tidy the Program class up.

Change Id values of the crash cmds, to fit into the 16 byte max length imposed by ChangeStagePacket.IdSize.

* add command: `ban ip <ipv4-address>`

To add an IPv4 address to the ban list.

* add command: `ban profile <profile-id>`

To add a profile ID to the ban list.

* add command: `unban ip <ipv4-address>`

To remove a banned IPv4 address from the ban list.

* add command: `unban profile <profile-id>`

To remove a banned profile ID from the ban list.

* add commands: `ban enable` and `ban disable`

To set the value of `BanList.Enabled` to `true` or `false` without editing the `settings.json` file.

* add command: `ban list`

To show the current ban list settings.

* fix: actually working ban functionality

Changes:
- ignore new sockets from banned IP addresses way earlier.
- ignore all packets by banned profiles.

Intentionally keeping the connection open instead of d/c banned clients.
This is to prevent endless server logs due to automatically reconnecting clients.

Before:
Reconnecting clients aren't entering `ClientJoined` and therefore the d/c is only working on first connections.
Effectively banned clients got a d/c and then automatically reconnected again without getting a d/c again.
Therefore allowing them to play normally.

* use SortedSet instead of List for settings

To enforce unique entries and maintain a stable order inside of the `settings.json`.

* add commands: `ban stage <stage-name>` and `unban stage <stage-name>`

To kick players from the server when they enter a banned stage.

<stage-name> can also be a kingdom alias, which bans/unbans all stages in that kingdom.

Because we aren't banning the player, d/c them would be no good, because of the client auto reconnect.
Instead send them the crash and ignore all packets by them until they d/c on their own.

This is an alternative solution for issue #43.

* Update Server.cs

---------

Co-authored-by: Sanae <[email protected]>
@Istador
Copy link
Contributor

Istador commented Sep 6, 2023

It was merged and is part of the new 1.0.4 release from today.

piplup55 pushed a commit to TeamPiplup/SmoOnlineServer that referenced this issue Sep 28, 2023
To kick players from the server when they enter a banned stage.

<stage-name> can also be a kingdom alias, which bans/unbans all stages in that kingdom.

Because we aren't banning the player, d/c them would be no good, because of the client auto reconnect.
Instead send them the crash and ignore all packets by them until they d/c on their own.

This is an alternative solution for issue Sanae6#43.
@ktmitton
Copy link

I've been thinking about this for a couple weeks, and wanted to throw out an idea. It's not complete yet, but I think it's far enough along to give the general idea if you check out my fork at https://github.com/ktmitton/SmoOnlineServer

In this, I set up the TCP server to work within the asp.net lifecycle as a HostedService, and I split the work into three different projects:

  • SuperMarioOdysseyOnline.Server.Core - This contains all the most basic code for starting up the TCP server as the HostedService, and gives the framework through which derived projects can work
  • SuperMarioOdysseyOnline.Server.Lobby - This contains standard ILobby implementations (HideAndSeek, Coop/ShineSync)
  • SuperMarioOdysseyOnline.Server.Basic - This is the most basic setup of a server that leverages the prior two projects. All it really does and specify the lobby configuration in the appsettings.Development.json, then calls builder.AddSuperMarioOdysseyOnline() and app.MapDefaultLobbies() in the Program.cs, that's enough to standup the server.

Using this approach, I think it gives us the following benefits:

  1. It puts a separation between the code that sends/receives packets and the code that decides what to do with them, which allows us to implement newer versions and stay backwards compatible
  2. It allows us to create different update strategies for players, so for instance if somebody has a lot of latency, we can opt to send them updates on further away players at longer intervals, and save them some bandwidth
  3. It allows people to extend what we've done by creating new game modes by, bare-minimum, implementing their own kind of ILobby

Number 3 in particular addresses this issue because we could implement an ILobby, or update the current Coop lobby, so it could auto-track the "leader", unlocking zones as the leader unlocks it. So auto-manage the banned stages list, but without needed to adjust how the basic connection code works, dependency injection handles the magic of getting the received data where it needs to go.

What do you think?

@ktmitton
Copy link

Oh, running in the asp.net lifecycle, it can also make docker images smaller because we should be able to just leverage the basic microsoft alpine images, and they'll automatically start up the webserver.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants