Skip to content

feat(matchmake-extension): Implement blocklist functionality#58

Open
Fujimon8142 wants to merge 5 commits intoPretendoNetwork:mainfrom
Fujimon8142:add-block-feature
Open

feat(matchmake-extension): Implement blocklist functionality#58
Fujimon8142 wants to merge 5 commits intoPretendoNetwork:mainfrom
Fujimon8142:add-block-feature

Conversation

@Fujimon8142
Copy link

@Fujimon8142 Fujimon8142 commented Oct 20, 2025

Addresses #48 ?

Changes:

My primary motivation for implementing this was to add a block feature to "Steel Diver: Sub Wars." This PR introduces the necessary components for blocklist functionality within the MatchmakeExtension protocol.

The changes include:

  • Database Schema: Added the matchmaking.block_lists table to store user block relationships.

  • Database Functions: Implemented functions (AddToBlockList, RemoveFromBlockList, GetBlockList, GetBlockedByList) in the database package to interact with the new table.

  • RMC Handlers: Added handlers for the AddToBlockList, GetMyBlockList, and RemoveFromBlockList methods. These are now registered in the common protocol.

  • Session Search Integration: Modified FindMatchmakeSessionBySearchCriteria to exclude sessions hosted by blocked users or users who have blocked the searcher.

Personal Note:
I am not deeply familiar with the official NEX specifications or the project's internal standards for consistency and maintainability, so I'm unsure if this code fully meets requirements. To be honest, I have very little confidence.

I did test the functionality using two 3DS consoles with "Steel Diver: Sub Wars."

Update dependencies to resolve compilation errors related to older versions of nex-go and nex-protocols-go (e.g., types.UInt64, sourceAccount.RequiresTokenAuth).

nex-protocols-go is updated to pseudo-version v2.2.2-0.20250908132726-4a71a019af39 as the latest tagged release did not include the necessary fixes.
Adds the `matchmaking.block_lists` table schema to store user block relationships.
Implements database functions required for blocklist operations:
- AddToBlockList
- RemoveFromBlockList
- GetBlockList
- GetBlockedByList
Implements RMC handlers for MatchmakeExtension blocklist methods:
- AddToBlockList
- RemoveFromBlockList
- GetMyBlockList

Registers these handlers in the common protocol and adds corresponding `OnAfter...` callback definitions.
Modifies `FindMatchmakeSessionBySearchCriteria` to exclude sessions where the owner has blocked the searching user, or where the searching user has blocked the owner.
Utilizes the newly added `GetBlockList` and `GetBlockedByList` functions.
@Fujimon8142 Fujimon8142 changed the title feat(matchmake): Implement blocklist functionality feat(matchmake-extension): Implement blocklist functionality Oct 20, 2025
@jonbarrow
Copy link
Member

Thank you for the work! I'll give this a proper review soon (doing some other stuff at the moment), but I wanted to touch very quickly on this part:

I am not deeply familiar with the official NEX specifications

The way this worked officially is that the block lists of users are not typically checked during match searching, and are instead checked at join time. This is typically safe, since there's no real way to know who is in a session until you join it so this can't be abused to still follow certain users. However it does mean that the client MAY attempt, and fail, to join many sessions if the user has many blocked users. It seems like Nintendo realized this limitation at some point, since they added blockListParam to MatchmakeSessionSearchCriteria in NEX 4, which presumably also filters blocked users at the search level

These are my (current) Notion notes from when I was testing things prior to the shutdown:

Blocking

Users can block each other to prevent them from interacting during some in-game features. However there seems to be certain times where the user block list is not checked, and some cases where the block list is checked but no error is thrown.

When using search methods to find sessions by a specific user (such as with MatchmakeExtension::GetPlayingSession), always return empty lists when searching for players who are blocked by the caller. Do not return empty lists for searching for sessions of users the caller has blocked. No error is thrown in these cases.

When browsing for sessions in general (such as with MatchmakeExtension::BrowseMatchmakeSession), the block lists of both the calling user and the partipants of the session are not checked. No error is thrown in these cases. Return sessions which may have participants who are blocked by the caller, or that the caller has blocked.

When attempting to join a session (such as with MatchmakeExtension::JoinMatchmakeSession), the block lists of both the calling user and the partipants of the session are checked. Throw RendezVous::DeniedByParticipants if the caller is blocked by any participant of the session. Throw RendezVous::ParticipantInBlackList if the caller has blocked any participants of the session.

MatchmakeBlockListParam seems to have been added in NEX 4. Unknown usage. Presumably filters using the block lists at search time rather than join time.

@Fujimon8142
Copy link
Author

​Thank you for the detailed feedback! I'll try to revise the code based on your comments when I have some time.

@Fujimon8142
Copy link
Author

Thank you for the clear explanation. I understand now.
​So, just to confirm my next steps:
Should I revert the commit feat(matchmake-extension): exclude blocked users from session search and then implement the block check within the session join methods?
​I believe the relevant methods are JoinMatchmakeSession, JoinMatchmakeSessionEx, and JoinMatchmakeSessionWithParam, where I'll need to throw ParticipantInBlackList or DeniedByParticipants as you described. I'm not entirely sure which of these Join methods "Steel Diver: Sub Wars" actually calls, as I'm at school right now and can't check, but I plan to investigate when I get home.
​Does this sound like the correct approach?

@jonbarrow
Copy link
Member

Yes, ideally commit 2612175 should be reverted and the following changes should be made:

  • GetPlayingSession, GetSimplePlayingSession and FindCommunityByParticipant should be updated such that gatherings are not in the returned results if the caller is blocked by any of the participants of the gathering. For example, if the results of these functions would normally return a gathering list such as [ 1, 2, 3, 4 ], but there is a user in gathering 3 which has blocked the calling user, the data returned to the caller would be [ 1, 2, 4 ]. For some reason the block list does not seem to be checked in GetDetailedParticipants, which sort of defeats the purpose in my opinion, but it seems to be how Nintendo wanted it. That method isn't supported by us at the moment anyway, so it's fine
  • None of the gathering search functions should check block statuses. If a gathering search such as BrowseMatchmakeSession would normally return a gathering that includes a user that has blocked the caller, it is ALWAYS returned anyway
  • The gathering join methods need to check the block lists. If the caller is blocked by ANY participant of the gathering, throw DeniedByParticipants. If the caller has any of the participants in their block list, throw ParticipantInBlackList. This applies to all join methods

I know your use case is Steel Diver: Sub Wars, but when adding features to this library we do ask that they be feature-complete (within reason), so you may have to touch methods Steel Diver: Sub Wars does not use for this PR

@jonbarrow
Copy link
Member

Also, if you were interested in fully closing #48 then UpdatePrivacySetting would also need to be implemented. The way privacy settings work is very similar to block lists:

  • If a user sets onlineStatus to false, then the results of GetPlayingSession and GetSimplePlayingSession act as if the caller is blocked by the hidden user (their results are removed from the results list)
  • If a user sets participationCommunity to false, then the results of FindCommunityByParticipant act as if the caller is blocked by the hidden user (their results are removed from the results list)
  • All users have both settings set to true (visible) by default
  • If a caller is friends with the hidden user, the visibility status is ignored

@Fujimon8142
Copy link
Author

Thank you for the clear instructions! I'll get to work on it when I get home.

@Fujimon8142
Copy link
Author

I have a quick question to clarify my understanding of the intended behavior for MatchmakeExtension::GetPlayingSession. In your previous comment, you mentioned two points that seem potentially contradictory to me regarding how it should handle cases where the caller has blocked the user being searched for:

  1. "always return empty lists when searching for players who are blocked by the caller."
  2. "Do not return empty lists for searching for sessions of users the caller has blocked."

Could you please clarify the correct behavior for GetPlayingSession specifically when the caller has blocked the user whose session they are trying to find? Should the session be included in the results, or should the list be empty in this case?

@jonbarrow
Copy link
Member

jonbarrow commented Oct 27, 2025

I have a quick question to clarify my understanding of the intended behavior for MatchmakeExtension::GetPlayingSession. In your previous comment, you mentioned two points that seem potentially contradictory to me regarding how it should handle cases where the caller has blocked the user being searched for:

  1. "always return empty lists when searching for players who are blocked by the caller."
  2. "Do not return empty lists for searching for sessions of users the caller has blocked."

Could you please clarify the correct behavior for GetPlayingSession specifically when the caller has blocked the user whose session they are trying to find? Should the session be included in the results, or should the list be empty in this case?

The Notion notes I sent before are somewhat old, they aren't contradictory just worded a bit poorly maybe. The gist is:

  • Assume there's 2 players, PlayerA and PlayerB
  • If PlayerA has blocked PlayerB, then if PlayerB tries to use GetPlayingSession, GetSimplePlayingSession or FindCommunityByParticipant, omit the sessions PlayerA is in from the results
  • If PlayerB has blocked PlayerA, then if PlayerB tries to use GetPlayingSession, GetSimplePlayingSession or FindCommunityByParticipant, do not omit the sessions PlayerA is in from the results
  • Notice the difference in blocking order
  • None of the gathering search functions should check the block list
  • Only the join methods (and probably the auto matchmake methods) should check the block list. If dontCareMyBlackList is set to true, then allow users to join sessions even if that session contains someone the joining user has blocked (basically, never throw ParticipantInBlackList if dontCareMyBlackList is enabled)

@Fujimon8142
Copy link
Author

Thank you again for the detailed explanation and guidance!

Based on our discussion, I've summarized my understanding of the required blocklist implementation logic. Could you please confirm if this table accurately reflects the expected behavior for the different methods? I plan to use these scenarios for testing.

Blocklist Implementation Plan & Test Cases

Method Called Scenario Expected Behavior Error Thrown (if any) Notes
GetPlayingSession
GetSimplePlayingSession
FindCommunityByParticipant
Caller (Player B) tries to find sessions of Player A, but Player A has blocked Player B. Omit sessions Player A is in from the results. None
GetPlayingSession
GetSimplePlayingSession
FindCommunityByParticipant
Caller (Player B) tries to find sessions of Player A, and Player B has blocked Player A. Do not omit sessions Player A is in from the results. None Block check happens at join time.
BrowseMatchmakeSession
(Other general search methods)
Caller (Player B) searches for sessions, and some results contain Player A who has blocked Player B. Include sessions with Player A in the results. None Block lists are not checked during general search.
BrowseMatchmakeSession
(Other general search methods)
Caller (Player B) searches for sessions, and some results contain Player A whom Player B has blocked. Include sessions with Player A in the results. None Block lists are not checked during general search.
JoinMatchmakeSession
JoinMatchmakeSessionEx
JoinMatchmakeSessionWithParam
Caller (Player B) tries to join a session where a participant (Player A) has blocked Player B. Prevent Player B from joining the session. RendezVous::DeniedByParticipants
JoinMatchmakeSession
JoinMatchmakeSessionEx
JoinMatchmakeSessionWithParam
Caller (Player B) tries to join a session containing Player A, whom Player B has blocked. Prevent Player B from joining the session (unless dontCareMyBlackList is true for JoinMatchmakeSessionEx). RendezVous::ParticipantInBlackList dontCareMyBlackList check specific to JoinMatchmakeSessionEx.
AutoMatchmake... (e.g., AutoMatchmakeWithSearchCriteriaPostpone) Caller (Player B) is matched into a session where a participant (Player A) has blocked Player B. Prevent Player B from being placed in/joining the session. RendezVous::DeniedByParticipants Assuming auto-matchmake performs join checks.
AutoMatchmake... (e.g., AutoMatchmakeWithSearchCriteriaPostpone) Caller (Player B) is matched into a session containing Player A, whom Player B has blocked. Prevent Player B from being placed in/joining the session. RendezVous::ParticipantInBlackList Assuming auto-matchmake performs join checks.

Specifically regarding Steel Diver, it seems likely it uses AutoMatchmakeWithSearchCriteriaPostpone and doesn't explicitly call a Join method afterwards. Therefore, implementing the DeniedByParticipants and ParticipantInBlackList checks within the logic of AutoMatchmakeWithSearchCriteriaPostpone (or the internal join it might trigger) seems correct for that specific game.

Does this summary align with your expectations for the implementation? I'll proceed based on this understanding once confirmed.

@jonbarrow
Copy link
Member

Almost. That's all correct except for the auto matchmaking parts at the end. An error should never be thrown, the caller should just never be matched into a session where they are blocked by a participant or have a participant blocked. For auto matchmaking just find the first session that matches the search and has no blocklist issues, and if a session like that doesn't exist then a new one should be made like normal

@Fujimon8142
Copy link
Author

Fujimon8142 commented Oct 31, 2025

I am trying to set up a baseline test using Yo-kai Watch Blasters. (My personal changes have not been pushed.)

My ultimate goal is to test my own modifications to nex-protocols-common-go, but before I do that, I need to confirm that everything works correctly with the official, unmodified nex-protocols-common-go first.

To do this, I've set up the server using the pretendo-docker environment.

To clarify my testing process:
1. I connected two consoles to my server.
2. On the first console, I create a lobby. This triggers a method in the logs, something like MatchmakeExtension::OpenParticipation. (I'm at school right now, so I apologize, I can't confirm the exact method name).
3. On the second console, I search for lobbies, which triggers MatchmakeExtension::BrowseMatchmakeSession.

However, even after doing this, no players (or lobbies) appear for the searching console. This happens even though I can clearly see BrowseMatchmakeSession appearing in the server logs.

I am having trouble diagnosing the root cause: Is this a problem with my local pretendo-docker configuration, or is the official Pretendo implementation for Yo-kai Watch Blasters itself simply incomplete?

Never mind, I'm sorry. It seems the problem was just with my search criteria. I haven't played this game in years, I don't really remember it well...

@Fujimon8142
Copy link
Author

Fujimon8142 commented Nov 15, 2025

I was hesitant to commit untested code to this pull request, so I've been working on a separate branch (add-block-feature-work-in-progress).

In Steel Diver: Sub Wars, I believe removeFromBlockList is called during matchmaking, and this functioned correctly.

I then tried testing with Yo-kai Watch Blasters, but I'm running into an issue. For some reason, it seems removeFromBlockList is being called with 0 for the lstPrincipalID parameter. In this game, this function is called immediately after I unblock a user from the in-game screen, but the ID inexplicably comes through as 0.

This is preventing me from testing the unblock functionality. Do you have any ideas what might be causing this?

Alternatively, if there are any other games that would be better for testing this feature, could you please recommend some?

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

Successfully merging this pull request may close these issues.

2 participants