Compare commits

...

274 commits

Author SHA1 Message Date
Daniel López Guimaraes
db917c2172
Merge pull request #49 from PretendoNetwork/notification-data 2025-02-24 23:44:39 +00:00
Daniel López Guimaraes
52d289e761
fix(matchmake-extension): Fix notification string length check
Kid Icarus: Uprising sends strings with byte length longer than 156, so
assume this should count runes instead.
2025-02-24 23:34:32 +00:00
Daniel López Guimaraes
9005d07605
fix(matchmake-extension): Fix OnAfter typo on notification data methods 2025-02-24 22:30:05 +00:00
Daniel López Guimaraes
0fe4724b02 fix(matchmake-extension): Send notification data to connected friends
Removes warnings for connections not being found when it isn't relevant.
2025-02-13 15:22:04 +00:00
Daniel López Guimaraes
ffaba6e616 fix(matchmake-extension): Fix incorrect SQL query for notifications
Also improve and fix the NotificationData methods.
2025-02-13 15:22:04 +00:00
Daniel López Guimaraes
1450b7ffcc feat(matchmake-extension): Implement NotificationData methods
The NotificationData methods are used by games to send notifications to
friends about user activity, among others. These notifications are
created or updated using `UpdateNotificationData`, which the server will
register and send to the connected friends (as seen on Mario Tennis Open).

The lifetime of these notifications is the same as the connection of the
user who sends them. That is, when a user sends a notification and then
disconnects, the notifications will be discarded.

All notifications sent over `UpdateNotificationData` are logged inside
the `tracking.notification_data` table to prevent abuse. The type of
these notifications is also constrained to a range of specific values
reserved for game-specific purposes (from 101 to 108).
2025-02-13 15:22:04 +00:00
Daniel López Guimaraes
f83d9061ee
fix(ranking): Fix inlined DateTime declaration
Instead of instantiating a new DateTime we can use the one that already
exists on the result param.
2025-02-11 22:41:48 +00:00
Daniel López Guimaraes
59c7633654
fix(matchmake-extension): Remove inlined DateTime
This isn't allowed now with the update to the `FromTimestamp()` method.
2025-02-11 16:35:00 +00:00
Jonathan Barrow
63aaefe721
Merge pull request #45 from PretendoNetwork/persistent-gatherings 2025-02-11 11:11:52 -05:00
Daniel López Guimaraes
7781eb9e7b
chore: Update nex-go module 2025-02-11 16:06:57 +00:00
Daniel López Guimaraes
fa8845a22b
chore: Update go modules 2025-02-11 15:56:56 +00:00
Daniel López Guimaraes
7ba13e1abc
fix(matchmake-extension): Use DateTime.Now() to simplify code 2025-02-09 20:22:08 +00:00
Daniel López Guimaraes
2fd1f78759
fix(matchmake-extension): Handle zero values on communities properly 2025-02-09 19:48:47 +00:00
Daniel López Guimaraes
426a8a81dc
chore(matchmake-extension): Inline session and participation count 2025-02-09 10:25:41 +00:00
Daniel López Guimaraes
a01073f4e9 fix(matchmake-extension): Use persistent gathering constants
Also reduce the PersistentGathering.Password size to 32 extrapolatinf
from the UserPassword on MatchmakeSession
2025-02-09 00:04:05 +00:00
Daniel López Guimaraes
d5a4a74726 chore(matchmake-extension): Inline SQL queries with NEX types 2025-02-09 00:04:05 +00:00
Daniel López Guimaraes
35aed21585 feat: Initial support for persistent gatherings/communities
Implement methods that are needed for Mario Kart 7 communities to work.
Note that support for communities on MK7 is partial since the community
statistics don't load because legacy Ranking isn't implemented. Aside
from that, players can create communities and join others without
issues. Other games which use persistent gatherings may or may not work.

In order to support the `ParticipationCount`, we replace matchmake
session joins with a wrapper which checks if the session is attached to
a community, and if it is, it will increment the participation count of
the player in a new table named `community_participations`. The
`MatchmakeSessionCount` is handled more easily by checking the sessions
that belong to the corresponding community.

A new parameter is also added named `PersistentGatheringCreationMax`
with a default value of 4, as reported and tested on various games. This
allows game servers to change the maximum number of active persistent
gatherings that a player can create. For example, Mario Kart 7 supports
up to 8 persistent gatherings instead of the default of 4.

In Mario Kart 7 there is no limitation on the number of players that can
"join" to a community. That is because they don't really join to it but
they create matchmake sessions linked to the persistent gathering (in
fact, the `MaximumParticipants` parameter on persistent gatherings is
set to 0). Thus, the `participants` parameter is unused in communities
(at least on MK7) and we instead log community participations with a new
tracking table `tracking.participate_community`.

Some changes also had to be done in other places like participant
disconnection handling or gathering registrations in order to implement
persistent gatherings accurately.
2025-02-09 00:04:03 +00:00
Ash Logan
8084bcdf96 fix(datastore): Allow PersistenceTargets in PrepareGetObject 2025-02-01 17:47:34 +11:00
Ash Logan
2b1d5b7c9b fix(matchmaking): Allow missing attributes in "ranked" selection methods
As a temporary workaround until real Ranked matchmaking is implemented, attributes[1] is in use, but some games send '' for this. Just skip ranking in that case instead of erroring out.
2025-02-01 16:23:17 +11:00
Daniel López Guimaraes
bcc53deac2
chore: Inline SQL queries with NEX types 2025-01-24 22:36:52 +00:00
Daniel López Guimaraes
7f067a8ccd
Merge pull request #47 from PretendoNetwork/types-updates 2025-01-12 20:32:53 +00:00
Daniel López Guimaraes
f9e65db077
chore: Update go modules 2025-01-12 20:30:47 +00:00
Daniel López Guimaraes
5651e7f651
fix(matchmake-extension): Fix CleanupSearchMatchmakeSession signature
The matchmake session needs to be a pointer to cleanup the required
values.
2025-01-12 00:17:59 +00:00
Daniel López Guimaraes
21fdc4730e
refactor: Replace AnyDataHolder with AnyObjectHolder 2025-01-09 23:24:02 +00:00
Daniel López Guimaraes
d7f8b585c1
feat: Migrate to new nex-go types 2025-01-01 19:55:13 +00:00
Daniel López Guimaraes
f7a2dab7cc
Merge pull request #44 from DaniElectra/custom-search-criteria 2024-11-05 23:42:37 +00:00
Daniel López Guimaraes
9a67d7e82c
feat(matchmake-extension): Support custom search criteria
Add the ability to cleanup the search criteria on
`BrowseMatchmakeSession`.
2024-11-05 22:07:24 +00:00
Daniel López Guimaraes
9696be334e
Merge pull request #35 from PretendoNetwork/matchmaking-rewrite 2024-11-04 12:08:33 +00:00
Daniel López Guimaraes
7b42e6a794
chore: Update go modules 2024-11-04 12:05:06 +00:00
Daniel López Guimaraes
dd5156d92b
chore: Remove debug log from SendNotificationEvent 2024-11-04 12:04:12 +00:00
Daniel López Guimaraes
e853c5df55
fix(match-making): Change host when host disconnects
Assign a new host when the current host is disconnecting from a
gathering. To simplify the implementation, set the owner of the
gathering as the new host.

Fixes Splatoon where the client doesn't transfer the host by
themselves.
2024-10-03 20:13:33 +01:00
Daniel López Guimaraes
16ed5f16db
fix(matchmake-extension): Only use attribute 1 on selection method
Otherwise the selection method is rendered useless since they are
incompatible.
2024-09-30 17:01:24 +01:00
Daniel López Guimaraes
b7042f3435
fix(matchmake-extension): Fix min/max participants search
The `min_participants` and `max_participants` are stored on
`gatherings`, not on `matchmake_sessions`.
2024-09-30 16:59:52 +01:00
Daniel López Guimaraes
80765de633
fix(matchmake-extension): Remove extra open participation check 2024-09-01 02:02:53 +01:00
Daniel López Guimaraes
0290f5c994
feat(match-making): Implement JoinMatchmakeSessionBehavior
Also include some minor fixes.
2024-09-01 01:54:36 +01:00
Daniel López Guimaraes
165afdc00d
feat(matchmake-extension): Support selection method for finding sessions
Currently we don't support ranked matchmaking properly nor the "score
based" version.
2024-09-01 01:30:26 +01:00
Daniel López Guimaraes
32a375d64e
feat(matchmake-extension): Implement GIDForParticipationCheck 2024-08-31 12:36:18 +01:00
Daniel López Guimaraes
808a786461
fix(match-making): Check if participants are allowed to change owner 2024-08-31 12:31:36 +01:00
Daniel López Guimaraes
69e3b1f342
feat(match-making): Add tracking system
Keep tracking on gathering (un)registrations, participants joining and
leaving and host and owner changes. All tracking is managed inside the
database functions except for the host and owner changes.

The `matchmaking.participants` now remains useless from this change at
the moment, so remove it for now.
2024-08-31 00:27:51 +01:00
Daniel López Guimaraes
32d393f708
Merge pull request #42 from ashquarky/matchmaking-rewrite 2024-08-25 16:16:16 +02:00
Ash Logan
ff20935c8c feat: Add extension point for CanJoinMatchmakeSession
Useful for games with custom behaviours, like Minecraft's friends-of-friends feature
and Splatoon's fests
2024-08-25 21:03:07 +10:00
Daniel López Guimaraes
9639ab6c06
Merge pull request #41 from ashquarky/matchmaking-rewrite 2024-08-10 11:54:18 +02:00
Ash Logan
6953e78d24 fix(matchmake-extension): Fix typo in JoinMatchmakeSessionWithParam
I wanted the answer, not my own question!
2024-08-10 00:14:44 +10:00
Ash Logan
dfe34baf48 fix(matchmake-extension): Unlock mutex after CreateMatchmakeSessionWithParam
Causes deadlocks if left locked
2024-08-10 00:14:15 +10:00
Daniel López Guimaraes
8caa52cbbe
feat: Add MatchmakingManager abstraction
Allows removal of matchmake_extension_database dependency inside
match-making
2024-07-03 02:08:45 +01:00
Daniel López Guimaraes
14ae73e15e chore: Update go modules 2024-07-01 20:08:51 +01:00
Daniel López Guimaraes
20f418e04f
feat(matchmake-extension): Support user and system password 2024-07-01 20:08:44 +01:00
Daniel López Guimaraes
ed1f78ccfa
fix(match-making): Don't use "negative" PIDs for extra participants
The concept of negative PIDs was a guess and wouldn't make sense on the
Switch. Instead, replace it with storing the "main" PID multiple times
on the participants array.
2024-07-01 19:26:43 +01:00
Daniel López Guimaraes
9ccdfc9704
fix(match-making): Add checks for MatchmakeSession fields and messages 2024-07-01 17:17:50 +01:00
Daniel López Guimaraes
cc89c2ed3f
feat(match-making): Split participant disconnect from new joins 2024-06-30 16:10:34 +01:00
Daniel López Guimaraes
03912fde5b
fix(find_by_single_id): Add missing mutex unlocks 2024-06-30 16:09:03 +01:00
Daniel López Guimaraes
54f171f298
fix(get_session_urls): Don't get station URLs from owner PID
Doing that wouldn't work anyway on games.
2024-06-30 16:07:31 +01:00
Daniel López Guimaraes
e71f872ac1 feat!: Matchmaking rewrite 2024-06-30 14:08:39 +01:00
Jonathan Barrow
98a2806b0c
Merge pull request #36 from DaniElectra/nex-1-secure 2024-06-29 21:18:13 -04:00
Daniel López Guimaraes
5ab8299563
feat(secure-connection): Add methods for NEX 1 2024-06-30 02:12:07 +01:00
Daniel López Guimaraes
e1957bbca9
fix: Tidy go modules 2024-05-19 18:59:28 +01:00
Daniel López Guimaraes
06dc3fbc05
chore: Update go modules 2024-05-19 18:55:09 +01:00
Jonathan Barrow
66a942a388
Merge pull request #30 from ashquarky/patch1 2024-05-03 10:00:28 -04:00
Jonathan Barrow
b169655951
Merge pull request #31 from ashquarky/patch2 2024-05-03 09:58:45 -04:00
Ash Logan
785f021ffa feat!(datastore): Add requesting PID to GetObjectInfosByDataStoreSearchParam
This is required for PUYOPUYOTETRIS, which doesn't use the perfectly good ownerPID
field and instead sets a search target to mean "your own account".
Very cool.

Breaking API change.
2024-05-03 22:27:09 +10:00
Ash Logan
3ca998f820 fix(matchmake-extension): Fix result type from AutoMatchmake.. to be AnyDataHolder
feels awfully reminiscent of the contentWriteable bugs of the past
2024-05-03 17:39:10 +10:00
Daniel López Guimaraes
8e8e2bdbeb
Merge pull request #29 from ashquarky/main 2024-04-20 02:20:05 +02:00
Ash Logan
1fd823034b ticket-granting: Set the StructureVersion for NEX >3.5
Fixes Minecraft: Wii U Edition
2024-04-18 16:42:51 +10:00
Jonathan Barrow
95a9481cc9
Merge branch 'main' of https://github.com/PretendoNetwork/nex-protocols-common-go 2024-04-09 12:16:32 -04:00
Jonathan Barrow
b782903fd9
secure-connection: update station identifing logic 2024-04-09 10:59:51 -04:00
Daniel López Guimaraes
d435690ad0
match-making: Send notification on UpdateSessionURL 2024-04-08 20:36:24 +01:00
Daniel López Guimaraes
112d27947c
Merge pull request #26 from PretendoNetwork/nex-go-rewrite 2024-04-08 00:57:11 +02:00
Daniel López Guimaraes
dfe7dba66b
chore: Update module version to v2 2024-04-07 23:56:20 +01:00
Daniel López Guimaraes
67fb7c2dd3
match-making: Set original owner on ownership change notification 2024-04-06 18:19:25 +01:00
Daniel López Guimaraes
e4fcbbbd97 match-making: Update ParticipationCount with ConnectionIDs size 2024-03-26 23:34:08 +00:00
Daniel López Guimaraes
469c77a247 match-making: Change the host if the owner is leaving 2024-03-26 23:33:57 +00:00
Daniel López Guimaraes
68d4aa6873
match-making: Fix race condition on FindOtherConnectionID 2024-03-23 23:04:34 +00:00
Daniel López Guimaraes
8208e97bc2
secure-connection: Fix public station flag check 2024-03-23 20:38:23 +00:00
Daniel López Guimaraes
656fa0fafb
match-making: Use MutexSlice for connection IDs 2024-03-23 19:25:10 +00:00
Daniel López Guimaraes
34f42e501d
chore: Use nex-go constants 2024-03-17 22:37:34 +00:00
Daniel López Guimaraes
93a6c5d0f5
match-making: Decrement ParticipationCount when leaving a session 2024-03-17 22:34:44 +00:00
Daniel López Guimaraes
cef643aed0
match-making: Remove "Leaving" print
In case something fails here, this can be logged on the server directly for debugging
2024-03-06 16:28:09 +01:00
PabloMK7
a8cc302e32
Remove unneeded import 2024-03-06 14:11:47 +01:00
PabloMK7
3d22887dc8
Removing lingering "Leaving" print 2024-03-06 14:04:04 +01:00
Daniel López Guimaraes
4c2b1506be
matchmake-extension: Implement JoinMatchmakeSessionEx
And some bugfixes.
2024-02-24 00:28:26 +00:00
Daniel López Guimaraes
7f33824207
match-making: Fix wrong session ownership check 2024-02-22 16:12:18 +00:00
Jonathan Barrow
9ef1a27cf2
matchmake-extension: rename postpone method handlers 2024-02-13 16:35:10 -05:00
Jonathan Barrow
2728968441
update: added OnAfter event handlers for common methods 2024-02-13 16:34:03 -05:00
Jonathan Barrow
87d7dc43b6
nat-traversal: fixed ReportNATTraversalResultDetail method ID 2024-02-13 16:18:17 -05:00
Jonathan Barrow
a97b74843a
nat-traversal: fixed comment 2024-02-13 16:14:51 -05:00
Daniel López Guimaraes
b6c8028476
refactor: Remove common protocols global variables 2024-02-11 14:51:26 +00:00
Daniel López Guimaraes
410ca40c01
refactor: Update ServerInterface to EndpointInterface
Also update method handlers to use the new nex.Error return.
2024-02-11 00:33:30 +00:00
Daniel López Guimaraes
bbb4872e89
Fix typos and use account getters from endpoint 2024-01-25 17:39:32 +00:00
Jonathan Barrow
d15b82cc12
chore: nex.Errors -> nex.ResultCodes 2024-01-24 16:51:45 -05:00
Jonathan Barrow
2f6d8445c6
ticket-granting: update to use new account methods 2024-01-24 13:11:35 -05:00
Jonathan Barrow
ce83fe9720
chore: update to new types 2024-01-22 12:40:37 -05:00
Daniel López Guimaraes
33c6ccc225
Update RMC method creation 2023-12-16 15:46:55 +00:00
Daniel López Guimaraes
6fab5521a7
README: Don't update all commonTicketGrantingProtocol fields 2023-12-14 23:27:42 +00:00
Daniel López Guimaraes
6aa24a9ccf
Rename common protocols to CommonProtocol
This reduces the redundancy and follows better the Go style.
2023-12-14 23:21:01 +00:00
Daniel López Guimaraes
1a22244675
README: Update according to latest changes 2023-12-14 21:35:11 +00:00
Daniel López Guimaraes
5168b5c3d4
Update PRUDP cast comments
Now that the websockets server is integrated into PRUDPServer, we have
to question if we even want to remove these casts, since it doesn't make
much sense for HPP.

Also updaete SecureConnection::Register to support TCP addresses on the
client for websocket implementations.
2023-12-14 21:33:25 +00:00
Daniel López Guimaraes
bca1304ade
ticket-granting: Clear StationURLSpecialProtocols
This field doesn't have a station URL set usually.
2023-12-14 21:30:30 +00:00
Jonathan Barrow
2f18d58d87
ticket-granting: fix ineffectual assignment to userPID 2023-12-14 14:51:45 -05:00
Jonathan Barrow
a00ab245bf
ticket-granting: fix RVConnectionData 2023-12-14 14:50:28 -05:00
Jonathan Barrow
a5300196f8
match-making: spacing issue in protocol.go 2023-12-14 14:46:03 -05:00
Jonathan Barrow
365fe3f1e3
renamed matchmaking and matchmaking-ext to match protocol lib 2023-12-14 04:16:59 -05:00
Jonathan Barrow
612033c7ee
Merge branch 'nex-go-rewrite' of https://github.com/PretendoNetwork/nex-protocols-common-go into nex-go-rewrite 2023-12-14 04:08:59 -05:00
Jonathan Barrow
63102b9e20
ticket-granting: update RVConnectionData 2023-12-14 04:08:52 -05:00
Daniel López Guimaraes
aab72d6f17
matchmaking: Use Interface for common protocol creation 2023-12-10 19:33:28 +00:00
Jonathan Barrow
c8ca7cab4a
most common protocols now use interfaces 2023-12-10 00:30:53 -05:00
Jonathan Barrow
1836be6eb6
ticket-granting: update to new PasswordFromPID handler 2023-12-09 23:32:31 -05:00
Jonathan Barrow
cf7a3ef105
merge: fix merge conflict 2023-12-09 23:30:48 -05:00
Daniel López Guimaraes
f3f8c3b3e5
Update with PasswordFromPID on ServerInterface 2023-12-09 21:05:11 +00:00
Daniel López Guimaraes
b03ebf8adb
Update with new virtual ports handling 2023-12-09 21:04:42 +00:00
Daniel López Guimaraes
116149213d
Update README.md 2023-12-07 17:19:31 +00:00
Daniel López Guimaraes
161f95da5f
Use ServerInterface and ClientInterface
There are some places where the PRUDP implementation is still needed,
but they aren't needed for HPP.
2023-12-07 14:05:33 +00:00
Daniel López Guimaraes
4162f6c9ac
Add ContainsPID helper
slices.Cotains doesn't work when checking a list of PIDs, since the PIDs
will usually point to different locations even if the underlying value
is the same.
2023-12-07 12:54:39 +00:00
Jonathan Barrow
202487c7c7
export SecureStationURL in TicketGranting 2023-12-02 14:23:44 -05:00
Jonathan Barrow
5dd7310eda
export most helpers rather than use setters 2023-12-01 02:54:56 -05:00
Jonathan Barrow
3ac05a7067
updated StationURL handling 2023-11-24 17:41:18 -05:00
Jonathan Barrow
7e6bd47ff4
updated to new stream functions 2023-11-19 14:05:35 -05:00
Jonathan Barrow
dc4ca3547b
forgot to save a file 2023-11-16 16:08:18 -05:00
Jonathan Barrow
0e39f84cf9
use new PID, DateTime and method returns 2023-11-14 22:47:15 -05:00
Jonathan Barrow
045afaab5b
use new PID type 2023-11-13 01:22:16 -05:00
Jonathan Barrow
edcc444a36
work with new nex-go 2023-11-12 06:47:56 -05:00
Jonathan Barrow
8acdf1c23d
Merge pull request #25 from PretendoNetwork/client-to-packet
Swap to passing the packet to methods instead of the client
2023-11-01 08:03:00 -04:00
Jonathan Barrow
6404c51293
grab server from common protocols 2023-11-01 08:00:58 -04:00
Jonathan Barrow
011d7a1753
pass the original packet to method handlers instead of client 2023-11-01 07:50:32 -04:00
Jonathan Barrow
2a06105c8d
Merge pull request #24 from PretendoNetwork/datastore 2023-10-31 18:29:16 -04:00
Jonathan Barrow
0df41b9a21
check owner and upload status in CompletePostObject 2023-10-31 18:10:24 -04:00
Jonathan Barrow
63149ad6f6
undo incomplete changes to CompletePostObject. Waiting for conversation to finish 2023-10-31 17:23:09 -04:00
Jonathan Barrow
8095267d16
unexport DataStore MinIO client 2023-10-31 16:14:39 -04:00
Jonathan Barrow
444944d57b
update VerifyObjectPermission to always allow an owner to access their objects 2023-10-30 19:34:36 -04:00
Jonathan Barrow
271cc3fab2
misspelled GetObjectSizeByDataID 2023-10-30 00:16:55 -04:00
Jonathan Barrow
9ed9b132e8
use FilterPropertiesByResultOption when requesting object data 2023-10-30 00:08:37 -04:00
Jonathan Barrow
49bca9e60d
removed creating zeroed structs manually 2023-10-29 23:28:44 -04:00
Jonathan Barrow
890b9dc32a
moved VerifyObjectPermission to common 2023-10-29 17:30:02 -04:00
Jonathan Barrow
719cd89f70
moved minio s3 client to common 2023-10-29 17:20:02 -04:00
Jonathan Barrow
4f97f68161
added SetDataKeyBase and SetNotifyKeyBase to separate data and notify objects 2023-10-29 14:30:09 -04:00
Jonathan Barrow
e143a8cc24
properly handle TotalCount and TotalCountType in SearchObject 2023-10-29 14:21:47 -04:00
Jonathan Barrow
014ad34eea
URL to url in PrepareGetObject 2023-10-29 14:11:22 -04:00
Jonathan Barrow
fc7039b169
log an error when upload sizes dont match 2023-10-29 14:10:26 -04:00
Jonathan Barrow
cdfa4a68c3
remove TODO and delete unsuccessful objects in CompletePostObject 2023-10-29 14:08:01 -04:00
Jonathan Barrow
3bbd382549
added SearchObject 2023-10-29 11:42:34 -04:00
Jonathan Barrow
c3d36833d7
added RateObject 2023-10-29 11:23:14 -04:00
Jonathan Barrow
a2c1d4f704
added PostMetaBinary 2023-10-29 11:15:38 -04:00
Jonathan Barrow
b643ac5b31
added TODO comment to PreparePostObject 2023-10-29 11:08:48 -04:00
Jonathan Barrow
823830b36c
DeleteObject was always using PRUDPv1 2023-10-29 11:04:32 -04:00
Jonathan Barrow
85ac1b29c1
added GetMetas 2023-10-29 11:03:57 -04:00
Jonathan Barrow
cb8f1fb5c0
added DeleteObject 2023-10-29 10:25:14 -04:00
Jonathan Barrow
554491872c
added CompletePostObjects 2023-10-29 10:13:00 -04:00
Jonathan Barrow
9953bced1b
removed method order todo comment. already in ID order 2023-10-29 10:11:39 -04:00
Jonathan Barrow
adec20f92c
added server PRUDP version checks for response packets 2023-10-29 01:08:11 -04:00
Jonathan Barrow
086c6017e0
added start of DataStore 2023-10-29 01:03:51 -04:00
Daniel López Guimaraes
738f1bf63a
Merge pull request #23 from PretendoNetwork/unregister-gathering 2023-10-24 01:41:53 +02:00
SuperMarioDaBom
783d917ccc
Add missing boolean response to UnregisterGathering 2023-10-23 16:31:25 -07:00
Daniel López Guimaraes
cfddbc4758
Merge pull request #22 from DaniElectra/redesign-search-criteria 2023-10-23 20:40:14 +02:00
Daniel López Guimaraes
5160332968
Move logger to globals
This allows us to log inside the matchmaking globals.
2023-10-20 23:27:38 +01:00
Daniel López Guimaraes
363fe3cba5
Redesign find sessions using SearchCriteria
The `MatchmakeSessionSearchCriteria` is intended to be compared with the
original session to find valid sessions. Thus, we can remove the
`SearchCriteria` field that we are storing internally and instead
compare values with the session.

With this, we implement comparing attributes by default, unless
game-specific checks are defined, in which case the attribute search
gets overriden. This allows overriding region-locked matchmaking.

This also allows removing session creation that requires search criteria
`CreateSessionBySearchCriteria` and instead move everything to
`CreateSessionByMatchmakeSession`.

We can also remove the cleanup of search criteria, since it's redundant
considering we allow game-specific checks.

Friends-only matches are also now supported, and requires setting the
`GetUserFriendPIDs` handler on `MatchmakeExtension`.

This improves accuracy finding sessions and is how Rambo's MK8 server is
implemented.

Tested successfully with:
- Mario Kart 7
- Steel Diver: Sub Wars
- IRONFALL Invasion
2023-10-19 18:42:05 +01:00
Daniel López Guimaraes
2e542d846c
Merge pull request #21 from shutterbug2000/matchmaking-util-change 2023-10-19 15:23:38 +01:00
shutterbug2000
942f04defd Change AutoMatchmakeWithParam_Postpone to use SearchCriteria
This should allow for more precise matchmaking
Also changes SearchMatchmakeSession to GameMatchmakeSession since a SearchCriteria session won't have a searchMatchmakeSession
2023-10-14 08:56:29 -05:00
shutterbug2000
74e821b440 Use CurrentMatchmakingCallID in other protocols 2023-10-13 07:41:54 -05:00
shutterbug2000
26f22bab45 Remove stray space 2023-10-12 16:44:14 -05:00
shutterbug2000
76bb2e2818 Code review fixes 2023-10-12 16:42:35 -05:00
shutterbug2000
aa73a77404 Code review fixes 2023-10-12 15:44:03 -05:00
shutterbug2000
5903bbbd53 Move session creation and notification sending
This helps reduce duplicate code and bugs that result from it
2023-10-12 12:29:58 -05:00
Daniel López Guimaraes
8358d71bc6
Merge pull request #20 from shutterbug2000/matchmaking-bugfix 2023-10-12 02:10:35 +01:00
shutterbug2000
bb05a69934 Fix typo in methods too 2023-10-11 20:09:02 -05:00
shutterbug2000
3cdf7e4865 Fix a bug and typo in MatchmakeExtension 2023-10-11 20:01:25 -05:00
Jonathan Barrow
3b4ab59da4
Merge pull request #19 from shutterbug2000/ranking
Add common Ranking protocol
2023-10-11 12:38:08 -04:00
shutterbug2000
f495b0b755 Formatting fixes 2023-10-11 08:04:09 -05:00
shutterbug2000
470f32c0de Code Review Changes 2023-10-11 07:50:27 -05:00
shutterbug2000
962aecdbd5 Adjust spacing 2023-10-11 05:39:01 -05:00
shutterbug2000
6b243dea56 Fix compile errors and move returned errors to end 2023-10-11 05:37:20 -05:00
shutterbug2000
d60b6b58ad Code review changes 2023-10-11 05:28:34 -05:00
shutterbug2000
5c006a0683 Add common Ranking protocol
Tested on both Ultimate NES Remix and DKC:TF without issues
2023-10-06 23:07:57 -05:00
Jonathan Barrow
ca8ad16294
Merge pull request #18 from DaniElectra/update-session-url
Implement various functions and player disconnects
2023-10-03 22:47:19 -04:00
Daniel López Guimaraes
0e7243fb78
Update go modules and add JoinMatchmakeSession
This allows players on Mario Kart 7 to join to their friends or other
people they have played with (rivals).
2023-10-03 23:45:34 +01:00
Daniel López Guimaraes
d33369f5bc
Rework handling of player disconnects
Reassign owner on session when it leaves unreliably.
2023-09-03 00:46:50 +01:00
Daniel López Guimaraes
c158b4f3b7
Implement UpdateSessionURL properly
Hopefully...
2023-09-02 21:13:14 +01:00
Daniel López Guimaraes
bcc5fc6e39
Implement UpdateSessionURL 2023-09-02 20:35:48 +01:00
Jonathan Barrow
d2d3440e9d
Merge pull request #17 from DaniElectra/fixes-2
Handle multiple station URLs on Register and more
2023-09-02 10:27:32 -04:00
Daniel López Guimaraes
d9bb79bc0e
Better check for nil clients
EndParticipation triggered a nil client during testing. Add checks for
all client searches.
2023-09-02 11:51:23 +01:00
Daniel López Guimaraes
eaecd44bbe
Search for public station on Register
This is more reliable than just taking the second URL.
2023-09-02 11:49:39 +01:00
Daniel López Guimaraes
07fa6d10c7
Use NEX Counter for gathering IDs 2023-09-02 11:48:46 +01:00
Daniel López Guimaraes
242bf55ff1
Various fixes 2023-08-31 18:32:47 +01:00
Daniel López Guimaraes
45027fb32f
Implement CloseParticipation
Called when a gathering fills up.
2023-08-31 18:28:19 +01:00
Daniel López Guimaraes
61960be5dc
Use sequential gathering IDs 2023-08-31 18:26:27 +01:00
Daniel López Guimaraes
665c2b80db
Handle multiple station URLs on Register
Games like Mario Kart 7 or Mario Tennis Open set two station URLs on the
Register method, the local sattion and the public station. In these
cases, use the public station given by the client.

Also add the PID and RVCID on both stations, as they are required on NAT
traversal and not all games will call `ReportNATProperties` to add them.
2023-08-31 18:21:32 +01:00
Jonathan Barrow
1a0be46eba
Updated Go modules 2023-08-31 00:48:41 -04:00
Jonathan Barrow
b369cddadf
Added BrowseMatchmakeSession and refactor FindSessionsByMatchmakeSessionSearchCriterias 2023-08-31 00:45:28 -04:00
Jonathan Barrow
1dc1b89c74
Split globals and util functions 2023-08-30 21:24:56 -04:00
Jonathan Barrow
c033481f9b
Rename and refactor some matchmaking utils 2023-08-30 21:23:22 -04:00
Jonathan Barrow
611a1146c5
Use NEXVersion.GameSpecificPatch for checking patch versions 2023-08-30 19:29:47 -04:00
Jonathan Barrow
2d4c8014a4
Updated NEX version check in CreateMatchmakeSession 2023-08-30 19:20:43 -04:00
Jonathan Barrow
2aec134810
Added MatchmakeExtension::ModifyCurrentGameAttribute 2023-08-30 01:26:52 -04:00
Jonathan Barrow
deae7db4c8
Added Utility protocol and Utility::AcquireNexUniqueID 2023-08-30 01:20:01 -04:00
Jonathan Barrow
c34842ee0b
Updated Go modules 2023-08-29 13:49:14 -04:00
Jonathan Barrow
8429e5ec77
Added JoinMatchmakeSessionWithParam 2023-08-28 05:35:14 -04:00
Jonathan Barrow
cde148bf02
Added FindBySingleID 2023-08-28 05:03:49 -04:00
Jonathan Barrow
fe22fe4646
Added UpdateApplicationBuffer 2023-08-28 04:54:33 -04:00
Jonathan Barrow
e3e9d1fa85
Added CreateMatchmakeSessionWithParam 2023-08-28 03:42:55 -04:00
Jonathan Barrow
130669545d
Improved matchmake criteria searching 2023-08-27 18:21:16 -04:00
Jonathan Barrow
d2c568777b
Improved NAT and StationURL handling 2023-08-15 06:49:48 -04:00
Jonathan Barrow
48ea32c4bd
Updated go modules 2023-08-15 00:05:07 -04:00
Jonathan Barrow
ef246842d4
Added UpdateProgressScore 2023-08-15 00:00:38 -04:00
Jonathan Barrow
1ff762db4e
Fixed ReplaceURL not always finding station 2023-08-14 23:29:48 -04:00
Jonathan Barrow
ea0e9a9e3c
Make Matchmake Extension protocol support game patches 2023-08-14 22:46:54 -04:00
Jonathan Barrow
6fdfb063c1
Added CreateMatchmakeSession and GetSimplePlayingSession 2023-08-14 22:31:53 -04:00
Jonathan Barrow
ff41aad31a
Beep boop wrong error 2023-08-08 10:31:54 -04:00
Jonathan Barrow
dc64652089
Check error on UpdateRC4Key 2023-08-08 10:20:50 -04:00
Jonathan Barrow
f75330649c
Formatting updates 2023-08-08 10:20:06 -04:00
Jonathan Barrow
1700e4e402
Added CreateReportDBRecord for SecureConnection 2023-08-08 10:17:54 -04:00
Jonathan Barrow
212370d13d
Check err on rand.Read(sessionKey) 2023-08-07 13:09:03 -04:00
Jonathan Barrow
155be71969
Updated Login error if insecure login disabled 2023-08-07 13:07:05 -04:00
Jonathan Barrow
f900166ae2
Merge pull request #16 from DaniElectra/errors
Update go modules and proper error handling
2023-08-07 13:03:53 -04:00
Daniel López Guimaraes
87a101c568
Upadte go modules 2023-08-07 17:28:45 +01:00
Daniel López Guimaraes
71163070db
Update go modules and proper error handling
With the latest release of nex-protocols-go we can now implement error
handling in a clean way.
2023-08-06 01:44:42 +01:00
Jonathan Barrow
807004e576
Merge pull request #15 from PretendoNetwork/style-update
Update to new naming style in nex-protocols-go
2023-07-27 17:11:10 -04:00
SuperMarioDaBom
613f7a7a82 Why didn't it pull the latest one... (facepalm) 2023-07-27 14:09:01 -07:00
SuperMarioDaBom
6a8807ee4a Update go.mod and go.sum 2023-07-27 14:07:10 -07:00
SuperMarioDaBom
5b5b237027 Update to new naming style in nex-protocols-go 2023-07-27 13:54:39 -07:00
Jonathan Barrow
d3e1504e17
Merge pull request #14 from shutterbug2000/pokegen6-pt1
Implement methods for Pokemon Gen6
2023-07-12 15:46:27 -04:00
shutterbug2000
70423fc875 Merge branch 'pokegen6-pt1' of https://github.com/shutterbug2000/nex-protocols-common-go into pokegen6-pt1 2023-07-12 19:45:12 +00:00
shutterbug2000
fc68fbf5c3 Update Go modules 2023-07-12 19:45:07 +00:00
shutterbug2000
d66db4e657
Apply suggestions from code review
Fixes some spacing issues

Co-authored-by: Jonathan Barrow <jonbarrow1998@gmail.com>
2023-07-12 14:40:58 -05:00
shutterbug2000
f3f84c3cbd Fix code review issues 2023-07-12 19:31:54 +00:00
shutterbug2000
5760221e74
Apply suggestions from code review
Co-authored-by: Daniel López Guimaraes <112760654+DaniElectra@users.noreply.github.com>
2023-07-12 13:47:32 -05:00
shutterbug2000
9ec24f6297 Implement methods for Pokemon Gen6
This allows Wonder Trade and Random Matchup free battle to function.
PSS matchup will require more methods.
2023-07-12 17:26:56 +00:00
Jonathan Barrow
41f4655e6d
Disable insecure Login method by default 2023-07-05 01:03:06 -04:00
Jonathan Barrow
1421c488af
Moved to new protocol library format 2023-07-04 23:32:42 -04:00
Jonathan Barrow
5785c860f9
Merge pull request #13 from DaniElectra/login-version
Set  RVConnectionData time as DateTime
2023-06-21 17:20:14 -04:00
Daniel López Guimaraes
01db10686f
Set RVConnectionData time to DateTime
Also revert structure version change, as it is handled in nex-go.
2023-06-21 22:16:41 +01:00
Daniel López Guimaraes
b7e6c97f94
Update go modules 2023-06-21 22:14:35 +01:00
Daniel López Guimaraes
e6b3e0b842
Set StructureVersion on RVConnectionData
Set the structure version to 1 if NEX version is 3.5+
2023-06-20 23:30:56 +01:00
Jonathan Barrow
0342dc2906
Merge pull request #12 from DaniElectra/revamp
Lots of changes on common protocols
2023-06-16 21:53:36 -04:00
Daniel López Guimaraes
eaad0fcea8
Fix time validation of Kerberos tickets
We have to increase the ticket time and compare to the server time.
Whoops.
2023-06-15 19:35:24 +01:00
Daniel López Guimaraes
ec12cfb374
Update go modules
Also implement Kerberos ticket time check.
2023-06-15 19:06:08 +01:00
Daniel López Guimaraes
c1dc70edfc
Fix PR issues 2023-06-12 19:04:17 +01:00
Daniel López Guimaraes
6f5fbd94d5
Send ownership change from EndParticipation 2023-06-08 18:30:01 +01:00
Daniel López Guimaraes
e598334373
Address PR issues
And other improvements
2023-06-06 21:29:47 +01:00
Daniel López Guimaraes
237060b9cb
Update go modules 2023-06-06 20:48:03 +01:00
Daniel López Guimaraes
3e7d634b71
Various changes to Matchmaking
- Use structure.Copy() and structure.Equal() now that they are
  implemented.
- Add notification types that weren't used before.
- Remove handlers for MatchMaking(-Ext) and use global sessions for
  handling rooms.
- And possibly more
2023-06-05 21:04:29 +01:00
Daniel López Guimaraes
2e3bc892e8
Use common struct on NAT Traversal
Also remove external handlers, as we now store the client station URLs
on nex.Client. And implement ReportNATTraversalResult.
2023-06-05 20:46:52 +01:00
Daniel López Guimaraes
7b54d5fa1d
Remove handlers on Secure Connection
Since we now store the client station URLs on nex.Client, there's no
need for external handlers to do so, so remove them. Also, the handlers
were deprecated as they didn't do anything anyway.
2023-06-05 20:44:31 +01:00
Jonathan Barrow
618b185517
Merge pull request #11 from shutterbug2000/matchmaking-common-2
Matchmaking protocol additions and changes
2023-05-23 16:03:42 -04:00
shutterbug2000
3e7de77680 Add a space that I missed
whoops lol
2023-05-23 03:55:03 -05:00
shutterbug2000
9f2d34a7ee Change session removal to go with the first session found
There shouldn't be any instance where a client can be in multiple sessions at once
2023-05-23 03:53:08 -05:00
shutterbug2000
46bc3e2bb4 Code review changes 2023-05-22 22:47:55 -05:00
shutterbug2000
9322a0ae3c Matchmaking protocol additions and changes
- Move utility methods to global so any MatchMaking protocol can use them
- Add EndParticipation to Matchmaking Ext
- Remove clients from Sessions when they leave the server
2023-05-22 13:11:39 -05:00
Jonathan Barrow
175bbe4ede
Updated go modules 2023-05-21 01:49:50 -04:00
shutterbug2000
dfd3680156 Bugfix 2023-05-21 00:46:59 -05:00
Jonathan Barrow
8e5711cb38
Merge pull request #10 from shutterbug2000/matchmake-extension-common-1
Add AutoMatchmake_Postpone common method
2023-05-21 01:39:41 -04:00
shutterbug2000
4ebf815657 TODOs for Jon
Also changes references to PlayersByConnectionId
2023-05-21 00:34:06 -05:00
shutterbug2000
a89c9d3ac7 Change PlayersByConnectionId to ConnectionIDs 2023-05-21 00:33:10 -05:00
shutterbug2000
eee585960d WriteDataHolder for MatchmakeSession 2023-05-21 00:30:03 -05:00
shutterbug2000
468c4143af Accidentally removed this, re-adding it lol 2023-05-20 23:59:13 -05:00
shutterbug2000
39ac779726 Fix some code review issues and other bugs 2023-05-20 23:55:02 -05:00
shutterbug2000
f2a32710a8 Add AutoMatchmake_Postpone common method
This gets MK7 into a match
2023-05-20 23:05:29 -05:00
Jonathan Barrow
e3729c531a
Merge pull request #9 from shutterbug2000/station-urls-change
Fix StationURLs for the changes in nex-go
2023-05-20 23:40:58 -04:00
shutterbug2000
f5c2a38b94 Fix StationURLs for the changes in nex-go 2023-05-20 22:33:11 -05:00
Jonathan Barrow
24dfd6451e
Merge pull request #8 from shutterbug2000/update-common-mm 2023-05-19 23:21:05 -04:00
shutterbug2000
2170b0dc1f Update Common MatchMaking to new format 2023-05-19 22:13:45 -05:00
Jonathan Barrow
b61d65d389
Merge pull request #7 from DaniElectra/hpp
Use PasswordFromPID function from NEX server
2023-04-23 14:28:59 -04:00
Daniel López Guimaraes
4424b19205
Update go modules 2023-04-23 19:27:59 +01:00
Daniel López Guimaraes
ff19b6d446
Use PasswordFromPID function from NEX server
Since we have to get a password on the server for HPP, check that
function to avoid confusion.
2023-04-23 18:02:56 +01:00
Jonathan Barrow
dce7956806
Merge pull request #6 from DaniElectra/matchmaking
Remove Matchmaking notification hacks
2023-04-08 20:44:30 -04:00
Daniel López Guimaraes
994a11276b
Remove Matchmaking notification hacks
The notification can be simplified since we have a structure for that.
2023-04-08 22:18:23 +01:00
Jonathan Barrow
a28a4364d7
Updated NEX library versions 2023-04-07 21:03:47 -04:00
Jonathan Barrow
0627dc3d7e
Bump module versions 2023-04-04 18:11:44 -04:00
Jonathan Barrow
14ae2f317f
Bump module versions 2023-04-04 15:57:02 -04:00
Jonathan Barrow
574dd852ab
Updated module versions 2023-03-30 00:57:37 -04:00
Jonathan Barrow
b594be18f3
Bump module versions 2023-03-26 18:53:49 -04:00
Jonathan Barrow
f84141fc31
Bump module and Go versions 2023-03-26 12:37:35 -04:00
Jonathan Barrow
abd27af2b2
Merge pull request #5 from DaniElectra/fixes
Various fixes
2023-03-26 12:36:03 -04:00
Daniel López Guimaraes
9c79a8c7d4
Fix RVConnectionData time
Add current UTC time to pConnectionData to fix Badge Arcade time issues.
2023-03-25 23:42:57 +00:00
Daniel López Guimaraes
323baf80fe
Fix Register SetLocalStationURL
This function was renamed when linting nex-go.
2023-03-25 23:40:51 +00:00
Jonathan Barrow
483070aa38
Updated go modules 2022-09-05 10:21:20 -04:00
shutterbug2000
8db897fb2c Fixes for Matchmaking protocol and update nex-protocols-go 2022-09-05 03:34:48 +00:00
139 changed files with 8557 additions and 1113 deletions

View file

@ -1,7 +1,7 @@
# NEX Protocols Common Go
## NEX protocols used by many games with premade handlers and a high level API
[![GoDoc](https://godoc.org/github.com/PretendoNetwork/nex-protocols-common-go?status.svg)](https://godoc.org/github.com/PretendoNetwork/nex-protocols-common-go)
[![GoDoc](https://godoc.org/github.com/PretendoNetwork/nex-protocols-common-go/v2?status.svg)](https://godoc.org/github.com/PretendoNetwork/nex-protocols-common-go/v2)
### Other NEX libraries
[nex-go](https://github.com/PretendoNetwork/nex-go) - Barebones NEX/PRUDP server implementation
@ -10,14 +10,14 @@
### Install
`go get github.com/PretendoNetwork/nex-protocols-common-go`
`go get github.com/PretendoNetwork/nex-protocols-common-go/v2`
### Usage
`nex-protocols-common-go` provides a higher level API than the [NEX Protocols Go module](https://github.com/PretendoNetwork/nex-protocols-go). This module handles many of the more common protcols and methods used shared by many servers. Instead of working directly with the NEX server, this module exposes an API for defining helper functions to provide the module with the data it needs to run
### Example, friends (Wii U) authentication server
### For a complete example, see the complete [Friends Authentication Server](https://github.com/PretendoNetwork/friends-authentication), and other game servers
### For a complete example, see the complete [Friends Server](https://github.com/PretendoNetwork/friends), and other game servers
```go
package main
@ -26,44 +26,52 @@ import (
"fmt"
"os"
"github.com/PretendoNetwork/nex-go"
"github.com/PretendoNetwork/nex-protocols-common-go/authentication"
"github.com/PretendoNetwork/nex-go/v2"
ticket_granting "github.com/PretendoNetwork/nex-protocols-go/v2/ticket-granting"
common_ticket_granting "github.com/PretendoNetwork/nex-protocols-common-go/v2/ticket-granting"
)
var nexServer *nex.Server
var nexServer *nex.PRUDPServer
func main() {
nexServer = nex.NewServer()
nexServer.SetPrudpVersion(0)
nexServer.SetKerberosKeySize(16)
nexServer.SetKerberosPassword(os.Getenv("KERBEROS_PASSWORD"))
nexServer.SetAccessKey("ridfebb9")
nexServer := nex.NewPRUDPServer()
nexServer.On("Data", func(packet *nex.PacketV0) {
request := packet.RMCRequest()
endpoint := nex.NewPRUDPEndPoint(1)
endpoint.ServerAccount = nex.NewAccount(types.NewPID(1), "Quazal Authentication", "password"))
endpoint.AccountDetailsByPID = accountDetailsByPID
endpoint.AccountDetailsByUsername = accountDetailsByUsername
endpoint.OnData(func(packet nex.PacketInterface) {
request := packet.RMCMessage()
fmt.Println("==Friends - Auth==")
fmt.Printf("Protocol ID: %#v\n", request.ProtocolID())
fmt.Printf("Method ID: %#v\n", request.MethodID())
fmt.Printf("Protocol ID: %#v\n", request.ProtocolID)
fmt.Printf("Method ID: %#v\n", request.MethodID)
fmt.Println("==================")
})
authenticationProtocol := authentication.NewCommonAuthenticationProtocol(nexServer)
nexServer.SetFragmentSize(962)
nexServer.LibraryVersions.SetDefault(nex.NewLibraryVersion(1, 1, 0))
nexServer.SessionKeyLength = 16
nexServer.AccessKey = "ridfebb9"
ticketGrantingProtocol := ticket_granting.NewProtocol(endpoint)
endpoint.RegisterServiceProtocol(ticketGrantingProtocol)
commonTicketGrantingProtocol := common_ticket_granting.NewCommonProtocol(ticketGrantingProtocol)
secureStationURL := nex.NewStationURL("")
secureStationURL.SetScheme("prudps")
secureStationURL.SetAddress(os.Getenv("SECURE_SERVER_LOCATION"))
secureStationURL.SetPort(os.Getenv("SECURE_SERVER_PORT"))
secureStationURL.SetCID("1")
secureStationURL.SetPID("2")
secureStationURL.SetSID("1")
secureStationURL.SetStream("10")
secureStationURL.SetType("2")
secureStationURL.Scheme = "prudps"
secureStationURL.Fields.Set("address", os.Getenv("SECURE_SERVER_LOCATION"))
secureStationURL.Fields.Set("port", os.Getenv("SECURE_SERVER_PORT"))
secureStationURL.Fields.Set("CID", "1")
secureStationURL.Fields.Set("PID", "2")
secureStationURL.Fields.Set("sid", "1")
secureStationURL.Fields.Set("stream", "10")
secureStationURL.Fields.Set("type", "2")
authenticationProtocol.SetSecureStationURL(secureStationURL)
authenticationProtocol.SetBuildName("Pretendo Friends Auth")
authenticationProtocol.SetPasswordFromPIDFunction(passwordFromPID)
commonTicketGrantingProtocol.SecureStationURL = secureStationURL
commonTicketGrantingProtocol.BuildName = "Pretendo Friends Auth"
nexServer.Listen(":60000")
nexServer.Listen(60000)
}
```

View file

@ -1,63 +0,0 @@
package authentication
import (
"crypto/rand"
"github.com/PretendoNetwork/nex-go"
)
func generateTicket(userPID uint32, targetPID uint32) ([]byte, uint32) {
if commonAuthenticationProtocol.passwordFromPIDHandler == nil {
logger.Warning("Missing passwordFromPIDHandler!")
return []byte{}, nex.Errors.Core.Unknown
}
var userPassword string
var targetPassword string
var errorCode uint32
// TODO: Maybe we should error out if the user PID is the server account?
switch userPID {
case 2: // "Quazal Rendez-Vous" (the server user) account
userPassword = commonAuthenticationProtocol.server.KerberosPassword()
case 100: // guest user account
userPassword = "MMQea3n!fsik"
default:
userPassword, errorCode = commonAuthenticationProtocol.passwordFromPIDHandler(userPID)
}
if errorCode != 0 {
return []byte{}, errorCode
}
switch targetPID {
case 2: // "Quazal Rendez-Vous" (the server user) account
targetPassword = commonAuthenticationProtocol.server.KerberosPassword()
case 100: // guest user account
targetPassword = "MMQea3n!fsik"
default:
targetPassword, errorCode = commonAuthenticationProtocol.passwordFromPIDHandler(userPID)
}
if errorCode != 0 {
return []byte{}, errorCode
}
userKey := nex.DeriveKerberosKey(userPID, []byte(userPassword))
targetKey := nex.DeriveKerberosKey(targetPID, []byte(targetPassword))
sessionKey := make([]byte, commonAuthenticationProtocol.server.KerberosKeySize())
rand.Read(sessionKey)
ticketInternalData := nex.NewKerberosTicketInternalData()
ticketInternalData.SetTimestamp(nex.NewDateTime(0)) // CHANGE THIS
ticketInternalData.SetUserPID(userPID)
ticketInternalData.SetSessionKey(sessionKey)
encryptedTicketInternalData := ticketInternalData.Encrypt(targetKey, nex.NewStreamOut(commonAuthenticationProtocol.server))
ticket := nex.NewKerberosTicket()
ticket.SetSessionKey(sessionKey)
ticket.SetTargetPID(targetPID)
ticket.SetInternalData(encryptedTicketInternalData)
return ticket.Encrypt(userKey, nex.NewStreamOut(commonAuthenticationProtocol.server)), 0
}

View file

@ -1,95 +0,0 @@
package authentication
import (
"strconv"
"strings"
"github.com/PretendoNetwork/nex-go"
nexproto "github.com/PretendoNetwork/nex-protocols-go"
)
func login(err error, client *nex.Client, callID uint32, username string) {
var userPID uint32
if username == "guest" {
userPID = 100
} else {
converted, err := strconv.Atoi(strings.TrimRight(username, "\x00"))
if err != nil {
panic(err)
}
userPID = uint32(converted)
}
var targetPID uint32 = 2 // "Quazal Rendez-Vous" (the server user) account PID
encryptedTicket, errorCode := generateTicket(userPID, targetPID)
rmcResponse := nex.NewRMCResponse(nexproto.AuthenticationProtocolID, callID)
if errorCode != 0 && errorCode != nex.Errors.RendezVous.InvalidUsername {
// Some other error happened
rmcResponse.SetError(errorCode)
} else {
var retval *nex.Result
var pidPrincipal uint32
var pbufResponse []byte
var pConnectionData *nex.RVConnectionData
var strReturnMsg string
pConnectionData = nex.NewRVConnectionData()
pConnectionData.SetStationURL(commonAuthenticationProtocol.secureStationURL.EncodeToString())
pConnectionData.SetSpecialProtocols([]byte{})
pConnectionData.SetStationURLSpecialProtocols("")
/*
From the wiki:
"If the username does not exist, the %retval% field is set to
RendezVous::InvalidUsername and the other fields are left blank."
*/
if errorCode == nex.Errors.RendezVous.InvalidUsername {
retval = nex.NewResultError(errorCode)
} else {
retval = nex.NewResultSuccess(nex.Errors.Core.Unknown)
pidPrincipal = userPID
pbufResponse = encryptedTicket
strReturnMsg = commonAuthenticationProtocol.buildName
}
rmcResponseStream := nex.NewStreamOut(commonAuthenticationProtocol.server)
rmcResponseStream.WriteResult(retval)
rmcResponseStream.WriteUInt32LE(pidPrincipal)
rmcResponseStream.WriteBuffer(pbufResponse)
rmcResponseStream.WriteStructure(pConnectionData)
rmcResponseStream.WriteString(strReturnMsg)
rmcResponseBody := rmcResponseStream.Bytes()
rmcResponse.SetSuccess(nexproto.AuthenticationMethodLogin, rmcResponseBody)
}
rmcResponseBytes := rmcResponse.Bytes()
var responsePacket nex.PacketInterface
if commonAuthenticationProtocol.server.PrudpVersion() == 0 {
responsePacket, _ = nex.NewPacketV0(client, nil)
responsePacket.SetVersion(0)
} else {
responsePacket, _ = nex.NewPacketV1(client, nil)
responsePacket.SetVersion(1)
}
responsePacket.SetSource(0xA1)
responsePacket.SetDestination(0xAF)
responsePacket.SetType(nex.DataPacket)
responsePacket.SetPayload(rmcResponseBytes)
responsePacket.AddFlag(nex.FlagNeedsAck)
responsePacket.AddFlag(nex.FlagReliable)
commonAuthenticationProtocol.server.Send(responsePacket)
}

View file

@ -1,95 +0,0 @@
package authentication
import (
"strconv"
"strings"
nex "github.com/PretendoNetwork/nex-go"
nexproto "github.com/PretendoNetwork/nex-protocols-go"
)
func loginEx(err error, client *nex.Client, callID uint32, username string, authenticationInfo *nexproto.AuthenticationInfo) {
var userPID uint32
if username == "guest" {
userPID = 100
} else {
converted, err := strconv.Atoi(strings.TrimRight(username, "\x00"))
if err != nil {
panic(err)
}
userPID = uint32(converted)
}
var targetPID uint32 = 2 // "Quazal Rendez-Vous" (the server user) account PID
encryptedTicket, errorCode := generateTicket(userPID, targetPID)
rmcResponse := nex.NewRMCResponse(nexproto.AuthenticationProtocolID, callID)
if errorCode != 0 && errorCode != nex.Errors.RendezVous.InvalidUsername {
// Some other error happened
rmcResponse.SetError(errorCode)
} else {
var retval *nex.Result
var pidPrincipal uint32
var pbufResponse []byte
var pConnectionData *nex.RVConnectionData
var strReturnMsg string
pConnectionData = nex.NewRVConnectionData()
pConnectionData.SetStationURL(commonAuthenticationProtocol.secureStationURL.EncodeToString())
pConnectionData.SetSpecialProtocols([]byte{})
pConnectionData.SetStationURLSpecialProtocols("")
/*
From the wiki:
"If the username does not exist, the %retval% field is set to
RendezVous::InvalidUsername and the other fields are left blank."
*/
if errorCode == nex.Errors.RendezVous.InvalidUsername {
retval = nex.NewResultError(errorCode)
} else {
retval = nex.NewResultSuccess(nex.Errors.Core.Unknown)
pidPrincipal = userPID
pbufResponse = encryptedTicket
strReturnMsg = commonAuthenticationProtocol.buildName
}
rmcResponseStream := nex.NewStreamOut(commonAuthenticationProtocol.server)
rmcResponseStream.WriteResult(retval)
rmcResponseStream.WriteUInt32LE(pidPrincipal)
rmcResponseStream.WriteBuffer(pbufResponse)
rmcResponseStream.WriteStructure(pConnectionData)
rmcResponseStream.WriteString(strReturnMsg)
rmcResponseBody := rmcResponseStream.Bytes()
rmcResponse.SetSuccess(nexproto.AuthenticationMethodLoginEx, rmcResponseBody)
}
rmcResponseBytes := rmcResponse.Bytes()
var responsePacket nex.PacketInterface
if commonAuthenticationProtocol.server.PrudpVersion() == 0 {
responsePacket, _ = nex.NewPacketV0(client, nil)
responsePacket.SetVersion(0)
} else {
responsePacket, _ = nex.NewPacketV1(client, nil)
responsePacket.SetVersion(1)
}
responsePacket.SetSource(0xA1)
responsePacket.SetDestination(0xAF)
responsePacket.SetType(nex.DataPacket)
responsePacket.SetPayload(rmcResponseBytes)
responsePacket.AddFlag(nex.FlagNeedsAck)
responsePacket.AddFlag(nex.FlagReliable)
commonAuthenticationProtocol.server.Send(responsePacket)
}

View file

@ -1,42 +0,0 @@
package authentication
import (
"github.com/PretendoNetwork/nex-go"
nexproto "github.com/PretendoNetwork/nex-protocols-go"
"github.com/PretendoNetwork/plogger-go"
)
var commonAuthenticationProtocol *CommonAuthenticationProtocol
var logger = plogger.NewLogger()
type CommonAuthenticationProtocol struct {
*nexproto.AuthenticationProtocol
server *nex.Server
secureStationURL *nex.StationURL
buildName string
passwordFromPIDHandler func(pid uint32) (string, uint32)
}
func (commonAuthenticationProtocol *CommonAuthenticationProtocol) SetSecureStationURL(stationURL *nex.StationURL) {
commonAuthenticationProtocol.secureStationURL = stationURL
}
func (commonAuthenticationProtocol *CommonAuthenticationProtocol) SetBuildName(buildName string) {
commonAuthenticationProtocol.buildName = buildName
}
func (commonAuthenticationProtocol *CommonAuthenticationProtocol) SetPasswordFromPIDFunction(handler func(pid uint32) (string, uint32)) {
commonAuthenticationProtocol.passwordFromPIDHandler = handler
}
// NewCommonAuthenticationProtocol returns a new CommonAuthenticationProtocol
func NewCommonAuthenticationProtocol(server *nex.Server) *CommonAuthenticationProtocol {
authenticationProtocol := nexproto.NewAuthenticationProtocol(server)
commonAuthenticationProtocol = &CommonAuthenticationProtocol{AuthenticationProtocol: authenticationProtocol, server: server}
commonAuthenticationProtocol.Login(login)
commonAuthenticationProtocol.LoginEx(loginEx)
commonAuthenticationProtocol.RequestTicket(requestTicket)
return commonAuthenticationProtocol
}

View file

@ -1,49 +0,0 @@
package authentication
import (
nex "github.com/PretendoNetwork/nex-go"
nexproto "github.com/PretendoNetwork/nex-protocols-go"
)
func requestTicket(err error, client *nex.Client, callID uint32, userPID uint32, targetPID uint32) {
encryptedTicket, errorCode := generateTicket(userPID, targetPID)
rmcResponse := nex.NewRMCResponse(nexproto.AuthenticationProtocolID, callID)
// TODO:
// If the source or target pid is invalid, the %retval% field is set to Core::AccessDenied and the ticket is empty.
if errorCode != 0 {
rmcResponse.SetError(errorCode)
} else {
rmcResponseStream := nex.NewStreamOut(commonAuthenticationProtocol.server)
rmcResponseStream.WriteResult(nex.NewResultSuccess(nex.Errors.Core.Unknown))
rmcResponseStream.WriteBuffer(encryptedTicket)
rmcResponseBody := rmcResponseStream.Bytes()
rmcResponse.SetSuccess(nexproto.AuthenticationMethodRequestTicket, rmcResponseBody)
}
rmcResponseBytes := rmcResponse.Bytes()
var responsePacket nex.PacketInterface
if commonAuthenticationProtocol.server.PrudpVersion() == 0 {
responsePacket, _ = nex.NewPacketV0(client, nil)
responsePacket.SetVersion(0)
} else {
responsePacket, _ = nex.NewPacketV1(client, nil)
responsePacket.SetVersion(1)
}
responsePacket.SetSource(0xA1)
responsePacket.SetDestination(0xAF)
responsePacket.SetType(nex.DataPacket)
responsePacket.SetPayload(rmcResponseBytes)
responsePacket.AddFlag(nex.FlagNeedsAck)
responsePacket.AddFlag(nex.FlagReliable)
commonAuthenticationProtocol.server.Send(responsePacket)
}

81
datastore/change_meta.go Normal file
View file

@ -0,0 +1,81 @@
package datastore
import (
"github.com/PretendoNetwork/nex-go/v2"
common_globals "github.com/PretendoNetwork/nex-protocols-common-go/v2/globals"
datastore "github.com/PretendoNetwork/nex-protocols-go/v2/datastore"
datastore_types "github.com/PretendoNetwork/nex-protocols-go/v2/datastore/types"
)
func (commonProtocol *CommonProtocol) changeMeta(err error, packet nex.PacketInterface, callID uint32, param datastore_types.DataStoreChangeMetaParam) (*nex.RMCMessage, *nex.Error) {
if commonProtocol.GetObjectInfoByDataID == nil {
common_globals.Logger.Warning("GetObjectInfoByDataID not defined")
return nil, nex.NewError(nex.ResultCodes.Core.NotImplemented, "change_error")
}
if commonProtocol.UpdateObjectPeriodByDataIDWithPassword == nil {
common_globals.Logger.Warning("UpdateObjectPeriodByDataIDWithPassword not defined")
return nil, nex.NewError(nex.ResultCodes.Core.NotImplemented, "change_error")
}
if commonProtocol.UpdateObjectMetaBinaryByDataIDWithPassword == nil {
common_globals.Logger.Warning("UpdateObjectMetaBinaryByDataIDWithPassword not defined")
return nil, nex.NewError(nex.ResultCodes.Core.NotImplemented, "change_error")
}
if commonProtocol.UpdateObjectDataTypeByDataIDWithPassword == nil {
common_globals.Logger.Warning("UpdateObjectDataTypeByDataIDWithPassword not defined")
return nil, nex.NewError(nex.ResultCodes.Core.NotImplemented, "change_error")
}
if err != nil {
common_globals.Logger.Error(err.Error())
return nil, nex.NewError(nex.ResultCodes.DataStore.Unknown, "change_error")
}
connection := packet.Sender()
endpoint := connection.Endpoint()
metaInfo, errCode := commonProtocol.GetObjectInfoByDataID(param.DataID)
if errCode != nil {
return nil, errCode
}
// TODO - Is this the right permission?
errCode = commonProtocol.VerifyObjectPermission(metaInfo.OwnerID, connection.PID(), metaInfo.DelPermission)
if errCode != nil {
return nil, errCode
}
if uint32(param.ModifiesFlag) & 0x08 != 0 {
errCode = commonProtocol.UpdateObjectPeriodByDataIDWithPassword(param.DataID, param.Period, param.UpdatePassword)
if errCode != nil {
return nil, errCode
}
}
if uint32(param.ModifiesFlag) & 0x10 != 0 {
errCode = commonProtocol.UpdateObjectMetaBinaryByDataIDWithPassword(param.DataID, param.MetaBinary, param.UpdatePassword)
if errCode != nil {
return nil, errCode
}
}
if uint32(param.ModifiesFlag) & 0x80 != 0 {
errCode = commonProtocol.UpdateObjectDataTypeByDataIDWithPassword(param.DataID, param.DataType, param.UpdatePassword)
if errCode != nil {
return nil, errCode
}
}
rmcResponse := nex.NewRMCSuccess(endpoint, nil)
rmcResponse.ProtocolID = datastore.ProtocolID
rmcResponse.MethodID = datastore.MethodChangeMeta
rmcResponse.CallID = callID
if commonProtocol.OnAfterChangeMeta != nil {
go commonProtocol.OnAfterChangeMeta(packet, param)
}
return rmcResponse, nil
}

View file

@ -0,0 +1,111 @@
package datastore
import (
"fmt"
"github.com/PretendoNetwork/nex-go/v2"
common_globals "github.com/PretendoNetwork/nex-protocols-common-go/v2/globals"
datastore "github.com/PretendoNetwork/nex-protocols-go/v2/datastore"
datastore_types "github.com/PretendoNetwork/nex-protocols-go/v2/datastore/types"
)
func (commonProtocol *CommonProtocol) completePostObject(err error, packet nex.PacketInterface, callID uint32, param datastore_types.DataStoreCompletePostParam) (*nex.RMCMessage, *nex.Error) {
if commonProtocol.minIOClient == nil {
common_globals.Logger.Warning("MinIOClient not defined")
return nil, nex.NewError(nex.ResultCodes.Core.NotImplemented, "change_error")
}
if commonProtocol.GetObjectInfoByDataID == nil {
common_globals.Logger.Warning("GetObjectInfoByDataID not defined")
return nil, nex.NewError(nex.ResultCodes.Core.NotImplemented, "change_error")
}
if commonProtocol.GetObjectOwnerByDataID == nil {
common_globals.Logger.Warning("GetObjectOwnerByDataID not defined")
return nil, nex.NewError(nex.ResultCodes.Core.NotImplemented, "change_error")
}
if commonProtocol.GetObjectSizeByDataID == nil {
common_globals.Logger.Warning("GetObjectSizeByDataID not defined")
return nil, nex.NewError(nex.ResultCodes.Core.NotImplemented, "change_error")
}
if commonProtocol.UpdateObjectUploadCompletedByDataID == nil {
common_globals.Logger.Warning("UpdateObjectUploadCompletedByDataID not defined")
return nil, nex.NewError(nex.ResultCodes.Core.NotImplemented, "change_error")
}
if commonProtocol.DeleteObjectByDataID == nil {
common_globals.Logger.Warning("DeleteObjectByDataID not defined")
return nil, nex.NewError(nex.ResultCodes.Core.NotImplemented, "change_error")
}
if err != nil {
common_globals.Logger.Error(err.Error())
return nil, nex.NewError(nex.ResultCodes.DataStore.Unknown, "change_error")
}
connection := packet.Sender()
endpoint := connection.Endpoint()
// * If GetObjectInfoByDataID returns data then that means
// * the object has already been marked as uploaded. So do
// * nothing
_, errCode := commonProtocol.GetObjectInfoByDataID(param.DataID)
if errCode == nil {
return nil, nex.NewError(nex.ResultCodes.DataStore.PermissionDenied, "change_error")
}
// * Only allow an objects owner to make this request
ownerPID, errCode := commonProtocol.GetObjectOwnerByDataID(param.DataID)
if errCode != nil {
return nil, errCode
}
if ownerPID != uint32(connection.PID()) {
return nil, nex.NewError(nex.ResultCodes.DataStore.PermissionDenied, "change_error")
}
bucket := commonProtocol.S3Bucket
key := fmt.Sprintf("%s/%d.bin", commonProtocol.s3DataKeyBase, param.DataID)
if param.IsSuccess {
objectSizeS3, err := commonProtocol.S3ObjectSize(bucket, key)
if err != nil {
common_globals.Logger.Error(err.Error())
return nil, nex.NewError(nex.ResultCodes.DataStore.NotFound, "change_error")
}
objectSizeDB, errCode := commonProtocol.GetObjectSizeByDataID(param.DataID)
if errCode != nil {
return nil, errCode
}
if objectSizeS3 != uint64(objectSizeDB) {
common_globals.Logger.Errorf("Object with DataID %d did not upload correctly! Mismatched sizes", param.DataID)
// TODO - Is this a good error?
return nil, nex.NewError(nex.ResultCodes.DataStore.Unknown, "change_error")
}
errCode = commonProtocol.UpdateObjectUploadCompletedByDataID(param.DataID, true)
if errCode != nil {
return nil, errCode
}
} else {
errCode := commonProtocol.DeleteObjectByDataID(param.DataID)
if errCode != nil {
return nil, errCode
}
}
rmcResponse := nex.NewRMCSuccess(endpoint, nil)
rmcResponse.ProtocolID = datastore.ProtocolID
rmcResponse.MethodID = datastore.MethodCompletePostObject
rmcResponse.CallID = callID
if commonProtocol.OnAfterCompletePostObject != nil {
go commonProtocol.OnAfterCompletePostObject(packet, param)
}
return rmcResponse, nil
}

View file

@ -0,0 +1,83 @@
package datastore
import (
"fmt"
"github.com/PretendoNetwork/nex-go/v2"
"github.com/PretendoNetwork/nex-go/v2/types"
common_globals "github.com/PretendoNetwork/nex-protocols-common-go/v2/globals"
datastore "github.com/PretendoNetwork/nex-protocols-go/v2/datastore"
)
func (commonProtocol *CommonProtocol) completePostObjects(err error, packet nex.PacketInterface, callID uint32, dataIDs types.List[types.UInt64]) (*nex.RMCMessage, *nex.Error) {
if commonProtocol.minIOClient == nil {
common_globals.Logger.Warning("MinIOClient not defined")
return nil, nex.NewError(nex.ResultCodes.Core.NotImplemented, "change_error")
}
if commonProtocol.GetObjectSizeByDataID == nil {
common_globals.Logger.Warning("GetObjectSizeByDataID not defined")
return nil, nex.NewError(nex.ResultCodes.Core.NotImplemented, "change_error")
}
if commonProtocol.UpdateObjectUploadCompletedByDataID == nil {
common_globals.Logger.Warning("UpdateObjectUploadCompletedByDataID not defined")
return nil, nex.NewError(nex.ResultCodes.Core.NotImplemented, "change_error")
}
if err != nil {
common_globals.Logger.Error(err.Error())
return nil, nex.NewError(nex.ResultCodes.DataStore.Unknown, "change_error")
}
connection := packet.Sender()
endpoint := connection.Endpoint()
var errorCode *nex.Error
for _, dataID := range dataIDs {
bucket := commonProtocol.S3Bucket
key := fmt.Sprintf("%s/%d.bin", commonProtocol.s3DataKeyBase, dataID)
objectSizeS3, err := commonProtocol.S3ObjectSize(bucket, key)
if err != nil {
common_globals.Logger.Error(err.Error())
errorCode = nex.NewError(nex.ResultCodes.DataStore.NotFound, "change_error")
break
}
objectSizeDB, errCode := commonProtocol.GetObjectSizeByDataID(dataID)
if errCode != nil {
errorCode = errCode
break
}
if objectSizeS3 != uint64(objectSizeDB) {
common_globals.Logger.Errorf("Object with DataID %d did not upload correctly! Mismatched sizes", dataID)
// TODO - Is this a good error?
errorCode = nex.NewError(nex.ResultCodes.DataStore.Unknown, "change_error")
break
}
errCode = commonProtocol.UpdateObjectUploadCompletedByDataID(dataID, true)
if errCode != nil {
errorCode = errCode
break
}
}
if errorCode != nil {
return nil, errorCode
}
rmcResponse := nex.NewRMCSuccess(endpoint, nil)
rmcResponse.ProtocolID = datastore.ProtocolID
rmcResponse.MethodID = datastore.MethodCompletePostObjects
rmcResponse.CallID = callID
if commonProtocol.OnAfterCompletePostObjects != nil {
go commonProtocol.OnAfterCompletePostObjects(packet, dataIDs)
}
return rmcResponse, nil
}

View file

@ -0,0 +1,54 @@
package datastore
import (
"github.com/PretendoNetwork/nex-go/v2"
common_globals "github.com/PretendoNetwork/nex-protocols-common-go/v2/globals"
datastore "github.com/PretendoNetwork/nex-protocols-go/v2/datastore"
datastore_types "github.com/PretendoNetwork/nex-protocols-go/v2/datastore/types"
)
func (commonProtocol *CommonProtocol) deleteObject(err error, packet nex.PacketInterface, callID uint32, param datastore_types.DataStoreDeleteParam) (*nex.RMCMessage, *nex.Error) {
if commonProtocol.GetObjectInfoByDataID == nil {
common_globals.Logger.Warning("GetObjectInfoByDataID not defined")
return nil, nex.NewError(nex.ResultCodes.Core.NotImplemented, "change_error")
}
if commonProtocol.DeleteObjectByDataIDWithPassword == nil {
common_globals.Logger.Warning("DeleteObjectByDataIDWithPassword not defined")
return nil, nex.NewError(nex.ResultCodes.Core.NotImplemented, "change_error")
}
if err != nil {
common_globals.Logger.Error(err.Error())
return nil, nex.NewError(nex.ResultCodes.DataStore.Unknown, "change_error")
}
connection := packet.Sender()
endpoint := connection.Endpoint()
metaInfo, errCode := commonProtocol.GetObjectInfoByDataID(param.DataID)
if errCode != nil {
return nil, errCode
}
errCode = commonProtocol.VerifyObjectPermission(metaInfo.OwnerID, connection.PID(), metaInfo.DelPermission)
if errCode != nil {
return nil, errCode
}
errCode = commonProtocol.DeleteObjectByDataIDWithPassword(param.DataID, param.UpdatePassword)
if errCode != nil {
return nil, errCode
}
rmcResponse := nex.NewRMCSuccess(endpoint, nil)
rmcResponse.ProtocolID = datastore.ProtocolID
rmcResponse.MethodID = datastore.MethodDeleteObject
rmcResponse.CallID = callID
if commonProtocol.OnAfterDeleteObject != nil {
go commonProtocol.OnAfterDeleteObject(packet, param)
}
return rmcResponse, nil
}

66
datastore/get_meta.go Normal file
View file

@ -0,0 +1,66 @@
package datastore
import (
"github.com/PretendoNetwork/nex-go/v2"
common_globals "github.com/PretendoNetwork/nex-protocols-common-go/v2/globals"
datastore "github.com/PretendoNetwork/nex-protocols-go/v2/datastore"
datastore_types "github.com/PretendoNetwork/nex-protocols-go/v2/datastore/types"
)
func (commonProtocol *CommonProtocol) getMeta(err error, packet nex.PacketInterface, callID uint32, param datastore_types.DataStoreGetMetaParam) (*nex.RMCMessage, *nex.Error) {
if commonProtocol.GetObjectInfoByPersistenceTargetWithPassword == nil {
common_globals.Logger.Warning("GetObjectInfoByPersistenceTargetWithPassword not defined")
return nil, nex.NewError(nex.ResultCodes.Core.NotImplemented, "change_error")
}
if commonProtocol.GetObjectInfoByDataIDWithPassword == nil {
common_globals.Logger.Warning("GetObjectInfoByDataIDWithPassword not defined")
return nil, nex.NewError(nex.ResultCodes.Core.NotImplemented, "change_error")
}
if err != nil {
common_globals.Logger.Error(err.Error())
return nil, nex.NewError(nex.ResultCodes.DataStore.Unknown, "change_error")
}
connection := packet.Sender()
endpoint := connection.Endpoint()
var pMetaInfo datastore_types.DataStoreMetaInfo
var errCode *nex.Error
// * Real server ignores PersistenceTarget if DataID is set
if param.DataID == 0 {
pMetaInfo, errCode = commonProtocol.GetObjectInfoByPersistenceTargetWithPassword(param.PersistenceTarget, param.AccessPassword)
} else {
pMetaInfo, errCode = commonProtocol.GetObjectInfoByDataIDWithPassword(param.DataID, param.AccessPassword)
}
if errCode != nil {
return nil, errCode
}
errCode = commonProtocol.VerifyObjectPermission(pMetaInfo.OwnerID, connection.PID(), pMetaInfo.Permission)
if errCode != nil {
return nil, errCode
}
pMetaInfo.FilterPropertiesByResultOption(param.ResultOption)
rmcResponseStream := nex.NewByteStreamOut(endpoint.LibraryVersions(), endpoint.ByteStreamSettings())
pMetaInfo.WriteTo(rmcResponseStream)
rmcResponseBody := rmcResponseStream.Bytes()
rmcResponse := nex.NewRMCSuccess(endpoint, rmcResponseBody)
rmcResponse.ProtocolID = datastore.ProtocolID
rmcResponse.MethodID = datastore.MethodGetMeta
rmcResponse.CallID = callID
if commonProtocol.OnAfterGetMeta != nil {
go commonProtocol.OnAfterGetMeta(packet, param)
}
return rmcResponse, nil
}

75
datastore/get_metas.go Normal file
View file

@ -0,0 +1,75 @@
package datastore
import (
"github.com/PretendoNetwork/nex-go/v2"
"github.com/PretendoNetwork/nex-go/v2/types"
common_globals "github.com/PretendoNetwork/nex-protocols-common-go/v2/globals"
datastore "github.com/PretendoNetwork/nex-protocols-go/v2/datastore"
datastore_types "github.com/PretendoNetwork/nex-protocols-go/v2/datastore/types"
)
func (commonProtocol *CommonProtocol) getMetas(err error, packet nex.PacketInterface, callID uint32, dataIDs types.List[types.UInt64], param datastore_types.DataStoreGetMetaParam) (*nex.RMCMessage, *nex.Error) {
if commonProtocol.GetObjectInfoByDataID == nil {
common_globals.Logger.Warning("GetObjectInfoByDataID not defined")
return nil, nex.NewError(nex.ResultCodes.Core.NotImplemented, "change_error")
}
if err != nil {
common_globals.Logger.Error(err.Error())
return nil, nex.NewError(nex.ResultCodes.DataStore.Unknown, "change_error")
}
connection := packet.Sender()
endpoint := connection.Endpoint()
// TODO - Verify if param.PersistenceTarget is respected? It wouldn't make sense here but who knows
pMetaInfo := types.NewList[datastore_types.DataStoreMetaInfo]()
pResults := types.NewList[types.QResult]()
// * param has an AccessPassword, but it goes unchecked here.
// * The password would need to be the same for every object
// * in the input array, which doesn't make any sense. Assuming
// * it's unused until proven otherwise
for _, dataID := range dataIDs {
objectInfo, errCode := commonProtocol.GetObjectInfoByDataID(dataID)
if errCode != nil {
objectInfo = datastore_types.NewDataStoreMetaInfo()
pResults = append(pResults, types.NewQResultError(errCode.ResultCode))
} else {
errCode = commonProtocol.VerifyObjectPermission(objectInfo.OwnerID, connection.PID(), objectInfo.Permission)
if errCode != nil {
objectInfo = datastore_types.NewDataStoreMetaInfo()
pResults = append(pResults, types.NewQResultError(errCode.ResultCode))
} else {
pResults = append(pResults, types.NewQResultSuccess(nex.ResultCodes.DataStore.Unknown))
}
objectInfo.FilterPropertiesByResultOption(param.ResultOption)
}
pMetaInfo = append(pMetaInfo, objectInfo)
}
rmcResponseStream := nex.NewByteStreamOut(endpoint.LibraryVersions(), endpoint.ByteStreamSettings())
pMetaInfo.WriteTo(rmcResponseStream)
pResults.WriteTo(rmcResponseStream)
rmcResponseBody := rmcResponseStream.Bytes()
rmcResponse := nex.NewRMCSuccess(endpoint, rmcResponseBody)
rmcResponse.ProtocolID = datastore.ProtocolID
rmcResponse.MethodID = datastore.MethodGetMetas
rmcResponse.CallID = callID
if commonProtocol.OnAfterGetMetas != nil {
go commonProtocol.OnAfterGetMetas(packet, dataIDs, param)
}
return rmcResponse, nil
}

View file

@ -0,0 +1,81 @@
package datastore
import (
"github.com/PretendoNetwork/nex-go/v2"
"github.com/PretendoNetwork/nex-go/v2/types"
common_globals "github.com/PretendoNetwork/nex-protocols-common-go/v2/globals"
datastore "github.com/PretendoNetwork/nex-protocols-go/v2/datastore"
datastore_types "github.com/PretendoNetwork/nex-protocols-go/v2/datastore/types"
)
func (commonProtocol *CommonProtocol) getMetasMultipleParam(err error, packet nex.PacketInterface, callID uint32, params types.List[datastore_types.DataStoreGetMetaParam]) (*nex.RMCMessage, *nex.Error) {
if commonProtocol.GetObjectInfoByPersistenceTargetWithPassword == nil {
common_globals.Logger.Warning("GetObjectInfoByPersistenceTargetWithPassword not defined")
return nil, nex.NewError(nex.ResultCodes.Core.NotImplemented, "change_error")
}
if commonProtocol.GetObjectInfoByDataIDWithPassword == nil {
common_globals.Logger.Warning("GetObjectInfoByDataIDWithPassword not defined")
return nil, nex.NewError(nex.ResultCodes.Core.NotImplemented, "change_error")
}
if err != nil {
common_globals.Logger.Error(err.Error())
return nil, nex.NewError(nex.ResultCodes.DataStore.Unknown, "change_error")
}
connection := packet.Sender()
endpoint := connection.Endpoint()
pMetaInfo := types.NewList[datastore_types.DataStoreMetaInfo]()
pResults := types.NewList[types.QResult]()
for _, param := range params {
var objectInfo datastore_types.DataStoreMetaInfo
var errCode *nex.Error
// * Real server ignores PersistenceTarget if DataID is set
if param.DataID == 0 {
objectInfo, errCode = commonProtocol.GetObjectInfoByPersistenceTargetWithPassword(param.PersistenceTarget, param.AccessPassword)
} else {
objectInfo, errCode = commonProtocol.GetObjectInfoByDataIDWithPassword(param.DataID, param.AccessPassword)
}
if errCode != nil {
objectInfo = datastore_types.NewDataStoreMetaInfo()
pResults = append(pResults, types.NewQResultError(errCode.ResultCode))
} else {
errCode = commonProtocol.VerifyObjectPermission(objectInfo.OwnerID, connection.PID(), objectInfo.Permission)
if errCode != nil {
objectInfo = datastore_types.NewDataStoreMetaInfo()
pResults = append(pResults, types.NewQResultError(errCode.ResultCode))
} else {
pResults = append(pResults, types.NewQResultSuccess(nex.ResultCodes.DataStore.Unknown))
}
objectInfo.FilterPropertiesByResultOption(param.ResultOption)
}
pMetaInfo = append(pMetaInfo, objectInfo)
}
rmcResponseStream := nex.NewByteStreamOut(endpoint.LibraryVersions(), endpoint.ByteStreamSettings())
pMetaInfo.WriteTo(rmcResponseStream)
pResults.WriteTo(rmcResponseStream)
rmcResponseBody := rmcResponseStream.Bytes()
rmcResponse := nex.NewRMCSuccess(endpoint, rmcResponseBody)
rmcResponse.ProtocolID = datastore.ProtocolID
rmcResponse.MethodID = datastore.MethodGetMetasMultipleParam
rmcResponse.CallID = callID
if commonProtocol.OnAfterGetMetasMultipleParam != nil {
go commonProtocol.OnAfterGetMetasMultipleParam(packet, params)
}
return rmcResponse, nil
}

View file

@ -0,0 +1,72 @@
package datastore
import (
"github.com/PretendoNetwork/nex-go/v2"
common_globals "github.com/PretendoNetwork/nex-protocols-common-go/v2/globals"
datastore "github.com/PretendoNetwork/nex-protocols-go/v2/datastore"
datastore_types "github.com/PretendoNetwork/nex-protocols-go/v2/datastore/types"
)
func (commonProtocol *CommonProtocol) postMetaBinary(err error, packet nex.PacketInterface, callID uint32, param datastore_types.DataStorePreparePostParam) (*nex.RMCMessage, *nex.Error) {
// * This method looks to function identically to DataStore::PreparePostObject,
// * except the only difference being it doesn't return an S3 upload URL. This
// * needs to be verified though, as there are other methods in the family such
// * as DataStore::PostMetaBinaryWithDataID which make less sense in this context,
// * unless those are just used to *update* a meta binary? Or maybe the DataID in
// * those methods is a pre-allocated DataID from the server? Needs more testing
if commonProtocol.InitializeObjectByPreparePostParam == nil {
common_globals.Logger.Warning("InitializeObjectByPreparePostParam not defined")
return nil, nex.NewError(nex.ResultCodes.Core.NotImplemented, "change_error")
}
if commonProtocol.InitializeObjectRatingWithSlot == nil {
common_globals.Logger.Warning("InitializeObjectRatingWithSlot not defined")
return nil, nex.NewError(nex.ResultCodes.Core.NotImplemented, "change_error")
}
if err != nil {
common_globals.Logger.Error(err.Error())
return nil, nex.NewError(nex.ResultCodes.DataStore.Unknown, "change_error")
}
connection := packet.Sender()
endpoint := connection.Endpoint()
// TODO - Need to verify what param.PersistenceInitParam.DeleteLastObject really means. It's often set to true even when it wouldn't make sense
dataID, errCode := commonProtocol.InitializeObjectByPreparePostParam(connection.PID(), param)
if errCode != nil {
common_globals.Logger.Errorf("Error code on object init: %s", errCode.Error())
return nil, errCode
}
// TODO - Should this be moved to InitializeObjectByPreparePostParam?
for _ , ratingInitParamWithSlot := range param.RatingInitParams {
errCode = commonProtocol.InitializeObjectRatingWithSlot(dataID, ratingInitParamWithSlot)
if errCode != nil {
common_globals.Logger.Errorf("Error code on rating init: %s", errCode.Error())
break
}
}
if errCode != nil {
return nil, errCode
}
rmcResponseStream := nex.NewByteStreamOut(endpoint.LibraryVersions(), endpoint.ByteStreamSettings())
rmcResponseStream.WriteUInt64LE(dataID)
rmcResponseBody := rmcResponseStream.Bytes()
rmcResponse := nex.NewRMCSuccess(endpoint, rmcResponseBody)
rmcResponse.ProtocolID = datastore.ProtocolID
rmcResponse.MethodID = datastore.MethodPostMetaBinary
rmcResponse.CallID = callID
if commonProtocol.OnAfterPostMetaBinary != nil {
go commonProtocol.OnAfterPostMetaBinary(packet, param)
}
return rmcResponse, nil
}

View file

@ -0,0 +1,90 @@
package datastore
import (
"fmt"
"time"
nex "github.com/PretendoNetwork/nex-go/v2"
"github.com/PretendoNetwork/nex-go/v2/types"
common_globals "github.com/PretendoNetwork/nex-protocols-common-go/v2/globals"
datastore "github.com/PretendoNetwork/nex-protocols-go/v2/datastore"
datastore_types "github.com/PretendoNetwork/nex-protocols-go/v2/datastore/types"
)
func (commonProtocol *CommonProtocol) prepareGetObject(err error, packet nex.PacketInterface, callID uint32, param datastore_types.DataStorePrepareGetParam) (*nex.RMCMessage, *nex.Error) {
if commonProtocol.GetObjectInfoByDataID == nil {
common_globals.Logger.Warning("GetObjectInfoByDataID not defined")
return nil, nex.NewError(nex.ResultCodes.Core.NotImplemented, "change_error")
}
if commonProtocol.S3Presigner == nil {
common_globals.Logger.Warning("S3Presigner not defined")
return nil, nex.NewError(nex.ResultCodes.Core.NotImplemented, "change_error")
}
if err != nil {
common_globals.Logger.Error(err.Error())
return nil, nex.NewError(nex.ResultCodes.Core.Unknown, "change_error")
}
connection := packet.Sender()
endpoint := connection.Endpoint()
var objectInfo datastore_types.DataStoreMetaInfo
var errCode *nex.Error
// * Real server ignores PersistenceTarget if DataID is set
if param.DataID == 0 {
objectInfo, errCode = commonProtocol.GetObjectInfoByPersistenceTargetWithPassword(param.PersistenceTarget, param.AccessPassword)
} else {
objectInfo, errCode = commonProtocol.GetObjectInfoByDataIDWithPassword(param.DataID, param.AccessPassword)
}
if errCode != nil {
return nil, errCode
}
errCode = commonProtocol.VerifyObjectPermission(objectInfo.OwnerID, connection.PID(), objectInfo.Permission)
if errCode != nil {
return nil, errCode
}
bucket := commonProtocol.S3Bucket
key := fmt.Sprintf("%s/%d.bin", commonProtocol.s3DataKeyBase, objectInfo.DataID)
url, err := commonProtocol.S3Presigner.GetObject(bucket, key, time.Minute*15)
if err != nil {
common_globals.Logger.Error(err.Error())
return nil, nex.NewError(nex.ResultCodes.DataStore.OperationNotAllowed, "change_error")
}
requestHeaders, errCode := commonProtocol.S3GetRequestHeaders()
if errCode != nil {
return nil, errCode
}
pReqGetInfo := datastore_types.NewDataStoreReqGetInfo()
pReqGetInfo.URL = types.NewString(url.String())
pReqGetInfo.RequestHeaders = types.NewList[datastore_types.DataStoreKeyValue]()
pReqGetInfo.Size = objectInfo.Size.Copy().(types.UInt32)
pReqGetInfo.RootCACert = types.NewBuffer(commonProtocol.RootCACert)
pReqGetInfo.DataID = param.DataID
pReqGetInfo.RequestHeaders = requestHeaders
rmcResponseStream := nex.NewByteStreamOut(endpoint.LibraryVersions(), endpoint.ByteStreamSettings())
pReqGetInfo.WriteTo(rmcResponseStream)
rmcResponseBody := rmcResponseStream.Bytes()
rmcResponse := nex.NewRMCSuccess(endpoint, rmcResponseBody)
rmcResponse.ProtocolID = datastore.ProtocolID
rmcResponse.MethodID = datastore.MethodPrepareGetObject
rmcResponse.CallID = callID
if commonProtocol.OnAfterPrepareGetObject != nil {
go commonProtocol.OnAfterPrepareGetObject(packet, param)
}
return rmcResponse, nil
}

View file

@ -0,0 +1,105 @@
package datastore
import (
"fmt"
"time"
"github.com/PretendoNetwork/nex-go/v2"
"github.com/PretendoNetwork/nex-go/v2/types"
common_globals "github.com/PretendoNetwork/nex-protocols-common-go/v2/globals"
datastore "github.com/PretendoNetwork/nex-protocols-go/v2/datastore"
datastore_types "github.com/PretendoNetwork/nex-protocols-go/v2/datastore/types"
)
func (commonProtocol *CommonProtocol) preparePostObject(err error, packet nex.PacketInterface, callID uint32, param datastore_types.DataStorePreparePostParam) (*nex.RMCMessage, *nex.Error) {
if commonProtocol.InitializeObjectByPreparePostParam == nil {
common_globals.Logger.Warning("InitializeObjectByPreparePostParam not defined")
return nil, nex.NewError(nex.ResultCodes.Core.NotImplemented, "change_error")
}
if commonProtocol.InitializeObjectRatingWithSlot == nil {
common_globals.Logger.Warning("InitializeObjectRatingWithSlot not defined")
return nil, nex.NewError(nex.ResultCodes.Core.NotImplemented, "change_error")
}
if commonProtocol.S3Presigner == nil {
common_globals.Logger.Warning("S3Presigner not defined")
return nil, nex.NewError(nex.ResultCodes.Core.NotImplemented, "change_error")
}
if err != nil {
common_globals.Logger.Error(err.Error())
return nil, nex.NewError(nex.ResultCodes.DataStore.Unknown, "change_error")
}
connection := packet.Sender()
endpoint := connection.Endpoint()
// TODO - Need to verify what param.PersistenceInitParam.DeleteLastObject really means. It's often set to true even when it wouldn't make sense
dataID, errCode := commonProtocol.InitializeObjectByPreparePostParam(connection.PID(), param)
if errCode != nil {
common_globals.Logger.Errorf("Error on object init: %s", errCode.Error())
return nil, errCode
}
// TODO - Should this be moved to InitializeObjectByPreparePostParam?
for _ , ratingInitParamWithSlot := range param.RatingInitParams {
errCode = commonProtocol.InitializeObjectRatingWithSlot(dataID, ratingInitParamWithSlot)
if errCode != nil {
common_globals.Logger.Errorf("Error on rating init: %s", errCode.Error())
break
}
}
if errCode != nil {
return nil, errCode
}
bucket := commonProtocol.S3Bucket
key := fmt.Sprintf("%s/%d.bin", commonProtocol.s3DataKeyBase, dataID)
URL, formData, err := commonProtocol.S3Presigner.PostObject(bucket, key, time.Minute*15)
if err != nil {
common_globals.Logger.Error(err.Error())
return nil, nex.NewError(nex.ResultCodes.DataStore.OperationNotAllowed, "change_error")
}
requestHeaders, errCode := commonProtocol.S3PostRequestHeaders()
if errCode != nil {
return nil, errCode
}
pReqPostInfo := datastore_types.NewDataStoreReqPostInfo()
pReqPostInfo.DataID = types.NewUInt64(dataID)
pReqPostInfo.URL = types.NewString(URL.String())
pReqPostInfo.RequestHeaders = types.NewList[datastore_types.DataStoreKeyValue]()
pReqPostInfo.FormFields = types.NewList[datastore_types.DataStoreKeyValue]()
pReqPostInfo.RootCACert = types.NewBuffer(commonProtocol.RootCACert)
pReqPostInfo.RequestHeaders = requestHeaders
for key, value := range formData {
field := datastore_types.NewDataStoreKeyValue()
field.Key = types.NewString(key)
field.Value = types.NewString(value)
pReqPostInfo.FormFields = append(pReqPostInfo.FormFields, field)
}
rmcResponseStream := nex.NewByteStreamOut(endpoint.LibraryVersions(), endpoint.ByteStreamSettings())
pReqPostInfo.WriteTo(rmcResponseStream)
rmcResponseBody := rmcResponseStream.Bytes()
rmcResponse := nex.NewRMCSuccess(endpoint, rmcResponseBody)
rmcResponse.ProtocolID = datastore.ProtocolID
rmcResponse.MethodID = datastore.MethodPreparePostObject
rmcResponse.CallID = callID
if commonProtocol.OnAfterPreparePostObject != nil {
go commonProtocol.OnAfterPreparePostObject(packet, param)
}
return rmcResponse, nil
}

163
datastore/protocol.go Normal file
View file

@ -0,0 +1,163 @@
package datastore
import (
"context"
"slices"
"strings"
"github.com/PretendoNetwork/nex-go/v2"
"github.com/PretendoNetwork/nex-go/v2/types"
datastore "github.com/PretendoNetwork/nex-protocols-go/v2/datastore"
datastore_types "github.com/PretendoNetwork/nex-protocols-go/v2/datastore/types"
"github.com/minio/minio-go/v7"
)
type CommonProtocol struct {
endpoint nex.EndpointInterface
protocol datastore.Interface
S3Bucket string
s3DataKeyBase string
s3NotifyKeyBase string
RootCACert []byte
minIOClient *minio.Client
S3Presigner S3PresignerInterface
GetUserFriendPIDs func(pid uint32) []uint32
GetObjectInfoByDataID func(dataID types.UInt64) (datastore_types.DataStoreMetaInfo, *nex.Error)
UpdateObjectPeriodByDataIDWithPassword func(dataID types.UInt64, dataType types.UInt16, password types.UInt64) *nex.Error
UpdateObjectMetaBinaryByDataIDWithPassword func(dataID types.UInt64, metaBinary types.QBuffer, password types.UInt64) *nex.Error
UpdateObjectDataTypeByDataIDWithPassword func(dataID types.UInt64, period types.UInt16, password types.UInt64) *nex.Error
GetObjectSizeByDataID func(dataID types.UInt64) (uint32, *nex.Error)
UpdateObjectUploadCompletedByDataID func(dataID types.UInt64, uploadCompleted bool) *nex.Error
GetObjectInfoByPersistenceTargetWithPassword func(persistenceTarget datastore_types.DataStorePersistenceTarget, password types.UInt64) (datastore_types.DataStoreMetaInfo, *nex.Error)
GetObjectInfoByDataIDWithPassword func(dataID types.UInt64, password types.UInt64) (datastore_types.DataStoreMetaInfo, *nex.Error)
S3GetRequestHeaders func() ([]datastore_types.DataStoreKeyValue, *nex.Error)
S3PostRequestHeaders func() ([]datastore_types.DataStoreKeyValue, *nex.Error)
InitializeObjectByPreparePostParam func(ownerPID types.PID, param datastore_types.DataStorePreparePostParam) (uint64, *nex.Error)
InitializeObjectRatingWithSlot func(dataID uint64, param datastore_types.DataStoreRatingInitParamWithSlot) *nex.Error
RateObjectWithPassword func(dataID types.UInt64, slot types.UInt8, ratingValue types.Int32, accessPassword types.UInt64) (datastore_types.DataStoreRatingInfo, *nex.Error)
DeleteObjectByDataIDWithPassword func(dataID types.UInt64, password types.UInt64) *nex.Error
DeleteObjectByDataID func(dataID types.UInt64) *nex.Error
GetObjectInfosByDataStoreSearchParam func(param datastore_types.DataStoreSearchParam, pid types.PID) ([]datastore_types.DataStoreMetaInfo, uint32, *nex.Error)
GetObjectOwnerByDataID func(dataID types.UInt64) (uint32, *nex.Error)
OnAfterDeleteObject func(packet nex.PacketInterface, param datastore_types.DataStoreDeleteParam)
OnAfterGetMeta func(packet nex.PacketInterface, param datastore_types.DataStoreGetMetaParam)
OnAfterGetMetas func(packet nex.PacketInterface, dataIDs types.List[types.UInt64], param datastore_types.DataStoreGetMetaParam)
OnAfterSearchObject func(packet nex.PacketInterface, param datastore_types.DataStoreSearchParam)
OnAfterRateObject func(packet nex.PacketInterface, target datastore_types.DataStoreRatingTarget, param datastore_types.DataStoreRateObjectParam, fetchRatings types.Bool)
OnAfterPostMetaBinary func(packet nex.PacketInterface, param datastore_types.DataStorePreparePostParam)
OnAfterPreparePostObject func(packet nex.PacketInterface, param datastore_types.DataStorePreparePostParam)
OnAfterPrepareGetObject func(packet nex.PacketInterface, param datastore_types.DataStorePrepareGetParam)
OnAfterCompletePostObject func(packet nex.PacketInterface, param datastore_types.DataStoreCompletePostParam)
OnAfterGetMetasMultipleParam func(packet nex.PacketInterface, params types.List[datastore_types.DataStoreGetMetaParam])
OnAfterCompletePostObjects func(packet nex.PacketInterface, dataIDs types.List[types.UInt64])
OnAfterChangeMeta func(packet nex.PacketInterface, param datastore_types.DataStoreChangeMetaParam)
OnAfterRateObjects func(packet nex.PacketInterface, targets types.List[datastore_types.DataStoreRatingTarget], params types.List[datastore_types.DataStoreRateObjectParam], transactional types.Bool, fetchRatings types.Bool)
}
func (c *CommonProtocol) S3StatObject(bucket, key string) (minio.ObjectInfo, error) {
return c.minIOClient.StatObject(context.TODO(), bucket, key, minio.StatObjectOptions{})
}
func (c *CommonProtocol) S3ObjectSize(bucket, key string) (uint64, error) {
info, err := c.S3StatObject(bucket, key)
if err != nil {
return 0, err
}
return uint64(info.Size), nil
}
func (c *CommonProtocol) VerifyObjectPermission(ownerPID, accessorPID types.PID, permission datastore_types.DataStorePermission) *nex.Error {
if permission.Permission > 3 {
return nex.NewError(nex.ResultCodes.DataStore.InvalidArgument, "change_error")
}
// * Owner can always access their own objects
if ownerPID.Equals(accessorPID) {
return nil
}
// * Allow anyone
if permission.Permission == 0 {
return nil
}
// * Allow only friends of the owner
if permission.Permission == 1 {
// TODO - This assumes a legacy client. Will not work on the Switch
friendsList := c.GetUserFriendPIDs(uint32(ownerPID))
if !slices.Contains(friendsList, uint32(accessorPID)) {
return nex.NewError(nex.ResultCodes.DataStore.PermissionDenied, "change_error")
}
}
// * Allow only users whose PIDs are defined in permission.RecipientIDs
if permission.Permission == 2 {
if !permission.RecipientIDs.Contains(accessorPID) {
return nex.NewError(nex.ResultCodes.DataStore.PermissionDenied, "change_error")
}
}
// * Allow only the owner
if permission.Permission == 3 {
if !ownerPID.Equals(accessorPID) {
return nex.NewError(nex.ResultCodes.DataStore.PermissionDenied, "change_error")
}
}
return nil
}
// SetDataKeyBase sets the base for the key to be used when uploading standard DataStore objects
func (c *CommonProtocol) SetDataKeyBase(base string) {
// * Just in case someone passes a badly formatted key
base = strings.TrimPrefix(base, "/")
base = strings.TrimSuffix(base, "/")
c.s3DataKeyBase = base
}
// SetNotifyKeyBase sets the base for the key to be used when uploading DataStore notification data
func (c *CommonProtocol) SetNotifyKeyBase(base string) {
// * Just in case someone passes a badly formatted key
base = strings.TrimPrefix(base, "/")
base = strings.TrimSuffix(base, "/")
c.s3NotifyKeyBase = base
}
// SetMinIOClient sets the MinIO S3 client
func (c *CommonProtocol) SetMinIOClient(client *minio.Client) {
c.minIOClient = client
c.S3Presigner = NewS3Presigner(c.minIOClient)
}
// NewCommonProtocol returns a new CommonProtocol
func NewCommonProtocol(protocol datastore.Interface) *CommonProtocol {
commonProtocol := &CommonProtocol{
endpoint: protocol.Endpoint(),
protocol: protocol,
RootCACert: []byte{},
S3GetRequestHeaders: func() ([]datastore_types.DataStoreKeyValue, *nex.Error) {
return []datastore_types.DataStoreKeyValue{}, nil
},
S3PostRequestHeaders: func() ([]datastore_types.DataStoreKeyValue, *nex.Error) {
return []datastore_types.DataStoreKeyValue{}, nil
},
}
protocol.SetHandlerDeleteObject(commonProtocol.deleteObject)
protocol.SetHandlerGetMeta(commonProtocol.getMeta)
protocol.SetHandlerGetMetas(commonProtocol.getMetas)
protocol.SetHandlerSearchObject(commonProtocol.searchObject)
protocol.SetHandlerRateObject(commonProtocol.rateObject)
protocol.SetHandlerPostMetaBinary(commonProtocol.postMetaBinary)
protocol.SetHandlerPreparePostObject(commonProtocol.preparePostObject)
protocol.SetHandlerPrepareGetObject(commonProtocol.prepareGetObject)
protocol.SetHandlerCompletePostObject(commonProtocol.completePostObject)
protocol.SetHandlerGetMetasMultipleParam(commonProtocol.getMetasMultipleParam)
protocol.SetHandlerCompletePostObjects(commonProtocol.completePostObjects)
protocol.SetHandlerChangeMeta(commonProtocol.changeMeta)
protocol.SetHandlerRateObjects(commonProtocol.rateObjects)
return commonProtocol
}

69
datastore/rate_object.go Normal file
View file

@ -0,0 +1,69 @@
package datastore
import (
"github.com/PretendoNetwork/nex-go/v2"
"github.com/PretendoNetwork/nex-go/v2/types"
common_globals "github.com/PretendoNetwork/nex-protocols-common-go/v2/globals"
datastore "github.com/PretendoNetwork/nex-protocols-go/v2/datastore"
datastore_types "github.com/PretendoNetwork/nex-protocols-go/v2/datastore/types"
)
func (commonProtocol *CommonProtocol) rateObject(err error, packet nex.PacketInterface, callID uint32, target datastore_types.DataStoreRatingTarget, param datastore_types.DataStoreRateObjectParam, fetchRatings types.Bool) (*nex.RMCMessage, *nex.Error) {
if commonProtocol.GetObjectInfoByDataIDWithPassword == nil {
common_globals.Logger.Warning("GetObjectInfoByDataIDWithPassword not defined")
return nil, nex.NewError(nex.ResultCodes.Core.NotImplemented, "change_error")
}
if commonProtocol.RateObjectWithPassword == nil {
common_globals.Logger.Warning("RateObjectWithPassword not defined")
return nil, nex.NewError(nex.ResultCodes.Core.NotImplemented, "change_error")
}
if err != nil {
common_globals.Logger.Error(err.Error())
return nil, nex.NewError(nex.ResultCodes.DataStore.Unknown, "change_error")
}
connection := packet.Sender()
endpoint := connection.Endpoint()
objectInfo, errCode := commonProtocol.GetObjectInfoByDataIDWithPassword(target.DataID, param.AccessPassword)
if errCode != nil {
return nil, errCode
}
errCode = commonProtocol.VerifyObjectPermission(objectInfo.OwnerID, connection.PID(), objectInfo.Permission)
if errCode != nil {
return nil, errCode
}
pRating, errCode := commonProtocol.RateObjectWithPassword(target.DataID, target.Slot, param.RatingValue, param.AccessPassword)
if errCode != nil {
return nil, errCode
}
// * This is kinda backwards. Server returns
// * the rating by default, so we check if
// * the client DOESN'T want it and then just
// * zero it out
if !fetchRatings {
pRating = datastore_types.NewDataStoreRatingInfo()
}
rmcResponseStream := nex.NewByteStreamOut(endpoint.LibraryVersions(), endpoint.ByteStreamSettings())
pRating.WriteTo(rmcResponseStream)
rmcResponseBody := rmcResponseStream.Bytes()
rmcResponse := nex.NewRMCSuccess(endpoint, rmcResponseBody)
rmcResponse.ProtocolID = datastore.ProtocolID
rmcResponse.MethodID = datastore.MethodRateObject
rmcResponse.CallID = callID
if commonProtocol.OnAfterRateObject != nil {
go commonProtocol.OnAfterRateObject(packet, target, param, fetchRatings)
}
return rmcResponse, nil
}

91
datastore/rate_objects.go Normal file
View file

@ -0,0 +1,91 @@
package datastore
import (
"github.com/PretendoNetwork/nex-go/v2"
"github.com/PretendoNetwork/nex-go/v2/types"
common_globals "github.com/PretendoNetwork/nex-protocols-common-go/v2/globals"
datastore "github.com/PretendoNetwork/nex-protocols-go/v2/datastore"
datastore_types "github.com/PretendoNetwork/nex-protocols-go/v2/datastore/types"
)
func (commonProtocol *CommonProtocol) rateObjects(err error, packet nex.PacketInterface, callID uint32, targets types.List[datastore_types.DataStoreRatingTarget], params types.List[datastore_types.DataStoreRateObjectParam], transactional types.Bool, fetchRatings types.Bool) (*nex.RMCMessage, *nex.Error) {
if commonProtocol.GetObjectInfoByDataIDWithPassword == nil {
common_globals.Logger.Warning("GetObjectInfoByDataIDWithPassword not defined")
return nil, nex.NewError(nex.ResultCodes.Core.NotImplemented, "change_error")
}
if commonProtocol.RateObjectWithPassword == nil {
common_globals.Logger.Warning("RateObjectWithPassword not defined")
return nil, nex.NewError(nex.ResultCodes.Core.NotImplemented, "change_error")
}
if err != nil {
common_globals.Logger.Error(err.Error())
return nil, nex.NewError(nex.ResultCodes.DataStore.Unknown, "change_error")
}
connection := packet.Sender()
endpoint := connection.Endpoint()
pRatings := types.NewList[datastore_types.DataStoreRatingInfo]()
pResults := types.NewList[types.QResult]()
// * Real DataStore does not actually check this.
// * I just didn't feel like working out the
// * logic for differing sized lists. So force
// * them to always be the same
if len(targets) != len(params) {
return nil, nex.NewError(nex.ResultCodes.DataStore.InvalidArgument, "change_error")
}
var errorCode *nex.Error
for i, target := range targets {
// * We already checked that targets and params will have the same length
param := params[i]
objectInfo, errCode := commonProtocol.GetObjectInfoByDataIDWithPassword(target.DataID, param.AccessPassword)
if errCode != nil {
errorCode = errCode
break
}
errCode = commonProtocol.VerifyObjectPermission(objectInfo.OwnerID, connection.PID(), objectInfo.Permission)
if errCode != nil {
errorCode = errCode
break
}
rating, errCode := commonProtocol.RateObjectWithPassword(target.DataID, target.Slot, param.RatingValue, param.AccessPassword)
if errCode != nil {
errorCode = errCode
break
}
if fetchRatings {
pRatings = append(pRatings, rating)
}
}
if errorCode != nil {
return nil, errorCode
}
rmcResponseStream := nex.NewByteStreamOut(endpoint.LibraryVersions(), endpoint.ByteStreamSettings())
pRatings.WriteTo(rmcResponseStream)
pResults.WriteTo(rmcResponseStream) // * pResults is ALWAYS empty in SMM?
rmcResponseBody := rmcResponseStream.Bytes()
rmcResponse := nex.NewRMCSuccess(endpoint, rmcResponseBody)
rmcResponse.ProtocolID = datastore.ProtocolID
rmcResponse.MethodID = datastore.MethodRateObjects
rmcResponse.CallID = callID
if commonProtocol.OnAfterRateObjects != nil {
go commonProtocol.OnAfterRateObjects(packet, targets, params, transactional, fetchRatings)
}
return rmcResponse, nil
}

51
datastore/s3_presigner.go Normal file
View file

@ -0,0 +1,51 @@
package datastore
import (
"context"
"net/url"
"time"
"github.com/minio/minio-go/v7"
)
type S3PresignerInterface interface {
GetObject(bucket, key string, lifetime time.Duration) (*url.URL, error)
PostObject(bucket, key string, lifetime time.Duration) (*url.URL, map[string]string, error)
}
type S3Presigner struct {
minio *minio.Client
}
func (p *S3Presigner) GetObject(bucket, key string, lifetime time.Duration) (*url.URL, error) {
reqParams := make(url.Values)
return p.minio.PresignedGetObject(context.Background(), bucket, key, lifetime, reqParams)
}
func (p *S3Presigner) PostObject(bucket, key string, lifetime time.Duration) (*url.URL, map[string]string, error) {
policy := minio.NewPostPolicy()
err := policy.SetBucket(bucket)
if err != nil {
return nil, nil, err
}
err = policy.SetKey(key)
if err != nil {
return nil, nil, err
}
err = policy.SetExpires(time.Now().UTC().Add(lifetime).UTC())
if err != nil {
return nil, nil, err
}
return p.minio.PresignedPostPolicy(context.Background(), policy)
}
func NewS3Presigner(minioClient *minio.Client) *S3Presigner {
return &S3Presigner{
minio: minioClient,
}
}

View file

@ -0,0 +1,97 @@
package datastore
import (
"github.com/PretendoNetwork/nex-go/v2"
"github.com/PretendoNetwork/nex-go/v2/types"
common_globals "github.com/PretendoNetwork/nex-protocols-common-go/v2/globals"
datastore "github.com/PretendoNetwork/nex-protocols-go/v2/datastore"
datastore_types "github.com/PretendoNetwork/nex-protocols-go/v2/datastore/types"
)
func (commonProtocol *CommonProtocol) searchObject(err error, packet nex.PacketInterface, callID uint32, param datastore_types.DataStoreSearchParam) (*nex.RMCMessage, *nex.Error) {
if commonProtocol.GetObjectInfosByDataStoreSearchParam == nil {
common_globals.Logger.Warning("GetObjectInfosByDataStoreSearchParam not defined")
return nil, nex.NewError(nex.ResultCodes.Core.NotImplemented, "change_error")
}
if err != nil {
common_globals.Logger.Error(err.Error())
return nil, nex.NewError(nex.ResultCodes.DataStore.Unknown, "change_error")
}
connection := packet.Sender()
endpoint := connection.Endpoint()
// * This is likely game-specific. Also developer note:
// * Please keep in mind that no results is allowed. errCode
// * should NEVER be DataStore::NotFound!
// *
// * DataStoreSearchParam contains a ResultRange to limit the
// * returned results. TotalCount is the total matching objects
// * in the database, whereas objects is the limited results
objects, totalCount, errCode := commonProtocol.GetObjectInfosByDataStoreSearchParam(param, connection.PID())
if errCode != nil {
return nil, errCode
}
pSearchResult := datastore_types.NewDataStoreSearchResult()
pSearchResult.Result = types.NewList[datastore_types.DataStoreMetaInfo]()
for _, object := range objects {
errCode = commonProtocol.VerifyObjectPermission(object.OwnerID, connection.PID(), object.Permission)
if errCode != nil {
// * Since we don't error here, should we also
// * "hide" these results by also decrementing
// * totalCount?
continue
}
object.FilterPropertiesByResultOption(param.ResultOption)
pSearchResult.Result = append(pSearchResult.Result, object)
}
var totalCountType uint8
// * Doing this here since the object
// * the permissions checks in the
// * previous loop will mutate the data
// * returned from the database
if totalCount == uint32(len(pSearchResult.Result)) {
totalCountType = 0 // * Has no more data. All possible results were returned
} else {
totalCountType = 1 // * Has more data. Not all possible results were returned
}
// * Disables the TotalCount
// *
// * Only seen in struct revision 3 or
// * NEX 4.0+
if param.StructureVersion >= 3 || endpoint.LibraryVersions().DataStore.GreaterOrEqual("4.0.0") {
if !param.TotalCountEnabled {
totalCount = 0
totalCountType = 3
}
}
pSearchResult.TotalCount = types.NewUInt32(totalCount)
pSearchResult.TotalCountType = types.NewUInt8(totalCountType)
rmcResponseStream := nex.NewByteStreamOut(endpoint.LibraryVersions(), endpoint.ByteStreamSettings())
pSearchResult.WriteTo(rmcResponseStream)
rmcResponseBody := rmcResponseStream.Bytes()
rmcResponse := nex.NewRMCSuccess(endpoint, rmcResponseBody)
rmcResponse.ProtocolID = datastore.ProtocolID
rmcResponse.MethodID = datastore.MethodSearchObject
rmcResponse.CallID = callID
if commonProtocol.OnAfterSearchObject != nil {
go commonProtocol.OnAfterSearchObject(packet, param)
}
return rmcResponse, nil
}

7
globals/globals.go Normal file
View file

@ -0,0 +1,7 @@
package common_globals
import (
"github.com/PretendoNetwork/plogger-go"
)
var Logger = plogger.NewLogger()

View file

@ -0,0 +1,27 @@
package common_globals
import (
"database/sql"
"sync"
"github.com/PretendoNetwork/nex-go/v2"
"github.com/PretendoNetwork/nex-go/v2/types"
)
// MatchmakingManager manages a matchmaking instance
type MatchmakingManager struct {
Database *sql.DB
Endpoint *nex.PRUDPEndPoint
Mutex *sync.RWMutex
GetUserFriendPIDs func(pid uint32) []uint32
GetDetailedGatheringByID func(manager *MatchmakingManager, sourcePID uint64, gatheringID uint32) (types.RVType, string, *nex.Error)
}
// NewMatchmakingManager returns a new MatchmakingManager
func NewMatchmakingManager(endpoint *nex.PRUDPEndPoint, db *sql.DB) *MatchmakingManager {
return &MatchmakingManager{
Endpoint: endpoint,
Database: db,
Mutex: &sync.RWMutex{},
}
}

187
globals/utils.go Normal file
View file

@ -0,0 +1,187 @@
package common_globals
import (
"slices"
"github.com/PretendoNetwork/nex-go/v2"
"github.com/PretendoNetwork/nex-go/v2/constants"
"github.com/PretendoNetwork/nex-go/v2/types"
match_making_constants "github.com/PretendoNetwork/nex-protocols-go/v2/match-making/constants"
match_making_types "github.com/PretendoNetwork/nex-protocols-go/v2/match-making/types"
notifications "github.com/PretendoNetwork/nex-protocols-go/v2/notifications"
notifications_types "github.com/PretendoNetwork/nex-protocols-go/v2/notifications/types"
)
var OutgoingCallID *nex.Counter[uint32] = nex.NewCounter[uint32](0)
// DeleteIndex removes a value from a slice with the given index
func DeleteIndex(s []uint32, index int) []uint32 {
s[index] = s[len(s)-1]
return s[:len(s)-1]
}
// RemoveDuplicates removes duplicate entries on a slice
func RemoveDuplicates[T comparable](sliceList []T) []T {
// * Extracted from https://stackoverflow.com/a/66751055
allKeys := make(map[T]bool)
list := []T{}
for _, item := range sliceList {
if _, value := allKeys[item]; !value {
allKeys[item] = true
list = append(list, item)
}
}
return list
}
// CheckValidGathering checks if a Gathering is valid
func CheckValidGathering(gathering match_making_types.Gathering) bool {
if len(gathering.Description) > 256 {
return false
}
return true
}
// CheckValidMatchmakeSession checks if a MatchmakeSession is valid
func CheckValidMatchmakeSession(matchmakeSession match_making_types.MatchmakeSession) bool {
if !CheckValidGathering(matchmakeSession.Gathering) {
return false
}
if len(matchmakeSession.Attributes) != 6 {
return false
}
if matchmakeSession.ProgressScore > 100 {
return false
}
if len(matchmakeSession.UserPassword) > 32 {
return false
}
// * Except for UserPassword, all strings must have a length lower than 256
if len(matchmakeSession.CodeWord) > 256 {
return false
}
// * All buffers must have a length lower than 512
if len(matchmakeSession.ApplicationBuffer) > 512 {
return false
}
if len(matchmakeSession.SessionKey) > 512 {
return false
}
return true
}
// CheckValidPersistentGathering checks if a PersistentGathering is valid
func CheckValidPersistentGathering(persistentGathering match_making_types.PersistentGathering) bool {
if !CheckValidGathering(persistentGathering.Gathering) {
return false
}
// * Only allow normal and password-protected community types
if uint32(persistentGathering.CommunityType) != uint32(match_making_constants.PersistentGatheringTypeOpen) && uint32(persistentGathering.CommunityType) != uint32(match_making_constants.PersistentGatheringTypePasswordLocked) {
return false
}
// * The UserPassword from a MatchmakeSession can be up to 32 characters, assuming the same here
//
// TODO - IS this actually the case?
if len(persistentGathering.Password) > 32 {
return false
}
if len(persistentGathering.Attribs) != 6 {
return false
}
// * All buffers must have a length lower than 512
if len(persistentGathering.ApplicationBuffer) > 512 {
return false
}
// * Check that the participation dates are within bounds of the current date
currentTime := types.NewDateTime(0).Now()
if persistentGathering.ParticipationStartDate != 0 && persistentGathering.ParticipationStartDate > currentTime {
return false
}
if persistentGathering.ParticipationEndDate != 0 && persistentGathering.ParticipationEndDate < currentTime {
return false
}
return true
}
// CanJoinMatchmakeSession checks if a PID is allowed to join a matchmake session
func CanJoinMatchmakeSession(manager *MatchmakingManager, pid types.PID, matchmakeSession match_making_types.MatchmakeSession) *nex.Error {
// TODO - Is this the right error?
if !matchmakeSession.OpenParticipation {
return nex.NewError(nex.ResultCodes.RendezVous.PermissionDenied, "change_error")
}
// * Only allow friends
// TODO - This won't work on Switch!
if matchmakeSession.ParticipationPolicy == 98 {
if manager.GetUserFriendPIDs == nil {
Logger.Warning("Missing GetUserFriendPIDs!")
return nex.NewError(nex.ResultCodes.Core.NotImplemented, "change_error")
}
friendList := manager.GetUserFriendPIDs(uint32(pid))
if !slices.Contains(friendList, uint32(matchmakeSession.OwnerPID)) {
return nex.NewError(nex.ResultCodes.RendezVous.NotFriend, "change_error")
}
}
return nil
}
// SendNotificationEvent sends a notification event to the specified targets
func SendNotificationEvent(endpoint *nex.PRUDPEndPoint, event notifications_types.NotificationEvent, targets []uint64) {
server := endpoint.Server
stream := nex.NewByteStreamOut(endpoint.LibraryVersions(), endpoint.ByteStreamSettings())
event.WriteTo(stream)
rmcRequest := nex.NewRMCRequest(endpoint)
rmcRequest.ProtocolID = notifications.ProtocolID
rmcRequest.CallID = OutgoingCallID.Next()
rmcRequest.MethodID = notifications.MethodProcessNotificationEvent
rmcRequest.Parameters = stream.Bytes()
rmcRequestBytes := rmcRequest.Bytes()
for _, pid := range targets {
target := endpoint.FindConnectionByPID(pid)
if target == nil {
Logger.Warning("Client not found")
continue
}
var messagePacket nex.PRUDPPacketInterface
if target.DefaultPRUDPVersion == 0 {
messagePacket, _ = nex.NewPRUDPPacketV0(server, target, nil)
} else {
messagePacket, _ = nex.NewPRUDPPacketV1(server, target, nil)
}
messagePacket.SetType(constants.DataPacket)
messagePacket.AddFlag(constants.PacketFlagNeedsAck)
messagePacket.AddFlag(constants.PacketFlagReliable)
messagePacket.SetSourceVirtualPortStreamType(target.StreamType)
messagePacket.SetSourceVirtualPortStreamID(endpoint.StreamID)
messagePacket.SetDestinationVirtualPortStreamType(target.StreamType)
messagePacket.SetDestinationVirtualPortStreamID(target.StreamID)
messagePacket.SetPayload(rmcRequestBytes)
server.Send(messagePacket)
}
}

45
go.mod
View file

@ -1,19 +1,40 @@
module github.com/PretendoNetwork/nex-protocols-common-go
module github.com/PretendoNetwork/nex-protocols-common-go/v2
go 1.18
go 1.22.1
toolchain go1.22.4
require (
github.com/PretendoNetwork/nex-go v1.0.8
github.com/PretendoNetwork/nex-protocols-go v1.0.8
github.com/PretendoNetwork/plogger-go v1.0.2
github.com/PretendoNetwork/nex-go/v2 v2.1.1
github.com/PretendoNetwork/nex-protocols-go/v2 v2.2.0
github.com/PretendoNetwork/plogger-go v1.0.4
github.com/PretendoNetwork/pq-extended v1.0.0
github.com/minio/minio-go/v7 v7.0.83
)
require (
github.com/fatih/color v1.13.0 // indirect
github.com/jwalton/go-supportscolor v1.1.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.16 // indirect
github.com/superwhiskers/crunch/v3 v3.5.6 // indirect
golang.org/x/sys v0.0.0-20220829200755-d48e67d00261 // indirect
golang.org/x/term v0.0.0-20220722155259-a9ba230a4035 // indirect
github.com/dolthub/maphash v0.1.0 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/fatih/color v1.18.0 // indirect
github.com/go-ini/ini v1.67.0 // indirect
github.com/goccy/go-json v0.10.4 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/jwalton/go-supportscolor v1.2.0 // indirect
github.com/klauspost/compress v1.17.11 // indirect
github.com/klauspost/cpuid/v2 v2.2.9 // indirect
github.com/lib/pq v1.10.9 // indirect
github.com/lxzan/gws v1.8.8 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/minio/md5-simd v1.1.2 // indirect
github.com/rasky/go-lzo v0.0.0-20200203143853-96a758eda86e // indirect
github.com/rs/xid v1.6.0 // indirect
github.com/superwhiskers/crunch/v3 v3.5.7 // indirect
golang.org/x/crypto v0.31.0 // indirect
golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 // indirect
golang.org/x/mod v0.22.0 // indirect
golang.org/x/net v0.33.0 // indirect
golang.org/x/sys v0.29.0 // indirect
golang.org/x/term v0.28.0 // indirect
golang.org/x/text v0.21.0 // indirect
)

100
go.sum
View file

@ -1,31 +1,75 @@
github.com/PretendoNetwork/nex-go v1.0.8 h1:shxQnFy1B1dUBsIs8l7RLsE/Ll/2AjlbXOwD6Us/Ybg=
github.com/PretendoNetwork/nex-go v1.0.8/go.mod h1:Bx2ONeSefnbJyE0IDIwGopxrjRrnszOV/uQv74Cx+m0=
github.com/PretendoNetwork/nex-protocols-go v1.0.8 h1:9ZmsSS4XTQ3+L1VaBb5vM6SJ3UDZ8gsRZk7n3o96Ny4=
github.com/PretendoNetwork/nex-protocols-go v1.0.8/go.mod h1:m9gMuVjNhcz4pDLylzA5e8CoumuhK1rfNWZhV4maYXw=
github.com/PretendoNetwork/plogger-go v1.0.2 h1:vWKEnEmJJzYwqLxLyiSsAvCrZV6qnnu/a0GQOjIfzY0=
github.com/PretendoNetwork/plogger-go v1.0.2/go.mod h1:7kD6M4vPq1JL4LTuPg6kuB1OvUBOwQOtAvTaUwMbwvU=
github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
github.com/jwalton/go-supportscolor v1.1.0 h1:HsXFJdMPjRUAx8cIW6g30hVSFYaxh9yRQwEWgkAR7lQ=
github.com/jwalton/go-supportscolor v1.1.0/go.mod h1:hFVUAZV2cWg+WFFC4v8pT2X/S2qUUBYMioBD9AINXGs=
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/superwhiskers/crunch/v3 v3.5.6 h1:gxAMb9Ga/DerwN8jg3U1OMAEPLww3WCW465R07owRUQ=
github.com/superwhiskers/crunch/v3 v3.5.6/go.mod h1:4ub2EKgF1MAhTjoOCTU4b9uLMsAweHEa89aRrfAypXA=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
github.com/PretendoNetwork/nex-go/v2 v2.1.1 h1:OI2W14lIw2V2kItV2c/57UGLrT6PV5CuhyU07VAAki4=
github.com/PretendoNetwork/nex-go/v2 v2.1.1/go.mod h1:3LyJzsv3AataJW8D0binp15Q8ZH22MWTYly1VNtXi64=
github.com/PretendoNetwork/nex-protocols-go/v2 v2.2.0 h1:abbCXgYN9icR9hmGV9GkuiOvg92+WBeVTnvtiAU06QU=
github.com/PretendoNetwork/nex-protocols-go/v2 v2.2.0/go.mod h1:+soBHmwX6ixGxj6cphLuCvfJqxcZPuowc/5e7Qi9Bz0=
github.com/PretendoNetwork/plogger-go v1.0.4 h1:PF7xHw9eDRHH+RsAP9tmAE7fG0N0p6H4iPwHKnsoXwc=
github.com/PretendoNetwork/plogger-go v1.0.4/go.mod h1:7kD6M4vPq1JL4LTuPg6kuB1OvUBOwQOtAvTaUwMbwvU=
github.com/PretendoNetwork/pq-extended v1.0.0 h1:GHZ0hLvCvmYKQPTV9I9XtTx8J1iB5Z9CEnfW2tUpsYg=
github.com/PretendoNetwork/pq-extended v1.0.0/go.mod h1:bq6Ai+3lG4/M0iamUBt2Uzi5vL/nYy1a1Ar2ow9NDF0=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dolthub/maphash v0.1.0 h1:bsQ7JsF4FkkWyrP3oCnFJgrCUAFbFf3kOl4L/QxPDyQ=
github.com/dolthub/maphash v0.1.0/go.mod h1:gkg4Ch4CdCDu5h6PMriVLawB7koZ+5ijb9puGMV50a4=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A=
github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
github.com/goccy/go-json v0.10.4 h1:JSwxQzIqKfmFX1swYPpUThQZp/Ka4wzJdK0LWVytLPM=
github.com/goccy/go-json v0.10.4/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/jwalton/go-supportscolor v1.2.0 h1:g6Ha4u7Vm3LIsQ5wmeBpS4gazu0UP1DRDE8y6bre4H8=
github.com/jwalton/go-supportscolor v1.2.0/go.mod h1:hFVUAZV2cWg+WFFC4v8pT2X/S2qUUBYMioBD9AINXGs=
github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc=
github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0=
github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.2.9 h1:66ze0taIn2H33fBvCkXuv9BmCwDfafmiIVpKV9kKGuY=
github.com/klauspost/cpuid/v2 v2.2.9/go.mod h1:rqkxqrZ1EhYM9G+hXH7YdowN5R5RGN6NK4QwQ3WMXF8=
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/lxzan/gws v1.8.8 h1:st193ZG8qN8sSw8/g/UituFhs7etmKzS7jUqhijg5wM=
github.com/lxzan/gws v1.8.8/go.mod h1:FcGeRMB7HwGuTvMLR24ku0Zx0p6RXqeKASeMc4VYgi4=
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34=
github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM=
github.com/minio/minio-go/v7 v7.0.83 h1:W4Kokksvlz3OKf3OqIlzDNKd4MERlC2oN8YptwJ0+GA=
github.com/minio/minio-go/v7 v7.0.83/go.mod h1:57YXpvc5l3rjPdhqNrDsvVlY0qPI6UTk1bflAe+9doY=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rasky/go-lzo v0.0.0-20200203143853-96a758eda86e h1:dCWirM5F3wMY+cmRda/B1BiPsFtmzXqV9b0hLWtVBMs=
github.com/rasky/go-lzo v0.0.0-20200203143853-96a758eda86e/go.mod h1:9leZcVcItj6m9/CfHY5Em/iBrCz7js8LcRQGTKEEv2M=
github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU=
github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/superwhiskers/crunch/v3 v3.5.7 h1:N9RLxaR65C36i26BUIpzPXGy2f6pQ7wisu2bawbKNqg=
github.com/superwhiskers/crunch/v3 v3.5.7/go.mod h1:4ub2EKgF1MAhTjoOCTU4b9uLMsAweHEa89aRrfAypXA=
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 h1:yqrTHse8TCMW1M1ZCP+VAR/l0kKxwaAIqN/il7x4voA=
golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8/go.mod h1:tujkw807nyEEAamNbDrEGzRav+ilXA7PCRAd6xsmwiU=
golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4=
golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I=
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220829200755-d48e67d00261 h1:v6hYoSR9T5oet+pMXwUWkbiVqx/63mlHjefrHmxwfeY=
golang.org/x/sys v0.0.0-20220829200755-d48e67d00261/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20220722155259-a9ba230a4035 h1:Q5284mrmYTpACcm+eAKjKJH48BBwSyfJqmmGDTtT8Vc=
golang.org/x/term v0.0.0-20220722155259-a9ba230a4035/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg=
golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek=
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View file

@ -1,17 +0,0 @@
github.com/PretendoNetwork/plogger v1.0.0 h1:wpS34n9eJjJgadjYKuC3VFXZ0EYbJ5/RhkBXI6h7Kmw=
github.com/PretendoNetwork/plogger-go v1.0.2 h1:vWKEnEmJJzYwqLxLyiSsAvCrZV6qnnu/a0GQOjIfzY0=
github.com/PretendoNetwork/plogger-go v1.0.2/go.mod h1:7kD6M4vPq1JL4LTuPg6kuB1OvUBOwQOtAvTaUwMbwvU=
github.com/mattn/go-colorable v0.1.9 h1:sqDoxXbdeALODt0DAeJCVp38ps9ZogZEAXjus69YV3U=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8 h1:OH54vjqzRWmbJ62fjuhxy7AxFFgoHN0/DPc/UrL8cAs=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220829200755-d48e67d00261 h1:v6hYoSR9T5oet+pMXwUWkbiVqx/63mlHjefrHmxwfeY=
golang.org/x/sys v0.0.0-20220829200755-d48e67d00261/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
golang.org/x/term v0.0.0-20220722155259-a9ba230a4035 h1:Q5284mrmYTpACcm+eAKjKJH48BBwSyfJqmmGDTtT8Vc=
golang.org/x/term v0.0.0-20220722155259-a9ba230a4035/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=

View file

@ -0,0 +1,52 @@
package match_making_ext
import (
"github.com/PretendoNetwork/nex-go/v2"
"github.com/PretendoNetwork/nex-go/v2/types"
common_globals "github.com/PretendoNetwork/nex-protocols-common-go/v2/globals"
"github.com/PretendoNetwork/nex-protocols-common-go/v2/match-making/database"
match_making_ext "github.com/PretendoNetwork/nex-protocols-go/v2/match-making-ext"
)
func (commonProtocol *CommonProtocol) endParticipation(err error, packet nex.PacketInterface, callID uint32, idGathering types.UInt32, strMessage types.String) (*nex.RMCMessage, *nex.Error) {
if err != nil {
common_globals.Logger.Error(err.Error())
return nil, nex.NewError(nex.ResultCodes.Core.InvalidArgument, "change_error")
}
if len(strMessage) > 256 {
return nil, nex.NewError(nex.ResultCodes.Core.InvalidArgument, "change_error")
}
commonProtocol.manager.Mutex.Lock()
connection := packet.Sender().(*nex.PRUDPConnection)
endpoint := connection.Endpoint().(*nex.PRUDPEndPoint)
nexError := database.EndGatheringParticipation(commonProtocol.manager, uint32(idGathering), connection, string(strMessage))
if nexError != nil {
commonProtocol.manager.Mutex.Unlock()
return nil, nexError
}
commonProtocol.manager.Mutex.Unlock()
retval := types.NewBool(true)
rmcResponseStream := nex.NewByteStreamOut(endpoint.LibraryVersions(), endpoint.ByteStreamSettings())
retval.WriteTo(rmcResponseStream)
rmcResponseBody := rmcResponseStream.Bytes()
rmcResponse := nex.NewRMCSuccess(endpoint, rmcResponseBody)
rmcResponse.ProtocolID = match_making_ext.ProtocolID
rmcResponse.MethodID = match_making_ext.MethodEndParticipation
rmcResponse.CallID = callID
if commonProtocol.OnAfterEndParticipation != nil {
go commonProtocol.OnAfterEndParticipation(packet, idGathering, strMessage)
}
return rmcResponse, nil
}

View file

@ -0,0 +1,32 @@
package match_making_ext
import (
"github.com/PretendoNetwork/nex-go/v2"
"github.com/PretendoNetwork/nex-go/v2/types"
common_globals "github.com/PretendoNetwork/nex-protocols-common-go/v2/globals"
match_making_ext "github.com/PretendoNetwork/nex-protocols-go/v2/match-making-ext"
)
type CommonProtocol struct {
endpoint nex.EndpointInterface
protocol match_making_ext.Interface
manager *common_globals.MatchmakingManager
OnAfterEndParticipation func(acket nex.PacketInterface, idGathering types.UInt32, strMessage types.String)
}
// SetManager defines the matchmaking manager to be used by the common protocol
func (commonProtocol *CommonProtocol) SetManager(manager *common_globals.MatchmakingManager) {
commonProtocol.manager = manager
}
// NewCommonProtocol returns a new CommonProtocol
func NewCommonProtocol(protocol match_making_ext.Interface) *CommonProtocol {
commonProtocol := &CommonProtocol{
endpoint: protocol.Endpoint(),
protocol: protocol,
}
protocol.SetHandlerEndParticipation(commonProtocol.endParticipation)
return commonProtocol
}

View file

@ -0,0 +1,165 @@
package database
import (
"github.com/PretendoNetwork/nex-go/v2"
"github.com/PretendoNetwork/nex-go/v2/types"
common_globals "github.com/PretendoNetwork/nex-protocols-common-go/v2/globals"
"github.com/PretendoNetwork/nex-protocols-common-go/v2/match-making/tracking"
match_making "github.com/PretendoNetwork/nex-protocols-go/v2/match-making"
match_making_types "github.com/PretendoNetwork/nex-protocols-go/v2/match-making/types"
notifications "github.com/PretendoNetwork/nex-protocols-go/v2/notifications"
notifications_types "github.com/PretendoNetwork/nex-protocols-go/v2/notifications/types"
pqextended "github.com/PretendoNetwork/pq-extended"
)
// DisconnectParticipant disconnects a participant from all non-persistent gatherings
func DisconnectParticipant(manager *common_globals.MatchmakingManager, connection *nex.PRUDPConnection) {
var nexError *nex.Error
rows, err := manager.Database.Query(`SELECT id, owner_pid, host_pid, min_participants, max_participants, participation_policy, policy_argument, flags, state, description, type, participants FROM matchmaking.gatherings WHERE $1 = ANY(participants) AND registered=true`, connection.PID())
if err != nil {
common_globals.Logger.Critical(err.Error())
return
}
for rows.Next() {
gathering := match_making_types.NewGathering()
var gatheringType string
var participants []uint64
err = rows.Scan(
&gathering.ID,
&gathering.OwnerPID,
&gathering.HostPID,
&gathering.MinimumParticipants,
&gathering.MaximumParticipants,
&gathering.ParticipationPolicy,
&gathering.PolicyArgument,
&gathering.Flags,
&gathering.State,
&gathering.Description,
&gatheringType,
pqextended.Array(&participants),
)
if err != nil {
common_globals.Logger.Critical(err.Error())
continue
}
// * If the gathering is a PersistentGathering and the gathering isn't set to leave when disconnecting, ignore and continue
//
// TODO - Is the match_making.GatheringFlags.PersistentGathering check correct here?
if uint32(gathering.Flags) & match_making.GatheringFlags.PersistentGathering != 0 || (gatheringType == "PersistentGathering" && uint32(gathering.Flags) & match_making.GatheringFlags.PersistentGatheringLeaveParticipation == 0) {
continue
}
// * Since the participant is leaving, override the participant list to avoid sending notifications to them
participants, nexError = RemoveParticipantFromGathering(manager, uint32(gathering.ID), uint64(connection.PID()))
if nexError != nil {
common_globals.Logger.Error(nexError.Error())
continue
}
nexError = tracking.LogDisconnectGathering(manager.Database, connection.PID(), uint32(gathering.ID), participants)
if nexError != nil {
common_globals.Logger.Error(nexError.Error())
continue
}
// * If the gathering is a persistent gathering and allows zero users, only remove the participant from the gathering
if uint32(gathering.Flags) & (match_making.GatheringFlags.PersistentGathering | match_making.GatheringFlags.PersistentGatheringAllowZeroUsers) != 0 {
continue
}
if len(participants) == 0 {
// * There are no more participants, so we only have to unregister the gathering
// * Since the participant is disconnecting, we don't send notification events
nexError = UnregisterGathering(manager, connection.PID(), uint32(gathering.ID))
if nexError != nil {
common_globals.Logger.Error(nexError.Error())
}
continue
}
ownerPID := uint64(gathering.OwnerPID)
if connection.PID().Equals(gathering.OwnerPID) {
// * This flag tells the server to change the matchmake session owner if they disconnect
// * If the flag is not set, delete the session
// * More info: https://nintendo-wiki.pretendo.network/docs/nex/protocols/match-making/types#flags
if uint32(gathering.Flags) & match_making.GatheringFlags.DisconnectChangeOwner == 0 {
nexError = UnregisterGathering(manager, connection.PID(), uint32(gathering.ID))
if nexError != nil {
common_globals.Logger.Error(nexError.Error())
continue
}
category := notifications.NotificationCategories.GatheringUnregistered
subtype := notifications.NotificationSubTypes.GatheringUnregistered.None
oEvent := notifications_types.NewNotificationEvent()
oEvent.PIDSource = connection.PID().Copy().(types.PID)
oEvent.Type = types.NewUInt32(notifications.BuildNotificationType(category, subtype))
oEvent.Param1 = gathering.ID
common_globals.SendNotificationEvent(connection.Endpoint().(*nex.PRUDPEndPoint), oEvent, common_globals.RemoveDuplicates(participants))
continue
}
ownerPID, nexError = MigrateGatheringOwnership(manager, connection, gathering, participants)
if nexError != nil {
common_globals.Logger.Error(nexError.Error())
}
}
// * If the host has disconnected, set the owner as the new host. We can guarantee that the ownerPID is not zero,
// * since otherwise the gathering would have been unregistered by MigrateGatheringOwnership
if connection.PID().Equals(gathering.HostPID) && ownerPID != 0 {
nexError = UpdateSessionHost(manager, uint32(gathering.ID), types.NewPID(ownerPID), types.NewPID(ownerPID))
if nexError != nil {
common_globals.Logger.Error(nexError.Error())
} else {
category := notifications.NotificationCategories.HostChanged
subtype := notifications.NotificationSubTypes.HostChanged.None
oEvent := notifications_types.NewNotificationEvent()
oEvent.PIDSource = connection.PID().Copy().(types.PID)
oEvent.Type = types.NewUInt32(notifications.BuildNotificationType(category, subtype))
oEvent.Param1 = gathering.ID
// TODO - Should the notification actually be sent to all participants?
common_globals.SendNotificationEvent(connection.Endpoint().(*nex.PRUDPEndPoint), oEvent, common_globals.RemoveDuplicates(participants))
nexError = tracking.LogChangeHost(manager.Database, connection.PID(), uint32(gathering.ID), gathering.HostPID, types.NewPID(ownerPID))
if nexError != nil {
common_globals.Logger.Error(nexError.Error())
continue
}
}
}
category := notifications.NotificationCategories.Participation
subtype := notifications.NotificationSubTypes.Participation.Disconnected
oEvent := notifications_types.NewNotificationEvent()
oEvent.PIDSource = connection.PID().Copy().(types.PID)
oEvent.Type = types.NewUInt32(notifications.BuildNotificationType(category, subtype))
oEvent.Param1 = gathering.ID
oEvent.Param2 = types.NewUInt32(uint32(connection.PID())) // TODO - This assumes a legacy client. Will not work on the Switch
var participantEndedTargets []uint64
// * When the VerboseParticipants or VerboseParticipantsEx flags are set, all participant notification events are sent to everyone
if uint32(gathering.Flags) & (match_making.GatheringFlags.VerboseParticipants | match_making.GatheringFlags.VerboseParticipantsEx) != 0 {
participantEndedTargets = common_globals.RemoveDuplicates(participants)
} else {
participantEndedTargets = []uint64{uint64(gathering.OwnerPID)}
}
// * Only send the notification event to the owner
common_globals.SendNotificationEvent(connection.Endpoint().(*nex.PRUDPEndPoint), oEvent, participantEndedTargets)
}
rows.Close()
}

View file

@ -0,0 +1,126 @@
package database
import (
"slices"
"github.com/PretendoNetwork/nex-go/v2"
"github.com/PretendoNetwork/nex-go/v2/types"
common_globals "github.com/PretendoNetwork/nex-protocols-common-go/v2/globals"
"github.com/PretendoNetwork/nex-protocols-common-go/v2/match-making/tracking"
match_making "github.com/PretendoNetwork/nex-protocols-go/v2/match-making"
notifications "github.com/PretendoNetwork/nex-protocols-go/v2/notifications"
notifications_types "github.com/PretendoNetwork/nex-protocols-go/v2/notifications/types"
)
// EndGatheringParticipation ends the participation of a connection within a gathering and performs any additional handling required
func EndGatheringParticipation(manager *common_globals.MatchmakingManager, gatheringID uint32, connection *nex.PRUDPConnection, message string) *nex.Error {
gathering, _, participants, _, nexError := FindGatheringByID(manager, gatheringID)
if nexError != nil {
return nexError
}
// TODO - Is this the right error?
if !slices.Contains(participants, uint64(connection.PID())) {
return nex.NewError(nex.ResultCodes.RendezVous.NotParticipatedGathering, "change_error")
}
newParticipants, nexError := RemoveParticipantFromGathering(manager, gatheringID, uint64(connection.PID()))
if nexError != nil {
return nexError
}
nexError = tracking.LogLeaveGathering(manager.Database, connection.PID(), gatheringID, newParticipants)
if nexError != nil {
return nexError
}
// * If the gathering is a persistent gathering and allows zero users, only remove the participant from the gathering
if uint32(gathering.Flags) & (match_making.GatheringFlags.PersistentGathering | match_making.GatheringFlags.PersistentGatheringAllowZeroUsers) != 0 {
return nil
}
if len(newParticipants) == 0 {
// * There are no more participants, so we just unregister the gathering
return UnregisterGathering(manager, connection.PID(), gatheringID)
}
var ownerPID uint64 = uint64(gathering.OwnerPID)
if connection.PID().Equals(gathering.OwnerPID) {
// * This flag tells the server to change the matchmake session owner if they disconnect
// * If the flag is not set, delete the session
// * More info: https://nintendo-wiki.pretendo.network/docs/nex/protocols/match-making/types#flags
if uint32(gathering.Flags) & match_making.GatheringFlags.DisconnectChangeOwner == 0 {
nexError = UnregisterGathering(manager, connection.PID(), gatheringID)
if nexError != nil {
return nexError
}
category := notifications.NotificationCategories.GatheringUnregistered
subtype := notifications.NotificationSubTypes.GatheringUnregistered.None
oEvent := notifications_types.NewNotificationEvent()
oEvent.PIDSource = connection.PID().Copy().(types.PID)
oEvent.Type = types.NewUInt32(notifications.BuildNotificationType(category, subtype))
oEvent.Param1 = types.NewUInt32(gatheringID)
common_globals.SendNotificationEvent(connection.Endpoint().(*nex.PRUDPEndPoint), oEvent, common_globals.RemoveDuplicates(newParticipants))
return nil
}
ownerPID, nexError = MigrateGatheringOwnership(manager, connection, gathering, newParticipants)
if nexError != nil {
return nexError
}
}
// * If the host has disconnected, set the owner as the new host. We can guarantee that the ownerPID is not zero,
// * since otherwise the gathering would have been unregistered by MigrateGatheringOwnership
if connection.PID().Equals(gathering.HostPID) && ownerPID != 0 {
nexError = UpdateSessionHost(manager, gatheringID, types.NewPID(ownerPID), types.NewPID(ownerPID))
if nexError != nil {
common_globals.Logger.Error(nexError.Error())
return nexError
}
category := notifications.NotificationCategories.HostChanged
subtype := notifications.NotificationSubTypes.HostChanged.None
oEvent := notifications_types.NewNotificationEvent()
oEvent.PIDSource = connection.PID().Copy().(types.PID)
oEvent.Type = types.NewUInt32(notifications.BuildNotificationType(category, subtype))
oEvent.Param1 = types.NewUInt32(gatheringID)
// TODO - Should the notification actually be sent to all participants?
common_globals.SendNotificationEvent(connection.Endpoint().(*nex.PRUDPEndPoint), oEvent, common_globals.RemoveDuplicates(participants))
nexError = tracking.LogChangeHost(manager.Database, connection.PID(), gatheringID, gathering.HostPID, types.NewPID(ownerPID))
if nexError != nil {
common_globals.Logger.Error(nexError.Error())
return nexError
}
}
category := notifications.NotificationCategories.Participation
subtype := notifications.NotificationSubTypes.Participation.Ended
oEvent := notifications_types.NewNotificationEvent()
oEvent.PIDSource = connection.PID().Copy().(types.PID)
oEvent.Type = types.NewUInt32(notifications.BuildNotificationType(category, subtype))
oEvent.Param1 = types.NewUInt32(gatheringID)
oEvent.Param2 = types.NewUInt32(uint32((connection.PID()))) // TODO - This assumes a legacy client. Will not work on the Switch
oEvent.StrParam = types.NewString(message)
var participationEndedTargets []uint64
// * When the VerboseParticipants or VerboseParticipantsEx flags are set, all participant notification events are sent to everyone
if uint32(gathering.Flags) & (match_making.GatheringFlags.VerboseParticipants | match_making.GatheringFlags.VerboseParticipantsEx) != 0 {
participationEndedTargets = common_globals.RemoveDuplicates(participants)
} else {
participationEndedTargets = []uint64{uint64(gathering.OwnerPID)}
}
common_globals.SendNotificationEvent(connection.Endpoint().(*nex.PRUDPEndPoint), oEvent, participationEndedTargets)
return nil
}

View file

@ -0,0 +1,49 @@
package database
import (
"database/sql"
"time"
"github.com/PretendoNetwork/nex-go/v2"
"github.com/PretendoNetwork/nex-go/v2/types"
common_globals "github.com/PretendoNetwork/nex-protocols-common-go/v2/globals"
match_making_types "github.com/PretendoNetwork/nex-protocols-go/v2/match-making/types"
pqextended "github.com/PretendoNetwork/pq-extended"
)
// FindGatheringByID finds a gathering on a database with the given ID. Returns the gathering, its type, the participant list and the started time
func FindGatheringByID(manager *common_globals.MatchmakingManager, id uint32) (match_making_types.Gathering, string, []uint64, types.DateTime, *nex.Error) {
row := manager.Database.QueryRow(`SELECT owner_pid, host_pid, min_participants, max_participants, participation_policy, policy_argument, flags, state, description, type, participants, started_time FROM matchmaking.gatherings WHERE id=$1 AND registered=true`, id)
gathering := match_making_types.NewGathering()
var gatheringType string
var participants []uint64
var startedTime time.Time
err := row.Scan(
&gathering.OwnerPID,
&gathering.HostPID,
&gathering.MinimumParticipants,
&gathering.MaximumParticipants,
&gathering.ParticipationPolicy,
&gathering.PolicyArgument,
&gathering.Flags,
&gathering.State,
&gathering.Description,
&gatheringType,
pqextended.Array(&participants),
&startedTime,
)
if err != nil {
if err == sql.ErrNoRows {
return gathering, "", nil, types.NewDateTime(0), nex.NewError(nex.ResultCodes.RendezVous.SessionVoid, err.Error())
} else {
return gathering, "", nil, types.NewDateTime(0), nex.NewError(nex.ResultCodes.Core.Unknown, err.Error())
}
}
gathering.ID = types.NewUInt32(id)
startedDateTime := types.NewDateTime(0)
return gathering, gatheringType, participants, startedDateTime.FromTimestamp(startedTime), nil
}

View file

@ -0,0 +1,21 @@
package database
import (
"github.com/PretendoNetwork/nex-go/v2"
"github.com/PretendoNetwork/nex-go/v2/types"
common_globals "github.com/PretendoNetwork/nex-protocols-common-go/v2/globals"
)
// GetDetailedGatheringByID returns a Gathering as an RVType by its gathering ID
func GetDetailedGatheringByID(manager *common_globals.MatchmakingManager, sourcePID uint64, gatheringID uint32) (types.RVType, string, *nex.Error) {
gathering, gatheringType, _, _, nexError := FindGatheringByID(manager, gatheringID)
if nexError != nil {
return nil, "", nexError
}
if gatheringType != "Gathering" {
return nil, "", nex.NewError(nex.ResultCodes.Core.Exception, "change_error")
}
return gathering, gatheringType, nil
}

View file

@ -0,0 +1,121 @@
package database
import (
"database/sql"
"slices"
"github.com/PretendoNetwork/nex-go/v2"
"github.com/PretendoNetwork/nex-go/v2/types"
common_globals "github.com/PretendoNetwork/nex-protocols-common-go/v2/globals"
"github.com/PretendoNetwork/nex-protocols-common-go/v2/match-making/tracking"
match_making "github.com/PretendoNetwork/nex-protocols-go/v2/match-making"
notifications "github.com/PretendoNetwork/nex-protocols-go/v2/notifications"
notifications_types "github.com/PretendoNetwork/nex-protocols-go/v2/notifications/types"
pqextended "github.com/PretendoNetwork/pq-extended"
)
// JoinGathering joins participants from the same connection into a gathering. Returns the new number of participants
func JoinGathering(manager *common_globals.MatchmakingManager, gatheringID uint32, connection *nex.PRUDPConnection, vacantParticipants uint16, joinMessage string) (uint32, *nex.Error) {
// * vacantParticipants represents the total number of participants that are joining (including the main participant)
// * Prevent underflow below if vacantParticipants is set to zero
if vacantParticipants == 0 {
vacantParticipants = 1
}
var ownerPID uint64
var maxParticipants uint32
var flags uint32
var participants []uint64
err := manager.Database.QueryRow(`SELECT owner_pid, max_participants, flags, participants FROM matchmaking.gatherings WHERE id=$1`, gatheringID).Scan(&ownerPID, &maxParticipants, &flags, pqextended.Array(&participants))
if err != nil {
if err == sql.ErrNoRows {
return 0, nex.NewError(nex.ResultCodes.RendezVous.SessionVoid, "change_error")
} else {
return 0, nex.NewError(nex.ResultCodes.Core.Unknown, err.Error())
}
}
if maxParticipants != 0 {
if uint32(len(participants)) + uint32(vacantParticipants) > maxParticipants {
return 0, nex.NewError(nex.ResultCodes.RendezVous.SessionFull, "change_error")
}
}
if slices.Contains(participants, uint64(connection.PID())) {
return 0, nex.NewError(nex.ResultCodes.RendezVous.AlreadyParticipatedGathering, "change_error")
}
var newParticipants []uint64
// * Additional participants are represented by duplicating the main participant PID on the array
for range vacantParticipants {
newParticipants = append(newParticipants, uint64(connection.PID()))
}
var totalParticipants []uint64 = append(newParticipants, participants...)
// * We have already checked that the gathering exists above, so we don't have to check the rows affected on sql.Result
_, err = manager.Database.Exec(`UPDATE matchmaking.gatherings SET participants=$1 WHERE id=$2`, pqextended.Array(totalParticipants), gatheringID)
if err != nil {
if err == sql.ErrNoRows {
return 0, nex.NewError(nex.ResultCodes.RendezVous.SessionVoid, "change_error")
} else {
return 0, nex.NewError(nex.ResultCodes.Core.Unknown, err.Error())
}
}
nexError := tracking.LogJoinGathering(manager.Database, connection.PID(), gatheringID, newParticipants, totalParticipants)
if nexError != nil {
return 0, nexError
}
var participantJoinedTargets []uint64
// * When the VerboseParticipants or VerboseParticipantsEx flags are set, all participant notification events are sent to everyone
if flags & (match_making.GatheringFlags.VerboseParticipants | match_making.GatheringFlags.VerboseParticipantsEx) != 0 {
participantJoinedTargets = common_globals.RemoveDuplicates(totalParticipants)
} else {
// * If the new participant is the same as the owner, then we are creating a new gathering.
// * We don't need to send notification events in that case
if uint64(connection.PID()) == ownerPID {
return uint32(len(totalParticipants)), nil
}
participantJoinedTargets = []uint64{ownerPID}
}
notificationCategory := notifications.NotificationCategories.Participation
notificationSubtype := notifications.NotificationSubTypes.Participation.NewParticipant
oEvent := notifications_types.NewNotificationEvent()
oEvent.PIDSource = connection.PID()
oEvent.Type = types.NewUInt32(notifications.BuildNotificationType(notificationCategory, notificationSubtype))
oEvent.Param1 = types.NewUInt32(gatheringID)
oEvent.Param2 = types.NewUInt32(uint32(connection.PID())) // TODO - This assumes a legacy client. Will not work on the Switch
oEvent.StrParam = types.NewString(joinMessage)
oEvent.Param3 = types.NewUInt32(uint32(len(totalParticipants)))
common_globals.SendNotificationEvent(connection.Endpoint().(*nex.PRUDPEndPoint), oEvent, participantJoinedTargets)
// * This flag also sends a recap of all currently connected players on the gathering to the participant that is connecting
if flags & match_making.GatheringFlags.VerboseParticipantsEx != 0 {
// TODO - Should this actually be deduplicated?
for _, participant := range common_globals.RemoveDuplicates(participants) {
notificationCategory := notifications.NotificationCategories.Participation
notificationSubtype := notifications.NotificationSubTypes.Participation.NewParticipant
oEvent := notifications_types.NewNotificationEvent()
oEvent.PIDSource = connection.PID()
oEvent.Type = types.NewUInt32(notifications.BuildNotificationType(notificationCategory, notificationSubtype))
oEvent.Param1 = types.NewUInt32(gatheringID)
oEvent.Param2 = types.NewUInt32(uint32(participant)) // TODO - This assumes a legacy client. Will not work on the Switch
oEvent.StrParam = types.NewString(joinMessage)
oEvent.Param3 = types.NewUInt32(uint32(len(totalParticipants)))
// * Send the notification to the joining participant
common_globals.SendNotificationEvent(connection.Endpoint().(*nex.PRUDPEndPoint), oEvent, []uint64{uint64(connection.PID())})
}
}
return uint32(len(totalParticipants)), nil
}

View file

@ -0,0 +1,140 @@
package database
import (
"database/sql"
"slices"
"github.com/PretendoNetwork/nex-go/v2"
"github.com/PretendoNetwork/nex-protocols-go/v2/match-making/constants"
"github.com/PretendoNetwork/nex-go/v2/types"
common_globals "github.com/PretendoNetwork/nex-protocols-common-go/v2/globals"
"github.com/PretendoNetwork/nex-protocols-common-go/v2/match-making/tracking"
match_making "github.com/PretendoNetwork/nex-protocols-go/v2/match-making"
notifications "github.com/PretendoNetwork/nex-protocols-go/v2/notifications"
notifications_types "github.com/PretendoNetwork/nex-protocols-go/v2/notifications/types"
pqextended "github.com/PretendoNetwork/pq-extended"
)
// JoinGatheringWithParticipants joins participants into a gathering. Returns the new number of participants
func JoinGatheringWithParticipants(manager *common_globals.MatchmakingManager, gatheringID uint32, connection *nex.PRUDPConnection, additionalParticipants []types.PID, joinMessage string, joinMatchmakeSessionBehavior constants.JoinMatchmakeSessionBehavior) (uint32, *nex.Error) {
var ownerPID uint64
var maxParticipants uint32
var flags uint32
var oldParticipants []uint64
err := manager.Database.QueryRow(`SELECT owner_pid, max_participants, flags, participants FROM matchmaking.gatherings WHERE id=$1`, gatheringID).Scan(&ownerPID, &maxParticipants, &flags, pqextended.Array(&oldParticipants))
if err != nil {
if err == sql.ErrNoRows {
return 0, nex.NewError(nex.ResultCodes.RendezVous.SessionVoid, "change_error")
} else {
return 0, nex.NewError(nex.ResultCodes.Core.Unknown, err.Error())
}
}
if uint32(len(oldParticipants) + 1 + len(additionalParticipants)) > maxParticipants {
return 0, nex.NewError(nex.ResultCodes.RendezVous.SessionFull, "change_error")
}
var newParticipants []uint64
// * If joinMatchmakeSessionBehavior is set to 1, we check if the caller is already joined into the session
if joinMatchmakeSessionBehavior == constants.JoinMatchmakeSessionBehaviorImAlreadyJoined {
if !slices.Contains(oldParticipants, uint64(connection.PID())) {
return 0, nex.NewError(nex.ResultCodes.RendezVous.NotParticipatedGathering, "change_error")
}
} else {
if slices.Contains(oldParticipants, uint64(connection.PID())) {
return 0, nex.NewError(nex.ResultCodes.RendezVous.AlreadyParticipatedGathering, "change_error")
}
// * Only include the caller as a new participant when they aren't joined
newParticipants = []uint64{uint64(connection.PID())}
}
for _, participant := range additionalParticipants {
newParticipants = append(newParticipants, uint64(participant))
}
participants := append(oldParticipants, newParticipants...)
// * We have already checked that the gathering exists above, so we don't have to check the rows affected on sql.Result
_, err = manager.Database.Exec(`UPDATE matchmaking.gatherings SET participants=$1 WHERE id=$2`, pqextended.Array(participants), gatheringID)
if err != nil {
return 0, nex.NewError(nex.ResultCodes.Core.Unknown, err.Error())
}
// NOTE - This will log even if no new participants are added
nexError := tracking.LogJoinGathering(manager.Database, connection.PID(), gatheringID, newParticipants, participants)
if nexError != nil {
return 0, nexError
}
var participantJoinedTargets []uint64
// * When the VerboseParticipants or the VerboseParticipantsEx flags are set, all participant notification events are sent to everyone
if flags & (match_making.GatheringFlags.VerboseParticipants | match_making.GatheringFlags.VerboseParticipantsEx) != 0 {
participantJoinedTargets = common_globals.RemoveDuplicates(participants)
} else {
participantJoinedTargets = []uint64{ownerPID}
}
// * Send the switch SwitchGathering to the new participants first
for _, participant := range common_globals.RemoveDuplicates(newParticipants) {
// * Don't send the SwitchGathering notification to the participant that requested the join
if uint64(connection.PID()) == participant {
continue
}
notificationCategory := notifications.NotificationCategories.SwitchGathering
notificationSubtype := notifications.NotificationSubTypes.SwitchGathering.None
oEvent := notifications_types.NewNotificationEvent()
oEvent.PIDSource = connection.PID()
oEvent.Type = types.NewUInt32(notifications.BuildNotificationType(notificationCategory, notificationSubtype))
oEvent.Param1 = types.NewUInt32(gatheringID)
oEvent.Param2 = types.NewUInt32(uint32(participant)) // TODO - This assumes a legacy client. Will not work on the Switch
// * Send the notification to the participant
common_globals.SendNotificationEvent(connection.Endpoint().(*nex.PRUDPEndPoint), oEvent, []uint64{participant})
}
for _, participant := range newParticipants {
// * If the new participant is the same as the owner, then we are creating a new gathering.
// * We don't need to send the new participant notification event in that case
if flags & (match_making.GatheringFlags.VerboseParticipants | match_making.GatheringFlags.VerboseParticipantsEx) != 0 || uint64(connection.PID()) != ownerPID {
notificationCategory := notifications.NotificationCategories.Participation
notificationSubtype := notifications.NotificationSubTypes.Participation.NewParticipant
oEvent := notifications_types.NewNotificationEvent()
oEvent.PIDSource = connection.PID()
oEvent.Type = types.NewUInt32(notifications.BuildNotificationType(notificationCategory, notificationSubtype))
oEvent.Param1 = types.NewUInt32(gatheringID)
oEvent.Param2 = types.NewUInt32(uint32(participant)) // TODO - This assumes a legacy client. Will not work on the Switch
oEvent.StrParam = types.NewString(joinMessage)
oEvent.Param3 = types.NewUInt32(uint32(len(participants)))
common_globals.SendNotificationEvent(connection.Endpoint().(*nex.PRUDPEndPoint), oEvent, participantJoinedTargets)
}
// * This flag also sends a recap of all currently connected players on the gathering to the participant that is connecting
if flags & match_making.GatheringFlags.VerboseParticipantsEx != 0 {
// TODO - Should this actually be deduplicated?
for _, oldParticipant := range common_globals.RemoveDuplicates(oldParticipants) {
notificationCategory := notifications.NotificationCategories.Participation
notificationSubtype := notifications.NotificationSubTypes.Participation.NewParticipant
oEvent := notifications_types.NewNotificationEvent()
oEvent.PIDSource = connection.PID()
oEvent.Type = types.NewUInt32(notifications.BuildNotificationType(notificationCategory, notificationSubtype))
oEvent.Param1 = types.NewUInt32(gatheringID)
oEvent.Param2 = types.NewUInt32(uint32(oldParticipant)) // TODO - This assumes a legacy client. Will not work on the Switch
oEvent.StrParam = types.NewString(joinMessage)
oEvent.Param3 = types.NewUInt32(uint32(len(participants)))
// * Send the notification to the joining participant
common_globals.SendNotificationEvent(connection.Endpoint().(*nex.PRUDPEndPoint), oEvent, []uint64{participant})
}
}
}
return uint32(len(participants)), nil
}

View file

@ -0,0 +1,75 @@
package database
import (
"github.com/PretendoNetwork/nex-go/v2"
"github.com/PretendoNetwork/nex-go/v2/types"
common_globals "github.com/PretendoNetwork/nex-protocols-common-go/v2/globals"
"github.com/PretendoNetwork/nex-protocols-common-go/v2/match-making/tracking"
match_making_types "github.com/PretendoNetwork/nex-protocols-go/v2/match-making/types"
notifications "github.com/PretendoNetwork/nex-protocols-go/v2/notifications"
notifications_types "github.com/PretendoNetwork/nex-protocols-go/v2/notifications/types"
)
// MigrateGatheringOwnership switches the owner of the gathering with a different one
func MigrateGatheringOwnership(manager *common_globals.MatchmakingManager, connection *nex.PRUDPConnection, gathering match_making_types.Gathering, participants []uint64) (uint64, *nex.Error) {
var nexError *nex.Error
var uniqueParticipants []uint64 = common_globals.RemoveDuplicates(participants)
var newOwner uint64
for _, participant := range uniqueParticipants {
if participant != uint64(gathering.OwnerPID) {
newOwner = participant
break
}
}
// * We couldn't find a new owner, so we unregister the gathering
if newOwner == 0 {
nexError = UnregisterGathering(manager, connection.PID(), uint32(gathering.ID))
if nexError != nil {
return 0, nexError
}
category := notifications.NotificationCategories.GatheringUnregistered
subtype := notifications.NotificationSubTypes.GatheringUnregistered.None
oEvent := notifications_types.NewNotificationEvent()
oEvent.PIDSource = connection.PID().Copy().(types.PID)
oEvent.Type = types.NewUInt32(notifications.BuildNotificationType(category, subtype))
oEvent.Param1 = gathering.ID
common_globals.SendNotificationEvent(connection.Endpoint().(*nex.PRUDPEndPoint), oEvent, uniqueParticipants)
return 0, nil
}
oldOwner := gathering.OwnerPID.Copy().(types.PID)
// * Set the new owner
gathering.OwnerPID = types.NewPID(newOwner)
nexError = UpdateSessionHost(manager, uint32(gathering.ID), gathering.OwnerPID, gathering.HostPID)
if nexError != nil {
return 0, nexError
}
nexError = tracking.LogChangeOwner(manager.Database, connection.PID(), uint32(gathering.ID), oldOwner, gathering.OwnerPID)
if nexError != nil {
return 0, nexError
}
category := notifications.NotificationCategories.OwnershipChanged
subtype := notifications.NotificationSubTypes.OwnershipChanged.None
oEvent := notifications_types.NewNotificationEvent()
oEvent.PIDSource = connection.PID().Copy().(types.PID)
oEvent.Type = types.NewUInt32(notifications.BuildNotificationType(category, subtype))
oEvent.Param1 = gathering.ID
oEvent.Param2 = types.NewUInt32(uint32(newOwner)) // TODO - This assumes a legacy client. Will not work on the Switch
// TODO - StrParam doesn't have this value on some servers
// * https://github.com/kinnay/NintendoClients/issues/101
// * unixTime := time.Now()
// * oEvent.StrParam = strconv.FormatInt(unixTime.UnixMicro(), 10)
common_globals.SendNotificationEvent(connection.Endpoint().(*nex.PRUDPEndPoint), oEvent, uniqueParticipants)
return newOwner, nil
}

View file

@ -0,0 +1,68 @@
package database
import (
"github.com/PretendoNetwork/nex-go/v2"
"github.com/PretendoNetwork/nex-go/v2/types"
common_globals "github.com/PretendoNetwork/nex-protocols-common-go/v2/globals"
"github.com/PretendoNetwork/nex-protocols-common-go/v2/match-making/tracking"
match_making_types "github.com/PretendoNetwork/nex-protocols-go/v2/match-making/types"
)
// RegisterGathering registers a new gathering on the database. No participants are added
func RegisterGathering(manager *common_globals.MatchmakingManager, ownerPID types.PID, hostPID types.PID, gathering *match_making_types.Gathering, gatheringType string) (types.DateTime, *nex.Error) {
startedTime := types.NewDateTime(0).Now()
var gatheringID uint32
err := manager.Database.QueryRow(`INSERT INTO matchmaking.gatherings (
owner_pid,
host_pid,
min_participants,
max_participants,
participation_policy,
policy_argument,
flags,
state,
description,
type,
started_time
) VALUES (
$1,
$2,
$3,
$4,
$5,
$6,
$7,
$8,
$9,
$10,
$11
) RETURNING id`,
ownerPID,
hostPID,
gathering.MinimumParticipants,
gathering.MaximumParticipants,
gathering.ParticipationPolicy,
gathering.PolicyArgument,
gathering.Flags,
gathering.State,
gathering.Description,
gatheringType,
startedTime.Standard(),
).Scan(&gatheringID)
if err != nil {
return types.NewDateTime(0), nex.NewError(nex.ResultCodes.Core.Unknown, err.Error())
}
gathering.ID = types.NewUInt32(gatheringID)
nexError := tracking.LogRegisterGathering(manager.Database, ownerPID, uint32(gathering.ID))
if nexError != nil {
return types.NewDateTime(0), nexError
}
gathering.OwnerPID = ownerPID.Copy().(types.PID)
gathering.HostPID = hostPID.Copy().(types.PID)
return startedTime, nil
}

View file

@ -0,0 +1,24 @@
package database
import (
"database/sql"
"github.com/PretendoNetwork/nex-go/v2"
common_globals "github.com/PretendoNetwork/nex-protocols-common-go/v2/globals"
pqextended "github.com/PretendoNetwork/pq-extended"
)
// RemoveParticipantFromGathering removes a participant from a gathering. Returns the new list of participants
func RemoveParticipantFromGathering(manager *common_globals.MatchmakingManager, gatheringID uint32, participant uint64) ([]uint64, *nex.Error) {
var newParticipants []uint64
err := manager.Database.QueryRow(`UPDATE matchmaking.gatherings SET participants=array_remove(participants, $1) WHERE id=$2 RETURNING participants`, participant, gatheringID).Scan(pqextended.Array(&newParticipants))
if err != nil {
if err == sql.ErrNoRows {
return nil, nex.NewError(nex.ResultCodes.RendezVous.SessionVoid, "change_error")
} else {
return nil, nex.NewError(nex.ResultCodes.Core.Unknown, err.Error())
}
}
return newParticipants, nil
}

View file

@ -0,0 +1,32 @@
package database
import (
"github.com/PretendoNetwork/nex-go/v2"
"github.com/PretendoNetwork/nex-go/v2/types"
common_globals "github.com/PretendoNetwork/nex-protocols-common-go/v2/globals"
"github.com/PretendoNetwork/nex-protocols-common-go/v2/match-making/tracking"
)
// UnregisterGathering unregisters a given gathering on a database
func UnregisterGathering(manager *common_globals.MatchmakingManager, sourcePID types.PID, id uint32) *nex.Error {
result, err := manager.Database.Exec(`UPDATE matchmaking.gatherings SET registered=false WHERE id=$1`, id)
if err != nil {
return nex.NewError(nex.ResultCodes.Core.Unknown, err.Error())
}
rowsAffected, err := result.RowsAffected()
if err != nil {
return nex.NewError(nex.ResultCodes.Core.Unknown, err.Error())
}
if rowsAffected == 0 {
return nex.NewError(nex.ResultCodes.RendezVous.SessionVoid, "change_error")
}
nexError := tracking.LogUnregisterGathering(manager.Database, sourcePID, id)
if nexError != nil {
return nexError
}
return nil
}

View file

@ -0,0 +1,26 @@
package database
import (
"github.com/PretendoNetwork/nex-go/v2"
"github.com/PretendoNetwork/nex-go/v2/types"
common_globals "github.com/PretendoNetwork/nex-protocols-common-go/v2/globals"
)
// UpdateSessionHost updates the owner and host PID of the session
func UpdateSessionHost(manager *common_globals.MatchmakingManager, gatheringID uint32, ownerPID types.PID, hostPID types.PID) *nex.Error {
result, err := manager.Database.Exec(`UPDATE matchmaking.gatherings SET owner_pid=$1, host_pid=$2 WHERE id=$3`, ownerPID, hostPID, gatheringID)
if err != nil {
return nex.NewError(nex.ResultCodes.Core.Unknown, err.Error())
}
rowsAffected, err := result.RowsAffected()
if err != nil {
return nex.NewError(nex.ResultCodes.Core.Unknown, err.Error())
}
if rowsAffected == 0 {
return nex.NewError(nex.ResultCodes.RendezVous.SessionVoid, "change_error")
}
return nil
}

View file

@ -0,0 +1,51 @@
package matchmaking
import (
"github.com/PretendoNetwork/nex-go/v2"
"github.com/PretendoNetwork/nex-go/v2/types"
common_globals "github.com/PretendoNetwork/nex-protocols-common-go/v2/globals"
match_making "github.com/PretendoNetwork/nex-protocols-go/v2/match-making"
match_making_types "github.com/PretendoNetwork/nex-protocols-go/v2/match-making/types"
)
func (commonProtocol *CommonProtocol) findBySingleID(err error, packet nex.PacketInterface, callID uint32, id types.UInt32) (*nex.RMCMessage, *nex.Error) {
if err != nil {
common_globals.Logger.Error(err.Error())
return nil, nex.NewError(nex.ResultCodes.Core.InvalidArgument, "change_error")
}
connection := packet.Sender().(*nex.PRUDPConnection)
endpoint := connection.Endpoint().(*nex.PRUDPEndPoint)
commonProtocol.manager.Mutex.RLock()
gathering, _, nexError := commonProtocol.manager.GetDetailedGatheringByID(commonProtocol.manager, uint64(connection.PID()), uint32(id))
if nexError != nil {
commonProtocol.manager.Mutex.RUnlock()
return nil, nexError
}
commonProtocol.manager.Mutex.RUnlock()
bResult := types.NewBool(true)
pGathering := match_making_types.NewGatheringHolder()
pGathering.Object = gathering.Copy().(match_making_types.GatheringInterface)
rmcResponseStream := nex.NewByteStreamOut(endpoint.LibraryVersions(), endpoint.ByteStreamSettings())
bResult.WriteTo(rmcResponseStream)
pGathering.WriteTo(rmcResponseStream)
rmcResponseBody := rmcResponseStream.Bytes()
rmcResponse := nex.NewRMCSuccess(endpoint, rmcResponseBody)
rmcResponse.ProtocolID = match_making.ProtocolID
rmcResponse.MethodID = match_making.MethodFindBySingleID
rmcResponse.CallID = callID
if commonProtocol.OnAfterFindBySingleID != nil {
go commonProtocol.OnAfterFindBySingleID(packet, id)
}
return rmcResponse, nil
}

View file

@ -0,0 +1,61 @@
package matchmaking
import (
"slices"
"github.com/PretendoNetwork/nex-go/v2"
"github.com/PretendoNetwork/nex-go/v2/types"
common_globals "github.com/PretendoNetwork/nex-protocols-common-go/v2/globals"
"github.com/PretendoNetwork/nex-protocols-common-go/v2/match-making/database"
match_making "github.com/PretendoNetwork/nex-protocols-go/v2/match-making"
)
func (commonProtocol *CommonProtocol) getSessionURLs(err error, packet nex.PacketInterface, callID uint32, gid types.UInt32) (*nex.RMCMessage, *nex.Error) {
if err != nil {
common_globals.Logger.Error(err.Error())
return nil, nex.NewError(nex.ResultCodes.Core.InvalidArgument, "change_error")
}
commonProtocol.manager.Mutex.RLock()
gathering, _, participants, _, nexError := database.FindGatheringByID(commonProtocol.manager, uint32(gid))
if nexError != nil {
commonProtocol.manager.Mutex.RUnlock()
return nil, nexError
}
connection := packet.Sender().(*nex.PRUDPConnection)
endpoint := connection.Endpoint().(*nex.PRUDPEndPoint)
if !slices.Contains(participants, uint64(connection.PID())) {
commonProtocol.manager.Mutex.RUnlock()
return nil, nex.NewError(nex.ResultCodes.RendezVous.PermissionDenied, "change_error")
}
host := endpoint.FindConnectionByPID(uint64(gathering.HostPID))
commonProtocol.manager.Mutex.RUnlock()
rmcResponseStream := nex.NewByteStreamOut(endpoint.LibraryVersions(), endpoint.ByteStreamSettings())
// * If no host was found, return an empty list of station URLs
if host == nil {
common_globals.Logger.Error("Host client not found")
stationURLs := types.NewList[types.StationURL]()
stationURLs.WriteTo(rmcResponseStream)
} else {
host.StationURLs.WriteTo(rmcResponseStream)
}
rmcResponseBody := rmcResponseStream.Bytes()
rmcResponse := nex.NewRMCSuccess(endpoint, rmcResponseBody)
rmcResponse.ProtocolID = match_making.ProtocolID
rmcResponse.MethodID = match_making.MethodGetSessionURLs
rmcResponse.CallID = callID
if commonProtocol.OnAfterGetSessionURLs != nil {
go commonProtocol.OnAfterGetSessionURLs(packet, gid)
}
return rmcResponse, nil
}

174
match-making/protocol.go Normal file
View file

@ -0,0 +1,174 @@
package matchmaking
import (
"github.com/PretendoNetwork/nex-go/v2"
"github.com/PretendoNetwork/nex-go/v2/types"
common_globals "github.com/PretendoNetwork/nex-protocols-common-go/v2/globals"
"github.com/PretendoNetwork/nex-protocols-common-go/v2/match-making/database"
_ "github.com/PretendoNetwork/nex-protocols-go/v2"
match_making "github.com/PretendoNetwork/nex-protocols-go/v2/match-making"
)
type CommonProtocol struct {
endpoint *nex.PRUDPEndPoint
protocol match_making.Interface
manager *common_globals.MatchmakingManager
OnAfterUnregisterGathering func(packet nex.PacketInterface, idGathering types.UInt32)
OnAfterFindBySingleID func(packet nex.PacketInterface, id types.UInt32)
OnAfterUpdateSessionURL func(packet nex.PacketInterface, idGathering types.UInt32, strURL types.String)
OnAfterUpdateSessionHostV1 func(packet nex.PacketInterface, gid types.UInt32)
OnAfterGetSessionURLs func(packet nex.PacketInterface, gid types.UInt32)
OnAfterUpdateSessionHost func(packet nex.PacketInterface, gid types.UInt32, isMigrateOwner types.Bool)
}
// SetManager defines the matchmaking manager to be used by the common protocol
func (commonProtocol *CommonProtocol) SetManager(manager *common_globals.MatchmakingManager) {
var err error
commonProtocol.manager = manager
manager.GetDetailedGatheringByID = database.GetDetailedGatheringByID
_, err = manager.Database.Exec(`CREATE SCHEMA IF NOT EXISTS matchmaking`)
if err != nil {
common_globals.Logger.Error(err.Error())
return
}
_, err = manager.Database.Exec(`CREATE TABLE IF NOT EXISTS matchmaking.gatherings (
id bigserial PRIMARY KEY,
owner_pid numeric(10),
host_pid numeric(10),
min_participants integer,
max_participants integer,
participation_policy bigint,
policy_argument bigint,
flags bigint,
state bigint,
description text,
registered boolean NOT NULL DEFAULT true,
type text NOT NULL DEFAULT '',
started_time timestamp,
participants numeric(10)[] NOT NULL DEFAULT array[]::numeric(10)[]
)`)
if err != nil {
common_globals.Logger.Error(err.Error())
return
}
_, err = manager.Database.Exec(`CREATE SCHEMA IF NOT EXISTS tracking`)
if err != nil {
common_globals.Logger.Error(err.Error())
return
}
_, err = manager.Database.Exec(`CREATE TABLE IF NOT EXISTS tracking.register_gathering (
id bigserial PRIMARY KEY,
date timestamp,
source_pid numeric(10),
gathering_id bigint
)`)
if err != nil {
common_globals.Logger.Error(err.Error())
return
}
_, err = manager.Database.Exec(`CREATE TABLE IF NOT EXISTS tracking.join_gathering (
id bigserial PRIMARY KEY,
date timestamp,
source_pid numeric(10),
gathering_id bigint,
new_participants numeric(10)[] NOT NULL DEFAULT array[]::numeric(10)[],
total_participants numeric(10)[] NOT NULL DEFAULT array[]::numeric(10)[]
)`)
if err != nil {
common_globals.Logger.Error(err.Error())
return
}
_, err = manager.Database.Exec(`CREATE TABLE IF NOT EXISTS tracking.leave_gathering (
id bigserial PRIMARY KEY,
date timestamp,
source_pid numeric(10),
gathering_id bigint,
total_participants numeric(10)[] NOT NULL DEFAULT array[]::numeric(10)[]
)`)
if err != nil {
common_globals.Logger.Error(err.Error())
return
}
_, err = manager.Database.Exec(`CREATE TABLE IF NOT EXISTS tracking.disconnect_gathering (
id bigserial PRIMARY KEY,
date timestamp,
source_pid numeric(10),
gathering_id bigint,
total_participants numeric(10)[] NOT NULL DEFAULT array[]::numeric(10)[]
)`)
if err != nil {
common_globals.Logger.Error(err.Error())
return
}
_, err = manager.Database.Exec(`CREATE TABLE IF NOT EXISTS tracking.unregister_gathering (
id bigserial PRIMARY KEY,
date timestamp,
source_pid numeric(10),
gathering_id bigint
)`)
if err != nil {
common_globals.Logger.Error(err.Error())
return
}
_, err = manager.Database.Exec(`CREATE TABLE IF NOT EXISTS tracking.change_host (
id bigserial PRIMARY KEY,
date timestamp,
source_pid numeric(10),
gathering_id bigint,
old_host_pid numeric(10),
new_host_pid numeric(10)
)`)
if err != nil {
common_globals.Logger.Error(err.Error())
return
}
_, err = manager.Database.Exec(`CREATE TABLE IF NOT EXISTS tracking.change_owner (
id bigserial PRIMARY KEY,
date timestamp,
source_pid numeric(10),
gathering_id bigint,
old_owner_pid numeric(10),
new_owner_pid numeric(10)
)`)
if err != nil {
common_globals.Logger.Error(err.Error())
return
}
}
// NewCommonProtocol returns a new CommonProtocol
func NewCommonProtocol(protocol match_making.Interface) *CommonProtocol {
endpoint := protocol.Endpoint().(*nex.PRUDPEndPoint)
commonProtocol := &CommonProtocol{
endpoint: endpoint,
protocol: protocol,
}
protocol.SetHandlerUnregisterGathering(commonProtocol.unregisterGathering)
protocol.SetHandlerFindBySingleID(commonProtocol.findBySingleID)
protocol.SetHandlerUpdateSessionURL(commonProtocol.updateSessionURL)
protocol.SetHandlerUpdateSessionHostV1(commonProtocol.updateSessionHostV1)
protocol.SetHandlerGetSessionURLs(commonProtocol.getSessionURLs)
protocol.SetHandlerUpdateSessionHost(commonProtocol.updateSessionHost)
endpoint.OnConnectionEnded(func(connection *nex.PRUDPConnection) {
commonProtocol.manager.Mutex.Lock()
database.DisconnectParticipant(commonProtocol.manager, connection)
commonProtocol.manager.Mutex.Unlock()
})
return commonProtocol
}

View file

@ -0,0 +1,33 @@
package tracking
import (
"database/sql"
"time"
"github.com/PretendoNetwork/nex-go/v2"
"github.com/PretendoNetwork/nex-go/v2/types"
)
// LogChangeHost logs a host change event on the given database
func LogChangeHost(db *sql.DB, sourcePID types.PID, gatheringID uint32, oldHostPID types.PID, newHostPID types.PID) *nex.Error {
eventTime := time.Now().UTC()
_, err := db.Exec(`INSERT INTO tracking.change_host (
date,
source_pid,
gathering_id,
old_host_pid,
new_host_pid
) VALUES (
$1,
$2,
$3,
$4,
$5
)`, eventTime, sourcePID, gatheringID, oldHostPID, newHostPID)
if err != nil {
return nex.NewError(nex.ResultCodes.Core.Unknown, err.Error())
}
return nil
}

View file

@ -0,0 +1,33 @@
package tracking
import (
"database/sql"
"time"
"github.com/PretendoNetwork/nex-go/v2"
"github.com/PretendoNetwork/nex-go/v2/types"
)
// LogChangeOwner logs an owner change event on the given database
func LogChangeOwner(db *sql.DB, sourcePID types.PID, gatheringID uint32, oldOwnerPID types.PID, newOwnerPID types.PID) *nex.Error {
eventTime := time.Now().UTC()
_, err := db.Exec(`INSERT INTO tracking.change_owner (
date,
source_pid,
gathering_id,
old_owner_pid,
new_owner_pid
) VALUES (
$1,
$2,
$3,
$4,
$5
)`, eventTime, sourcePID, gatheringID, oldOwnerPID, newOwnerPID)
if err != nil {
return nex.NewError(nex.ResultCodes.Core.Unknown, err.Error())
}
return nil
}

View file

@ -0,0 +1,32 @@
package tracking
import (
"database/sql"
"time"
"github.com/PretendoNetwork/nex-go/v2"
"github.com/PretendoNetwork/nex-go/v2/types"
pqextended "github.com/PretendoNetwork/pq-extended"
)
// LogDisconnectGathering logs a gathering disconnect event on the given database
func LogDisconnectGathering(db *sql.DB, pid types.PID, gatheringID uint32, totalParticipants []uint64) *nex.Error {
eventTime := time.Now().UTC()
_, err := db.Exec(`INSERT INTO tracking.disconnect_gathering (
date,
source_pid,
gathering_id,
total_participants
) VALUES (
$1,
$2,
$3,
$4
)`, eventTime, pid, gatheringID, pqextended.Array(totalParticipants))
if err != nil {
return nex.NewError(nex.ResultCodes.Core.Unknown, err.Error())
}
return nil
}

View file

@ -0,0 +1,34 @@
package tracking
import (
"database/sql"
"time"
"github.com/PretendoNetwork/nex-go/v2"
"github.com/PretendoNetwork/nex-go/v2/types"
pqextended "github.com/PretendoNetwork/pq-extended"
)
// LogJoinGathering logs a gathering join event on the given database
func LogJoinGathering(db *sql.DB, sourcePID types.PID, gatheringID uint32, newParticipants []uint64, totalParticipants []uint64) *nex.Error {
eventTime := time.Now().UTC()
_, err := db.Exec(`INSERT INTO tracking.join_gathering (
date,
source_pid,
gathering_id,
new_participants,
total_participants
) VALUES (
$1,
$2,
$3,
$4,
$5
)`, eventTime, sourcePID, gatheringID, pqextended.Array(newParticipants), pqextended.Array(totalParticipants))
if err != nil {
return nex.NewError(nex.ResultCodes.Core.Unknown, err.Error())
}
return nil
}

View file

@ -0,0 +1,32 @@
package tracking
import (
"database/sql"
"time"
"github.com/PretendoNetwork/nex-go/v2"
"github.com/PretendoNetwork/nex-go/v2/types"
pqextended "github.com/PretendoNetwork/pq-extended"
)
// LogLeaveGathering logs a gathering leave event on the given database
func LogLeaveGathering(db *sql.DB, pid types.PID, gatheringID uint32, totalParticipants []uint64) *nex.Error {
eventTime := time.Now().UTC()
_, err := db.Exec(`INSERT INTO tracking.leave_gathering (
date,
source_pid,
gathering_id,
total_participants
) VALUES (
$1,
$2,
$3,
$4
)`, eventTime, pid, gatheringID, pqextended.Array(totalParticipants))
if err != nil {
return nex.NewError(nex.ResultCodes.Core.Unknown, err.Error())
}
return nil
}

View file

@ -0,0 +1,29 @@
package tracking
import (
"database/sql"
"time"
"github.com/PretendoNetwork/nex-go/v2"
"github.com/PretendoNetwork/nex-go/v2/types"
)
// LogRegisterGathering logs a gathering registration event on the given database
func LogRegisterGathering(db *sql.DB, sourcePID types.PID, gatheringID uint32) *nex.Error {
eventTime := time.Now().UTC()
_, err := db.Exec(`INSERT INTO tracking.register_gathering (
date,
source_pid,
gathering_id
) VALUES (
$1,
$2,
$3
)`, eventTime, sourcePID, gatheringID)
if err != nil {
return nex.NewError(nex.ResultCodes.Core.Unknown, err.Error())
}
return nil
}

View file

@ -0,0 +1,29 @@
package tracking
import (
"database/sql"
"time"
"github.com/PretendoNetwork/nex-go/v2"
"github.com/PretendoNetwork/nex-go/v2/types"
)
// LogUnregisterGathering logs a gathering registration event on the given database
func LogUnregisterGathering(db *sql.DB, sourcePID types.PID, gatheringID uint32) *nex.Error {
eventTime := time.Now().UTC()
_, err := db.Exec(`INSERT INTO tracking.unregister_gathering (
date,
source_pid,
gathering_id
) VALUES (
$1,
$2,
$3
)`, eventTime, sourcePID, gatheringID)
if err != nil {
return nex.NewError(nex.ResultCodes.Core.Unknown, err.Error())
}
return nil
}

View file

@ -0,0 +1,70 @@
package matchmaking
import (
"github.com/PretendoNetwork/nex-go/v2"
"github.com/PretendoNetwork/nex-go/v2/types"
common_globals "github.com/PretendoNetwork/nex-protocols-common-go/v2/globals"
"github.com/PretendoNetwork/nex-protocols-common-go/v2/match-making/database"
match_making "github.com/PretendoNetwork/nex-protocols-go/v2/match-making"
notifications "github.com/PretendoNetwork/nex-protocols-go/v2/notifications"
notifications_types "github.com/PretendoNetwork/nex-protocols-go/v2/notifications/types"
)
func (commonProtocol *CommonProtocol) unregisterGathering(err error, packet nex.PacketInterface, callID uint32, idGathering types.UInt32) (*nex.RMCMessage, *nex.Error) {
if err != nil {
common_globals.Logger.Error(err.Error())
return nil, nex.NewError(nex.ResultCodes.Core.InvalidArgument, "change_error")
}
commonProtocol.manager.Mutex.Lock()
gathering, _, participants, _, nexError := database.FindGatheringByID(commonProtocol.manager, uint32(idGathering))
if nexError != nil {
commonProtocol.manager.Mutex.Unlock()
return nil, nexError
}
connection := packet.Sender().(*nex.PRUDPConnection)
endpoint := connection.Endpoint().(*nex.PRUDPEndPoint)
if !gathering.OwnerPID.Equals(connection.PID()) {
commonProtocol.manager.Mutex.Unlock()
return nil, nex.NewError(nex.ResultCodes.RendezVous.PermissionDenied, "change_error")
}
nexError = database.UnregisterGathering(commonProtocol.manager, connection.PID(), uint32(idGathering))
if nexError != nil {
commonProtocol.manager.Mutex.Unlock()
return nil, nexError
}
category := notifications.NotificationCategories.GatheringUnregistered
subtype := notifications.NotificationSubTypes.GatheringUnregistered.None
oEvent := notifications_types.NewNotificationEvent()
oEvent.PIDSource = connection.PID().Copy().(types.PID)
oEvent.Type = types.NewUInt32(notifications.BuildNotificationType(category, subtype))
oEvent.Param1 = idGathering
common_globals.SendNotificationEvent(endpoint, oEvent, common_globals.RemoveDuplicates(participants))
commonProtocol.manager.Mutex.Unlock()
retval := types.NewBool(true)
rmcResponseStream := nex.NewByteStreamOut(endpoint.LibraryVersions(), endpoint.ByteStreamSettings())
retval.WriteTo(rmcResponseStream)
rmcResponseBody := rmcResponseStream.Bytes()
rmcResponse := nex.NewRMCSuccess(endpoint, rmcResponseBody)
rmcResponse.ProtocolID = match_making.ProtocolID
rmcResponse.MethodID = match_making.MethodUnregisterGathering
rmcResponse.CallID = callID
if commonProtocol.OnAfterUnregisterGathering != nil {
go commonProtocol.OnAfterUnregisterGathering(packet, idGathering)
}
return rmcResponse, nil
}

View file

@ -0,0 +1,102 @@
package matchmaking
import (
"slices"
"github.com/PretendoNetwork/nex-go/v2"
"github.com/PretendoNetwork/nex-go/v2/types"
common_globals "github.com/PretendoNetwork/nex-protocols-common-go/v2/globals"
"github.com/PretendoNetwork/nex-protocols-common-go/v2/match-making/database"
"github.com/PretendoNetwork/nex-protocols-common-go/v2/match-making/tracking"
match_making "github.com/PretendoNetwork/nex-protocols-go/v2/match-making"
notifications "github.com/PretendoNetwork/nex-protocols-go/v2/notifications"
notifications_types "github.com/PretendoNetwork/nex-protocols-go/v2/notifications/types"
)
func (commonProtocol *CommonProtocol) updateSessionHost(err error, packet nex.PacketInterface, callID uint32, gid types.UInt32, isMigrateOwner types.Bool) (*nex.RMCMessage, *nex.Error) {
if err != nil {
common_globals.Logger.Error(err.Error())
return nil, nex.NewError(nex.ResultCodes.Core.InvalidArgument, "change_error")
}
commonProtocol.manager.Mutex.Lock()
gathering, _, participants, _, nexError := database.FindGatheringByID(commonProtocol.manager, uint32(gid))
if nexError != nil {
commonProtocol.manager.Mutex.Unlock()
return nil, nexError
}
connection := packet.Sender().(*nex.PRUDPConnection)
endpoint := connection.Endpoint().(*nex.PRUDPEndPoint)
if !slices.Contains(participants, uint64(connection.PID())) {
commonProtocol.manager.Mutex.Unlock()
return nil, nex.NewError(nex.ResultCodes.RendezVous.PermissionDenied, "change_error")
}
if !isMigrateOwner {
nexError = database.UpdateSessionHost(commonProtocol.manager, uint32(gid), gathering.OwnerPID, connection.PID())
if nexError != nil {
commonProtocol.manager.Mutex.Unlock()
return nil, nexError
}
nexError = tracking.LogChangeHost(commonProtocol.manager.Database, connection.PID(), uint32(gid), gathering.HostPID, connection.PID())
if nexError != nil {
commonProtocol.manager.Mutex.Unlock()
return nil, nexError
}
} else {
if uint32(gathering.Flags) & match_making.GatheringFlags.ParticipantsChangeOwner == 0 {
commonProtocol.manager.Mutex.Unlock()
return nil, nex.NewError(nex.ResultCodes.RendezVous.InvalidOperation, "change_error")
}
nexError = database.UpdateSessionHost(commonProtocol.manager, uint32(gid), connection.PID(), connection.PID())
if nexError != nil {
commonProtocol.manager.Mutex.Unlock()
return nil, nexError
}
nexError = tracking.LogChangeHost(commonProtocol.manager.Database, connection.PID(), uint32(gid), gathering.HostPID, connection.PID())
if nexError != nil {
commonProtocol.manager.Mutex.Unlock()
return nil, nexError
}
nexError = tracking.LogChangeOwner(commonProtocol.manager.Database, connection.PID(), uint32(gid), gathering.OwnerPID, connection.PID())
if nexError != nil {
commonProtocol.manager.Mutex.Unlock()
return nil, nexError
}
category := notifications.NotificationCategories.OwnershipChanged
subtype := notifications.NotificationSubTypes.OwnershipChanged.None
oEvent := notifications_types.NewNotificationEvent()
oEvent.PIDSource = connection.PID()
oEvent.Type = types.NewUInt32(notifications.BuildNotificationType(category, subtype))
oEvent.Param1 = gid
oEvent.Param2 = types.NewUInt32(uint32(connection.PID())) // TODO - This assumes a legacy client. Will not work on the Switch
// TODO - StrParam doesn't have this value on some servers
// * https://github.com/kinnay/NintendoClients/issues/101
// * unixTime := time.Now()
// * oEvent.StrParam = strconv.FormatInt(unixTime.UnixMicro(), 10)
common_globals.SendNotificationEvent(endpoint, oEvent, common_globals.RemoveDuplicates(participants))
}
commonProtocol.manager.Mutex.Unlock()
rmcResponse := nex.NewRMCSuccess(endpoint, nil)
rmcResponse.ProtocolID = match_making.ProtocolID
rmcResponse.MethodID = match_making.MethodUpdateSessionHost
rmcResponse.CallID = callID
if commonProtocol.OnAfterUpdateSessionHost != nil {
go commonProtocol.OnAfterUpdateSessionHost(packet, gid, isMigrateOwner)
}
return rmcResponse, nil
}

View file

@ -0,0 +1,98 @@
package matchmaking
import (
"slices"
"github.com/PretendoNetwork/nex-go/v2"
"github.com/PretendoNetwork/nex-go/v2/types"
common_globals "github.com/PretendoNetwork/nex-protocols-common-go/v2/globals"
"github.com/PretendoNetwork/nex-protocols-common-go/v2/match-making/database"
"github.com/PretendoNetwork/nex-protocols-common-go/v2/match-making/tracking"
match_making "github.com/PretendoNetwork/nex-protocols-go/v2/match-making"
notifications "github.com/PretendoNetwork/nex-protocols-go/v2/notifications"
notifications_types "github.com/PretendoNetwork/nex-protocols-go/v2/notifications/types"
)
func (commonProtocol *CommonProtocol) updateSessionHostV1(err error, packet nex.PacketInterface, callID uint32, gid types.UInt32) (*nex.RMCMessage, *nex.Error) {
if err != nil {
common_globals.Logger.Error(err.Error())
return nil, nex.NewError(nex.ResultCodes.Core.InvalidArgument, "change_error")
}
commonProtocol.manager.Mutex.Lock()
gathering, _, participants, _, nexError := database.FindGatheringByID(commonProtocol.manager, uint32(gid))
if nexError != nil {
commonProtocol.manager.Mutex.Unlock()
return nil, nexError
}
connection := packet.Sender().(*nex.PRUDPConnection)
endpoint := connection.Endpoint().(*nex.PRUDPEndPoint)
if !slices.Contains(participants, uint64(connection.PID())) {
commonProtocol.manager.Mutex.Unlock()
return nil, nex.NewError(nex.ResultCodes.RendezVous.PermissionDenied, "change_error")
}
if uint32(gathering.Flags) & match_making.GatheringFlags.ParticipantsChangeOwner == 0 {
nexError = database.UpdateSessionHost(commonProtocol.manager, uint32(gid), gathering.OwnerPID, connection.PID())
if nexError != nil {
commonProtocol.manager.Mutex.Unlock()
return nil, nexError
}
nexError = tracking.LogChangeHost(commonProtocol.manager.Database, connection.PID(), uint32(gid), gathering.HostPID, connection.PID())
if nexError != nil {
commonProtocol.manager.Mutex.Unlock()
return nil, nexError
}
} else {
nexError = database.UpdateSessionHost(commonProtocol.manager, uint32(gid), connection.PID(), connection.PID())
if nexError != nil {
commonProtocol.manager.Mutex.Unlock()
return nil, nexError
}
nexError = tracking.LogChangeHost(commonProtocol.manager.Database, connection.PID(), uint32(gid), gathering.HostPID, connection.PID())
if nexError != nil {
commonProtocol.manager.Mutex.Unlock()
return nil, nexError
}
nexError = tracking.LogChangeOwner(commonProtocol.manager.Database, connection.PID(), uint32(gid), gathering.OwnerPID, connection.PID())
if nexError != nil {
commonProtocol.manager.Mutex.Unlock()
return nil, nexError
}
category := notifications.NotificationCategories.OwnershipChanged
subtype := notifications.NotificationSubTypes.OwnershipChanged.None
oEvent := notifications_types.NewNotificationEvent()
oEvent.PIDSource = connection.PID()
oEvent.Type = types.NewUInt32(notifications.BuildNotificationType(category, subtype))
oEvent.Param1 = gid
oEvent.Param2 = types.NewUInt32(uint32(connection.PID())) // TODO - This assumes a legacy client. Will not work on the Switch
// TODO - StrParam doesn't have this value on some servers
// * https://github.com/kinnay/NintendoClients/issues/101
// * unixTime := time.Now()
// * oEvent.StrParam = strconv.FormatInt(unixTime.UnixMicro(), 10)
common_globals.SendNotificationEvent(endpoint, oEvent, common_globals.RemoveDuplicates(participants))
}
commonProtocol.manager.Mutex.Unlock()
rmcResponse := nex.NewRMCSuccess(endpoint, nil)
rmcResponse.ProtocolID = match_making.ProtocolID
rmcResponse.MethodID = match_making.MethodUpdateSessionHostV1
rmcResponse.CallID = callID
if commonProtocol.OnAfterUpdateSessionHostV1 != nil {
go commonProtocol.OnAfterUpdateSessionHostV1(packet, gid)
}
return rmcResponse, nil
}

View file

@ -0,0 +1,70 @@
package matchmaking
import (
"slices"
"github.com/PretendoNetwork/nex-go/v2"
"github.com/PretendoNetwork/nex-go/v2/types"
common_globals "github.com/PretendoNetwork/nex-protocols-common-go/v2/globals"
"github.com/PretendoNetwork/nex-protocols-common-go/v2/match-making/database"
"github.com/PretendoNetwork/nex-protocols-common-go/v2/match-making/tracking"
match_making "github.com/PretendoNetwork/nex-protocols-go/v2/match-making"
)
func (commonProtocol *CommonProtocol) updateSessionURL(err error, packet nex.PacketInterface, callID uint32, idGathering types.UInt32, strURL types.String) (*nex.RMCMessage, *nex.Error) {
if err != nil {
common_globals.Logger.Error(err.Error())
return nil, nex.NewError(nex.ResultCodes.Core.InvalidArgument, "change_error")
}
commonProtocol.manager.Mutex.Lock()
gathering, _, participants, _, nexError := database.FindGatheringByID(commonProtocol.manager, uint32(idGathering))
if nexError != nil {
commonProtocol.manager.Mutex.Unlock()
return nil, nexError
}
connection := packet.Sender().(*nex.PRUDPConnection)
endpoint := connection.Endpoint().(*nex.PRUDPEndPoint)
if !slices.Contains(participants, uint64(connection.PID())) {
commonProtocol.manager.Mutex.Unlock()
return nil, nex.NewError(nex.ResultCodes.RendezVous.PermissionDenied, "change_error")
}
// TODO - Mario Kart 7 seems to set an empty strURL. What does that do if it's actually set?
// * Only update the host
nexError = database.UpdateSessionHost(commonProtocol.manager, uint32(idGathering), gathering.OwnerPID, connection.PID())
if nexError != nil {
commonProtocol.manager.Mutex.Unlock()
return nil, nexError
}
nexError = tracking.LogChangeHost(commonProtocol.manager.Database, connection.PID(), uint32(idGathering), gathering.HostPID, connection.PID())
if nexError != nil {
commonProtocol.manager.Mutex.Unlock()
return nil, nexError
}
commonProtocol.manager.Mutex.Unlock()
retval := types.NewBool(true)
rmcResponseStream := nex.NewByteStreamOut(endpoint.LibraryVersions(), endpoint.ByteStreamSettings())
retval.WriteTo(rmcResponseStream)
rmcResponseBody := rmcResponseStream.Bytes()
rmcResponse := nex.NewRMCSuccess(endpoint, rmcResponseBody)
rmcResponse.ProtocolID = match_making.ProtocolID
rmcResponse.MethodID = match_making.MethodGetSessionURLs
rmcResponse.CallID = callID
if commonProtocol.OnAfterUpdateSessionURL != nil {
go commonProtocol.OnAfterUpdateSessionURL(packet, idGathering, strURL)
}
return rmcResponse, nil
}

View file

@ -0,0 +1,99 @@
package matchmake_extension
import (
"github.com/PretendoNetwork/nex-go/v2"
"github.com/PretendoNetwork/nex-go/v2/types"
common_globals "github.com/PretendoNetwork/nex-protocols-common-go/v2/globals"
match_making_types "github.com/PretendoNetwork/nex-protocols-go/v2/match-making/types"
matchmake_extension "github.com/PretendoNetwork/nex-protocols-go/v2/matchmake-extension"
database "github.com/PretendoNetwork/nex-protocols-common-go/v2/matchmake-extension/database"
)
func (commonProtocol *CommonProtocol) autoMatchmakePostpone(err error, packet nex.PacketInterface, callID uint32, anyGathering match_making_types.GatheringHolder, message types.String) (*nex.RMCMessage, *nex.Error) {
if commonProtocol.CleanupSearchMatchmakeSession == nil {
common_globals.Logger.Warning("MatchmakeExtension::AutoMatchmake_Postpone missing CleanupSearchMatchmakeSession!")
return nil, nex.NewError(nex.ResultCodes.Core.NotImplemented, "change_error")
}
if err != nil {
common_globals.Logger.Error(err.Error())
return nil, nex.NewError(nex.ResultCodes.Core.InvalidArgument, "change_error")
}
if len(message) > 256 {
return nil, nex.NewError(nex.ResultCodes.Core.InvalidArgument, "change_error")
}
connection := packet.Sender().(*nex.PRUDPConnection)
endpoint := connection.Endpoint().(*nex.PRUDPEndPoint)
commonProtocol.manager.Mutex.Lock()
// * A client may disconnect from a session without leaving reliably,
// * so let's make sure the client is removed from the session
database.EndMatchmakeSessionsParticipation(commonProtocol.manager, connection)
var matchmakeSession match_making_types.MatchmakeSession
if anyGathering.Object.ObjectID().Equals(types.NewString("MatchmakeSession")) {
matchmakeSession = anyGathering.Object.(match_making_types.MatchmakeSession)
} else {
common_globals.Logger.Critical("Non-MatchmakeSession DataType?!")
commonProtocol.manager.Mutex.Unlock()
return nil, nex.NewError(nex.ResultCodes.Core.InvalidArgument, "change_error")
}
if !common_globals.CheckValidMatchmakeSession(matchmakeSession) {
commonProtocol.manager.Mutex.Unlock()
return nil, nex.NewError(nex.ResultCodes.Core.InvalidArgument, "change_error")
}
searchMatchmakeSession := matchmakeSession.Copy().(match_making_types.MatchmakeSession)
commonProtocol.CleanupSearchMatchmakeSession(&searchMatchmakeSession)
resultSession, nexError := database.FindMatchmakeSession(commonProtocol.manager, connection, searchMatchmakeSession)
if nexError != nil {
commonProtocol.manager.Mutex.Unlock()
return nil, nexError
}
if resultSession == nil {
newMatchmakeSession := searchMatchmakeSession.Copy().(match_making_types.MatchmakeSession)
resultSession = &newMatchmakeSession
nexError = database.CreateMatchmakeSession(commonProtocol.manager, connection, resultSession)
if nexError != nil {
common_globals.Logger.Error(nexError.Error())
commonProtocol.manager.Mutex.Unlock()
return nil, nexError
}
}
participants, nexError := database.JoinMatchmakeSession(commonProtocol.manager, *resultSession, connection, 1, string(message))
if nexError != nil {
commonProtocol.manager.Mutex.Unlock()
return nil, nexError
}
resultSession.ParticipationCount = types.NewUInt32(participants)
commonProtocol.manager.Mutex.Unlock()
matchmakeDataHolder := match_making_types.NewGatheringHolder()
matchmakeDataHolder.Object = resultSession.Copy().(match_making_types.GatheringInterface)
rmcResponseStream := nex.NewByteStreamOut(endpoint.LibraryVersions(), endpoint.ByteStreamSettings())
matchmakeDataHolder.WriteTo(rmcResponseStream)
rmcResponseBody := rmcResponseStream.Bytes()
rmcResponse := nex.NewRMCSuccess(endpoint, rmcResponseBody)
rmcResponse.ProtocolID = matchmake_extension.ProtocolID
rmcResponse.MethodID = matchmake_extension.MethodAutoMatchmakePostpone
rmcResponse.CallID = callID
if commonProtocol.OnAfterAutoMatchmakePostpone != nil {
go commonProtocol.OnAfterAutoMatchmakePostpone(packet, anyGathering, message)
}
return rmcResponse, nil
}

View file

@ -0,0 +1,105 @@
package matchmake_extension
import (
"github.com/PretendoNetwork/nex-go/v2"
"github.com/PretendoNetwork/nex-go/v2/types"
common_globals "github.com/PretendoNetwork/nex-protocols-common-go/v2/globals"
"github.com/PretendoNetwork/nex-protocols-common-go/v2/matchmake-extension/database"
"github.com/PretendoNetwork/nex-protocols-go/v2/match-making/constants"
match_making_types "github.com/PretendoNetwork/nex-protocols-go/v2/match-making/types"
matchmake_extension "github.com/PretendoNetwork/nex-protocols-go/v2/matchmake-extension"
)
func (commonProtocol *CommonProtocol) autoMatchmakeWithParamPostpone(err error, packet nex.PacketInterface, callID uint32, autoMatchmakeParam match_making_types.AutoMatchmakeParam) (*nex.RMCMessage, *nex.Error) {
if commonProtocol.CleanupMatchmakeSessionSearchCriterias == nil {
common_globals.Logger.Warning("MatchmakeExtension::AutoMatchmakeWithParam_Postpone missing CleanupMatchmakeSessionSearchCriterias!")
return nil, nex.NewError(nex.ResultCodes.Core.NotImplemented, "change_error")
}
if err != nil {
common_globals.Logger.Error(err.Error())
return nil, nex.NewError(nex.ResultCodes.Core.InvalidArgument, "change_error")
}
if !common_globals.CheckValidMatchmakeSession(autoMatchmakeParam.SourceMatchmakeSession) {
return nil, nex.NewError(nex.ResultCodes.Core.InvalidArgument, "change_error")
}
if len(autoMatchmakeParam.JoinMessage) > 256 {
return nil, nex.NewError(nex.ResultCodes.Core.InvalidArgument, "change_error")
}
connection := packet.Sender().(*nex.PRUDPConnection)
endpoint := connection.Endpoint().(*nex.PRUDPEndPoint)
commonProtocol.manager.Mutex.Lock()
if autoMatchmakeParam.GIDForParticipationCheck != 0 {
// * Check that all new participants are participating in the specified gathering ID
nexError := database.CheckGatheringForParticipation(commonProtocol.manager, uint32(autoMatchmakeParam.GIDForParticipationCheck), append(autoMatchmakeParam.AdditionalParticipants, connection.PID()))
if nexError != nil {
commonProtocol.manager.Mutex.Unlock()
return nil, nexError
}
}
// * A client may disconnect from a session without leaving reliably,
// * so let's make sure the client is removed from the session
database.EndMatchmakeSessionsParticipation(commonProtocol.manager, connection)
commonProtocol.CleanupMatchmakeSessionSearchCriterias(autoMatchmakeParam.LstSearchCriteria)
resultRange := types.NewResultRange()
resultRange.Length = 1
resultSessions, nexError := database.FindMatchmakeSessionBySearchCriteria(commonProtocol.manager, connection, autoMatchmakeParam.LstSearchCriteria, resultRange, &autoMatchmakeParam.SourceMatchmakeSession)
if nexError != nil {
commonProtocol.manager.Mutex.Unlock()
return nil, nexError
}
var resultSession match_making_types.MatchmakeSession
if len(resultSessions) == 0 {
resultSession = autoMatchmakeParam.SourceMatchmakeSession.Copy().(match_making_types.MatchmakeSession)
nexError = database.CreateMatchmakeSession(commonProtocol.manager, connection, &resultSession)
if nexError != nil {
common_globals.Logger.Error(nexError.Error())
commonProtocol.manager.Mutex.Unlock()
return nil, nexError
}
} else {
resultSession = resultSessions[0]
// TODO - What should really happen here?
if resultSession.UserPasswordEnabled || resultSession.SystemPasswordEnabled {
commonProtocol.manager.Mutex.Unlock()
return nil, nex.NewError(nex.ResultCodes.RendezVous.PermissionDenied, "change_error")
}
}
participants, nexError := database.JoinMatchmakeSessionWithParticipants(commonProtocol.manager, resultSession, connection, autoMatchmakeParam.AdditionalParticipants, string(autoMatchmakeParam.JoinMessage), constants.JoinMatchmakeSessionBehaviorJoinMyself)
if nexError != nil {
commonProtocol.manager.Mutex.Unlock()
return nil, nexError
}
resultSession.ParticipationCount = types.NewUInt32(participants)
commonProtocol.manager.Mutex.Unlock()
rmcResponseStream := nex.NewByteStreamOut(endpoint.LibraryVersions(), endpoint.ByteStreamSettings())
resultSession.WriteTo(rmcResponseStream)
rmcResponseBody := rmcResponseStream.Bytes()
rmcResponse := nex.NewRMCSuccess(endpoint, rmcResponseBody)
rmcResponse.ProtocolID = matchmake_extension.ProtocolID
rmcResponse.MethodID = matchmake_extension.MethodAutoMatchmakeWithParamPostpone
rmcResponse.CallID = callID
if commonProtocol.OnAfterAutoMatchmakeWithParamPostpone != nil {
go commonProtocol.OnAfterAutoMatchmakeWithParamPostpone(packet, autoMatchmakeParam)
}
return rmcResponse, nil
}

View file

@ -0,0 +1,118 @@
package matchmake_extension
import (
"github.com/PretendoNetwork/nex-go/v2"
"github.com/PretendoNetwork/nex-go/v2/types"
common_globals "github.com/PretendoNetwork/nex-protocols-common-go/v2/globals"
"github.com/PretendoNetwork/nex-protocols-common-go/v2/matchmake-extension/database"
match_making_types "github.com/PretendoNetwork/nex-protocols-go/v2/match-making/types"
matchmake_extension "github.com/PretendoNetwork/nex-protocols-go/v2/matchmake-extension"
)
func (commonProtocol *CommonProtocol) autoMatchmakeWithSearchCriteriaPostpone(err error, packet nex.PacketInterface, callID uint32, lstSearchCriteria types.List[match_making_types.MatchmakeSessionSearchCriteria], anyGathering match_making_types.GatheringHolder, strMessage types.String) (*nex.RMCMessage, *nex.Error) {
if commonProtocol.CleanupMatchmakeSessionSearchCriterias == nil {
common_globals.Logger.Warning("MatchmakeExtension::AutoMatchmakeWithSearchCriteria_Postpone missing CleanupMatchmakeSessionSearchCriterias!")
return nil, nex.NewError(nex.ResultCodes.Core.NotImplemented, "change_error")
}
if err != nil {
common_globals.Logger.Error(err.Error())
return nil, nex.NewError(nex.ResultCodes.Core.InvalidArgument, "change_error")
}
if len(strMessage) > 256 {
return nil, nex.NewError(nex.ResultCodes.Core.InvalidArgument, "change_error")
}
if len(lstSearchCriteria) > 2 {
return nil, nex.NewError(nex.ResultCodes.Core.InvalidArgument, "change_error")
}
connection := packet.Sender().(*nex.PRUDPConnection)
endpoint := connection.Endpoint().(*nex.PRUDPEndPoint)
commonProtocol.manager.Mutex.Lock()
// * A client may disconnect from a session without leaving reliably,
// * so let's make sure the client is removed from the session
database.EndMatchmakeSessionsParticipation(commonProtocol.manager, connection)
var matchmakeSession match_making_types.MatchmakeSession
if anyGathering.Object.GatheringObjectID().Equals(types.NewString("MatchmakeSession")) {
matchmakeSession = anyGathering.Object.(match_making_types.MatchmakeSession)
} else {
common_globals.Logger.Critical("Non-MatchmakeSession DataType?!")
commonProtocol.manager.Mutex.Unlock()
return nil, nex.NewError(nex.ResultCodes.Core.InvalidArgument, "change_error")
}
if !common_globals.CheckValidMatchmakeSession(matchmakeSession) {
commonProtocol.manager.Mutex.Unlock()
return nil, nex.NewError(nex.ResultCodes.Core.InvalidArgument, "change_error")
}
commonProtocol.CleanupMatchmakeSessionSearchCriterias(lstSearchCriteria)
resultRange := types.NewResultRange()
resultRange.Length = 1
resultSessions, nexError := database.FindMatchmakeSessionBySearchCriteria(commonProtocol.manager, connection, lstSearchCriteria, resultRange, &matchmakeSession)
if nexError != nil {
commonProtocol.manager.Mutex.Unlock()
return nil, nexError
}
var resultSession match_making_types.MatchmakeSession
if len(resultSessions) == 0 {
resultSession = matchmakeSession.Copy().(match_making_types.MatchmakeSession)
nexError = database.CreateMatchmakeSession(commonProtocol.manager, connection, &resultSession)
if nexError != nil {
common_globals.Logger.Error(nexError.Error())
commonProtocol.manager.Mutex.Unlock()
return nil, nexError
}
} else {
resultSession = resultSessions[0]
// TODO - What should really happen here?
if resultSession.UserPasswordEnabled || resultSession.SystemPasswordEnabled {
commonProtocol.manager.Mutex.Unlock()
return nil, nex.NewError(nex.ResultCodes.RendezVous.PermissionDenied, "change_error")
}
}
var vacantParticipants uint16 = 1
if len(lstSearchCriteria) > 0 {
vacantParticipants = uint16(lstSearchCriteria[0].VacantParticipants)
}
participants, nexError := database.JoinMatchmakeSession(commonProtocol.manager, resultSession, connection, vacantParticipants, string(strMessage))
if nexError != nil {
commonProtocol.manager.Mutex.Unlock()
return nil, nexError
}
resultSession.ParticipationCount = types.NewUInt32(participants)
commonProtocol.manager.Mutex.Unlock()
matchmakeDataHolder := match_making_types.NewGatheringHolder()
matchmakeDataHolder.Object = resultSession.Copy().(match_making_types.GatheringInterface)
rmcResponseStream := nex.NewByteStreamOut(endpoint.LibraryVersions(), endpoint.ByteStreamSettings())
matchmakeDataHolder.WriteTo(rmcResponseStream)
rmcResponseBody := rmcResponseStream.Bytes()
rmcResponse := nex.NewRMCSuccess(endpoint, rmcResponseBody)
rmcResponse.ProtocolID = matchmake_extension.ProtocolID
rmcResponse.MethodID = matchmake_extension.MethodAutoMatchmakeWithSearchCriteriaPostpone
rmcResponse.CallID = callID
if commonProtocol.OnAfterAutoMatchmakeWithSearchCriteriaPostpone != nil {
go commonProtocol.OnAfterAutoMatchmakeWithSearchCriteriaPostpone(packet, lstSearchCriteria, anyGathering, strMessage)
}
return rmcResponse, nil
}

View file

@ -0,0 +1,69 @@
package matchmake_extension
import (
"github.com/PretendoNetwork/nex-go/v2"
"github.com/PretendoNetwork/nex-go/v2/types"
common_globals "github.com/PretendoNetwork/nex-protocols-common-go/v2/globals"
"github.com/PretendoNetwork/nex-protocols-common-go/v2/matchmake-extension/database"
match_making_types "github.com/PretendoNetwork/nex-protocols-go/v2/match-making/types"
matchmake_extension "github.com/PretendoNetwork/nex-protocols-go/v2/matchmake-extension"
)
func (commonProtocol *CommonProtocol) browseMatchmakeSession(err error, packet nex.PacketInterface, callID uint32, searchCriteria match_making_types.MatchmakeSessionSearchCriteria, resultRange types.ResultRange) (*nex.RMCMessage, *nex.Error) {
if err != nil {
common_globals.Logger.Error(err.Error())
return nil, nex.NewError(nex.ResultCodes.Core.InvalidArgument, "change_error")
}
connection := packet.Sender().(*nex.PRUDPConnection)
endpoint := connection.Endpoint().(*nex.PRUDPEndPoint)
commonProtocol.manager.Mutex.RLock()
searchCriterias := []match_making_types.MatchmakeSessionSearchCriteria{searchCriteria}
lstSearchCriteria := types.NewList[match_making_types.MatchmakeSessionSearchCriteria]()
lstSearchCriteria = searchCriterias
if commonProtocol.CleanupMatchmakeSessionSearchCriterias != nil {
commonProtocol.CleanupMatchmakeSessionSearchCriterias(lstSearchCriteria)
}
sessions, nexError := database.FindMatchmakeSessionBySearchCriteria(commonProtocol.manager, connection, searchCriterias, resultRange, nil)
if nexError != nil {
commonProtocol.manager.Mutex.RUnlock()
return nil, nexError
}
lstGathering := types.NewList[match_making_types.GatheringHolder]()
for _, session := range sessions {
// * Scrap session key and user password
session.SessionKey = make([]byte, 0)
session.UserPassword = ""
matchmakeSessionDataHolder := match_making_types.NewGatheringHolder()
matchmakeSessionDataHolder.Object = session.Copy().(match_making_types.GatheringInterface)
lstGathering = append(lstGathering, matchmakeSessionDataHolder)
}
commonProtocol.manager.Mutex.RUnlock()
rmcResponseStream := nex.NewByteStreamOut(endpoint.LibraryVersions(), endpoint.ByteStreamSettings())
lstGathering.WriteTo(rmcResponseStream)
rmcResponseBody := rmcResponseStream.Bytes()
rmcResponse := nex.NewRMCSuccess(endpoint, rmcResponseBody)
rmcResponse.ProtocolID = matchmake_extension.ProtocolID
rmcResponse.MethodID = matchmake_extension.MethodBrowseMatchmakeSession
rmcResponse.CallID = callID
if commonProtocol.OnAfterBrowseMatchmakeSession != nil {
go commonProtocol.OnAfterBrowseMatchmakeSession(packet, searchCriteria, resultRange)
}
return rmcResponse, nil
}

View file

@ -0,0 +1,51 @@
package matchmake_extension
import (
"github.com/PretendoNetwork/nex-go/v2"
"github.com/PretendoNetwork/nex-go/v2/types"
common_globals "github.com/PretendoNetwork/nex-protocols-common-go/v2/globals"
"github.com/PretendoNetwork/nex-protocols-common-go/v2/matchmake-extension/database"
matchmake_extension "github.com/PretendoNetwork/nex-protocols-go/v2/matchmake-extension"
)
func (commonProtocol *CommonProtocol) closeParticipation(err error, packet nex.PacketInterface, callID uint32, gid types.UInt32) (*nex.RMCMessage, *nex.Error) {
if err != nil {
common_globals.Logger.Error(err.Error())
return nil, nex.NewError(nex.ResultCodes.Core.InvalidArgument, "change_error")
}
connection := packet.Sender().(*nex.PRUDPConnection)
endpoint := connection.Endpoint().(*nex.PRUDPEndPoint)
commonProtocol.manager.Mutex.Lock()
session, _, nexError := database.GetMatchmakeSessionByID(commonProtocol.manager, endpoint, uint32(gid))
if nexError != nil {
commonProtocol.manager.Mutex.Unlock()
return nil, nexError
}
if !session.Gathering.OwnerPID.Equals(connection.PID()) {
commonProtocol.manager.Mutex.Unlock()
return nil, nex.NewError(nex.ResultCodes.RendezVous.PermissionDenied, "change_error")
}
nexError = database.UpdateParticipation(commonProtocol.manager, uint32(gid), false)
if nexError != nil {
commonProtocol.manager.Mutex.Unlock()
return nil, nexError
}
commonProtocol.manager.Mutex.Unlock()
rmcResponse := nex.NewRMCSuccess(endpoint, nil)
rmcResponse.ProtocolID = matchmake_extension.ProtocolID
rmcResponse.MethodID = matchmake_extension.MethodCloseParticipation
rmcResponse.CallID = callID
if commonProtocol.OnAfterCloseParticipation != nil {
go commonProtocol.OnAfterCloseParticipation(packet, gid)
}
return rmcResponse, nil
}

View file

@ -0,0 +1,69 @@
package matchmake_extension
import (
"github.com/PretendoNetwork/nex-go/v2"
"github.com/PretendoNetwork/nex-go/v2/types"
common_globals "github.com/PretendoNetwork/nex-protocols-common-go/v2/globals"
match_making_database "github.com/PretendoNetwork/nex-protocols-common-go/v2/match-making/database"
"github.com/PretendoNetwork/nex-protocols-common-go/v2/matchmake-extension/database"
match_making_types "github.com/PretendoNetwork/nex-protocols-go/v2/match-making/types"
matchmake_extension "github.com/PretendoNetwork/nex-protocols-go/v2/matchmake-extension"
)
func (commonProtocol *CommonProtocol) createCommunity(err error, packet nex.PacketInterface, callID uint32, community match_making_types.PersistentGathering, strMessage types.String) (*nex.RMCMessage, *nex.Error) {
if err != nil {
common_globals.Logger.Error(err.Error())
return nil, nex.NewError(nex.ResultCodes.Core.InvalidArgument, "change_error")
}
if !common_globals.CheckValidPersistentGathering(community) {
return nil, nex.NewError(nex.ResultCodes.Core.InvalidArgument, "change_error")
}
connection := packet.Sender().(*nex.PRUDPConnection)
endpoint := connection.Endpoint().(*nex.PRUDPEndPoint)
commonProtocol.manager.Mutex.Lock()
createdPersistentGatherings, nexError := database.GetCreatedPersistentGatherings(commonProtocol.manager, connection.PID())
if nexError != nil {
commonProtocol.manager.Mutex.Unlock()
return nil, nexError
}
if createdPersistentGatherings >= commonProtocol.PersistentGatheringCreationMax {
commonProtocol.manager.Mutex.Unlock()
return nil, nex.NewError(nex.ResultCodes.RendezVous.PersistentGatheringCreationMax, "change_error")
}
nexError = database.CreatePersistentGathering(commonProtocol.manager, connection, &community)
if nexError != nil {
commonProtocol.manager.Mutex.Unlock()
return nil, nexError
}
_, nexError = match_making_database.JoinGathering(commonProtocol.manager, uint32(community.ID), connection, 1, string(strMessage))
if nexError != nil {
commonProtocol.manager.Mutex.Unlock()
return nil, nexError
}
commonProtocol.manager.Mutex.Unlock()
rmcResponseStream := nex.NewByteStreamOut(endpoint.LibraryVersions(), endpoint.ByteStreamSettings())
community.ID.WriteTo(rmcResponseStream)
rmcResponseBody := rmcResponseStream.Bytes()
rmcResponse := nex.NewRMCSuccess(endpoint, rmcResponseBody)
rmcResponse.ProtocolID = matchmake_extension.ProtocolID
rmcResponse.MethodID = matchmake_extension.MethodCreateCommunity
rmcResponse.CallID = callID
if commonProtocol.OnAfterCreateCommunity != nil {
go commonProtocol.OnAfterCreateCommunity(packet, community, strMessage)
}
return rmcResponse, nil
}

View file

@ -0,0 +1,85 @@
package matchmake_extension
import (
"github.com/PretendoNetwork/nex-go/v2"
"github.com/PretendoNetwork/nex-go/v2/types"
common_globals "github.com/PretendoNetwork/nex-protocols-common-go/v2/globals"
"github.com/PretendoNetwork/nex-protocols-common-go/v2/matchmake-extension/database"
match_making_types "github.com/PretendoNetwork/nex-protocols-go/v2/match-making/types"
matchmake_extension "github.com/PretendoNetwork/nex-protocols-go/v2/matchmake-extension"
)
func (commonProtocol *CommonProtocol) createMatchmakeSession(err error, packet nex.PacketInterface, callID uint32, anyGathering match_making_types.GatheringHolder, message types.String, participationCount types.UInt16) (*nex.RMCMessage, *nex.Error) {
if err != nil {
common_globals.Logger.Error(err.Error())
return nil, nex.NewError(nex.ResultCodes.Core.InvalidArgument, "change_error")
}
if len(message) > 256 {
return nil, nex.NewError(nex.ResultCodes.Core.InvalidArgument, "change_error")
}
connection := packet.Sender().(*nex.PRUDPConnection)
endpoint := connection.Endpoint().(*nex.PRUDPEndPoint)
server := endpoint.Server
commonProtocol.manager.Mutex.Lock()
// * A client may disconnect from a session without leaving reliably,
// * so let's make sure the client is removed from the session
database.EndMatchmakeSessionsParticipation(commonProtocol.manager, connection)
var matchmakeSession match_making_types.MatchmakeSession
if anyGathering.Object.GatheringObjectID().Equals(types.NewString("MatchmakeSession")) {
matchmakeSession = anyGathering.Object.(match_making_types.MatchmakeSession)
} else {
common_globals.Logger.Critical("Non-MatchmakeSession DataType?!")
commonProtocol.manager.Mutex.Unlock()
return nil, nex.NewError(nex.ResultCodes.Core.InvalidArgument, "change_error")
}
if !common_globals.CheckValidMatchmakeSession(matchmakeSession) {
commonProtocol.manager.Mutex.Unlock()
return nil, nex.NewError(nex.ResultCodes.Core.InvalidArgument, "change_error")
}
nexError := database.CreateMatchmakeSession(commonProtocol.manager, connection, &matchmakeSession)
if nexError != nil {
common_globals.Logger.Error(nexError.Error())
commonProtocol.manager.Mutex.Unlock()
return nil, nexError
}
participants, nexError := database.JoinMatchmakeSession(commonProtocol.manager, matchmakeSession, connection, uint16(participationCount), string(message))
if nexError != nil {
common_globals.Logger.Error(nexError.Error())
commonProtocol.manager.Mutex.Unlock()
return nil, nexError
}
matchmakeSession.ParticipationCount = types.NewUInt32(participants)
commonProtocol.manager.Mutex.Unlock()
rmcResponseStream := nex.NewByteStreamOut(endpoint.LibraryVersions(), endpoint.ByteStreamSettings())
matchmakeSession.Gathering.ID.WriteTo(rmcResponseStream)
if server.LibraryVersions.MatchMaking.GreaterOrEqual("3.0.0") {
matchmakeSession.SessionKey.WriteTo(rmcResponseStream)
}
rmcResponseBody := rmcResponseStream.Bytes()
rmcResponse := nex.NewRMCSuccess(endpoint, rmcResponseBody)
rmcResponse.ProtocolID = matchmake_extension.ProtocolID
rmcResponse.MethodID = matchmake_extension.MethodCreateMatchmakeSession
rmcResponse.CallID = callID
if commonProtocol.OnAfterCreateMatchmakeSession != nil {
go commonProtocol.OnAfterCreateMatchmakeSession(packet, anyGathering, message, participationCount)
}
return rmcResponse, nil
}

View file

@ -0,0 +1,80 @@
package matchmake_extension
import (
"github.com/PretendoNetwork/nex-go/v2"
"github.com/PretendoNetwork/nex-go/v2/types"
common_globals "github.com/PretendoNetwork/nex-protocols-common-go/v2/globals"
"github.com/PretendoNetwork/nex-protocols-common-go/v2/matchmake-extension/database"
"github.com/PretendoNetwork/nex-protocols-go/v2/match-making/constants"
match_making_types "github.com/PretendoNetwork/nex-protocols-go/v2/match-making/types"
matchmake_extension "github.com/PretendoNetwork/nex-protocols-go/v2/matchmake-extension"
)
func (commonProtocol *CommonProtocol) createMatchmakeSessionWithParam(err error, packet nex.PacketInterface, callID uint32, createMatchmakeSessionParam match_making_types.CreateMatchmakeSessionParam) (*nex.RMCMessage, *nex.Error) {
if err != nil {
common_globals.Logger.Error(err.Error())
return nil, nex.NewError(nex.ResultCodes.Core.InvalidArgument, "change_error")
}
connection := packet.Sender().(*nex.PRUDPConnection)
endpoint := connection.Endpoint().(*nex.PRUDPEndPoint)
if !common_globals.CheckValidMatchmakeSession(createMatchmakeSessionParam.SourceMatchmakeSession) {
return nil, nex.NewError(nex.ResultCodes.Core.InvalidArgument, "change_error")
}
if len(createMatchmakeSessionParam.JoinMessage) > 256 {
return nil, nex.NewError(nex.ResultCodes.Core.InvalidArgument, "change_error")
}
commonProtocol.manager.Mutex.Lock()
if createMatchmakeSessionParam.GIDForParticipationCheck != 0 {
// * Check that all new participants are participating in the specified gathering ID
nexError := database.CheckGatheringForParticipation(commonProtocol.manager, uint32(createMatchmakeSessionParam.GIDForParticipationCheck), append(createMatchmakeSessionParam.AdditionalParticipants, connection.PID()))
if nexError != nil {
commonProtocol.manager.Mutex.Unlock()
return nil, nexError
}
}
// * A client may disconnect from a session without leaving reliably,
// * so let's make sure the client is removed from all sessions
database.EndMatchmakeSessionsParticipation(commonProtocol.manager, connection)
joinedMatchmakeSession := createMatchmakeSessionParam.SourceMatchmakeSession.Copy().(match_making_types.MatchmakeSession)
nexError := database.CreateMatchmakeSession(commonProtocol.manager, connection, &joinedMatchmakeSession)
if nexError != nil {
common_globals.Logger.Error(nexError.Error())
commonProtocol.manager.Mutex.Unlock()
return nil, nexError
}
participants, nexError := database.JoinMatchmakeSessionWithParticipants(commonProtocol.manager, joinedMatchmakeSession, connection, createMatchmakeSessionParam.AdditionalParticipants, string(createMatchmakeSessionParam.JoinMessage), constants.JoinMatchmakeSessionBehaviorJoinMyself)
if nexError != nil {
common_globals.Logger.Error(nexError.Error())
commonProtocol.manager.Mutex.Unlock()
return nil, nexError
}
commonProtocol.manager.Mutex.Unlock()
joinedMatchmakeSession.ParticipationCount = types.NewUInt32(participants)
rmcResponseStream := nex.NewByteStreamOut(endpoint.LibraryVersions(), endpoint.ByteStreamSettings())
joinedMatchmakeSession.WriteTo(rmcResponseStream)
rmcResponseBody := rmcResponseStream.Bytes()
rmcResponse := nex.NewRMCSuccess(endpoint, rmcResponseBody)
rmcResponse.ProtocolID = matchmake_extension.ProtocolID
rmcResponse.MethodID = matchmake_extension.MethodCreateMatchmakeSessionWithParam
rmcResponse.CallID = callID
if commonProtocol.OnAfterCreateMatchmakeSessionWithParam != nil {
go commonProtocol.OnAfterCreateMatchmakeSessionWithParam(packet, createMatchmakeSessionParam)
}
return rmcResponse, nil
}

View file

@ -0,0 +1,26 @@
package database
import (
"slices"
"github.com/PretendoNetwork/nex-go/v2"
"github.com/PretendoNetwork/nex-go/v2/types"
common_globals "github.com/PretendoNetwork/nex-protocols-common-go/v2/globals"
"github.com/PretendoNetwork/nex-protocols-common-go/v2/match-making/database"
)
// CheckGatheringForParticipation checks that the given PIDs are participating on the gathering ID
func CheckGatheringForParticipation(manager *common_globals.MatchmakingManager, gatheringID uint32, participantsCheck []types.PID) *nex.Error {
_, _, participants, _, err := database.FindGatheringByID(manager, gatheringID)
if err != nil {
return err
}
for _, participant := range participantsCheck {
if !slices.Contains(participants, uint64(participant)) {
return nex.NewError(nex.ResultCodes.RendezVous.NotParticipatedGathering, "change_error")
}
}
return nil
}

View file

@ -0,0 +1,100 @@
package database
import (
"crypto/rand"
"github.com/PretendoNetwork/nex-go/v2"
"github.com/PretendoNetwork/nex-go/v2/types"
common_globals "github.com/PretendoNetwork/nex-protocols-common-go/v2/globals"
match_making_database "github.com/PretendoNetwork/nex-protocols-common-go/v2/match-making/database"
match_making_types "github.com/PretendoNetwork/nex-protocols-go/v2/match-making/types"
pqextended "github.com/PretendoNetwork/pq-extended"
)
// CreateMatchmakeSession creates a new MatchmakeSession on the database. No participants are added
func CreateMatchmakeSession(manager *common_globals.MatchmakingManager, connection *nex.PRUDPConnection, matchmakeSession *match_making_types.MatchmakeSession) *nex.Error {
startedTime, nexError := match_making_database.RegisterGathering(manager, connection.PID(), connection.PID(), &matchmakeSession.Gathering, "MatchmakeSession")
if nexError != nil {
return nexError
}
attribs := make([]uint32, len(matchmakeSession.Attributes))
for i, value := range matchmakeSession.Attributes {
attribs[i] = uint32(value)
}
endpoint := connection.Endpoint().(*nex.PRUDPEndPoint)
matchmakeParam := nex.NewByteStreamOut(endpoint.LibraryVersions(), endpoint.ByteStreamSettings())
srVariant := types.NewVariant()
srVariant.TypeID = 3
srVariant.Type = types.NewBool(true)
matchmakeSession.MatchmakeParam.Params["@SR"] = srVariant
girVariant := types.NewVariant()
girVariant.TypeID = 1
girVariant.Type = types.NewInt64(3)
matchmakeSession.MatchmakeParam.Params["@GIR"] = girVariant
matchmakeSession.MatchmakeParam.WriteTo(matchmakeParam)
matchmakeSession.StartedTime = startedTime
matchmakeSession.SessionKey = make([]byte, 32)
matchmakeSession.SystemPasswordEnabled = false
rand.Read(matchmakeSession.SessionKey)
_, err := manager.Database.Exec(`INSERT INTO matchmaking.matchmake_sessions (
id,
game_mode,
attribs,
open_participation,
matchmake_system_type,
application_buffer,
progress_score,
session_key,
option_zero,
matchmake_param,
user_password,
refer_gid,
user_password_enabled,
system_password_enabled,
codeword
) VALUES (
$1,
$2,
$3,
$4,
$5,
$6,
$7,
$8,
$9,
$10,
$11,
$12,
$13,
$14,
$15
)`,
matchmakeSession.Gathering.ID,
matchmakeSession.GameMode,
pqextended.Array(attribs),
matchmakeSession.OpenParticipation,
matchmakeSession.MatchmakeSystemType,
matchmakeSession.ApplicationBuffer,
matchmakeSession.ProgressScore,
matchmakeSession.SessionKey,
matchmakeSession.Option,
matchmakeParam.Bytes(),
matchmakeSession.UserPassword,
matchmakeSession.ReferGID,
matchmakeSession.UserPasswordEnabled,
matchmakeSession.SystemPasswordEnabled,
matchmakeSession.CodeWord,
)
if err != nil {
return nex.NewError(nex.ResultCodes.Core.Unknown, err.Error())
}
return nil
}

View file

@ -0,0 +1,54 @@
package database
import (
"github.com/PretendoNetwork/nex-go/v2"
"github.com/PretendoNetwork/nex-go/v2/types"
common_globals "github.com/PretendoNetwork/nex-protocols-common-go/v2/globals"
match_making_database "github.com/PretendoNetwork/nex-protocols-common-go/v2/match-making/database"
match_making_types "github.com/PretendoNetwork/nex-protocols-go/v2/match-making/types"
pqextended "github.com/PretendoNetwork/pq-extended"
)
// CreatePersistentGathering creates a new PersistentGathering on the database. No participants are added
func CreatePersistentGathering(manager *common_globals.MatchmakingManager, connection *nex.PRUDPConnection, persistentGathering *match_making_types.PersistentGathering) *nex.Error {
_, nexError := match_making_database.RegisterGathering(manager, connection.PID(), types.NewPID(0), &persistentGathering.Gathering, "PersistentGathering")
if nexError != nil {
return nexError
}
attribs := make([]uint32, len(persistentGathering.Attribs))
for i, value := range persistentGathering.Attribs {
attribs[i] = uint32(value)
}
_, err := manager.Database.Exec(`INSERT INTO matchmaking.persistent_gatherings (
id,
community_type,
password,
attribs,
application_buffer,
participation_start_date,
participation_end_date
) VALUES (
$1,
$2,
$3,
$4,
$5,
$6,
$7
)`,
uint32(persistentGathering.Gathering.ID),
uint32(persistentGathering.CommunityType),
string(persistentGathering.Password),
pqextended.Array(attribs),
[]byte(persistentGathering.ApplicationBuffer),
persistentGathering.ParticipationStartDate.Standard(),
persistentGathering.ParticipationEndDate.Standard(),
)
if err != nil {
return nex.NewError(nex.ResultCodes.Core.Unknown, err.Error())
}
return nil
}

View file

@ -0,0 +1,29 @@
package database
import (
"github.com/PretendoNetwork/nex-go/v2"
common_globals "github.com/PretendoNetwork/nex-protocols-common-go/v2/globals"
"github.com/PretendoNetwork/nex-protocols-common-go/v2/match-making/database"
)
// EndMatchmakeSessionsParticipation ends participation on all matchmake sessions
func EndMatchmakeSessionsParticipation(manager *common_globals.MatchmakingManager, connection *nex.PRUDPConnection) {
rows, err := manager.Database.Query(`SELECT id FROM matchmaking.gatherings WHERE type='MatchmakeSession' AND $1=ANY(participants)`, connection.PID())
if err != nil {
common_globals.Logger.Error(err.Error())
return
}
for rows.Next() {
var gatheringID uint32
err = rows.Scan(&gatheringID)
if err != nil {
common_globals.Logger.Error(err.Error())
continue
}
database.EndGatheringParticipation(manager, gatheringID, connection, "")
}
rows.Close()
}

View file

@ -0,0 +1,145 @@
package database
import (
"database/sql"
"time"
"github.com/PretendoNetwork/nex-go/v2"
"github.com/PretendoNetwork/nex-go/v2/types"
common_globals "github.com/PretendoNetwork/nex-protocols-common-go/v2/globals"
match_making_types "github.com/PretendoNetwork/nex-protocols-go/v2/match-making/types"
pqextended "github.com/PretendoNetwork/pq-extended"
)
// FindMatchmakeSession finds a matchmake session with the given search matchmake session
func FindMatchmakeSession(manager *common_globals.MatchmakingManager, connection *nex.PRUDPConnection, searchMatchmakeSession match_making_types.MatchmakeSession) (*match_making_types.MatchmakeSession, *nex.Error) {
attribs := make([]uint32, len(searchMatchmakeSession.Attributes))
for i, value := range searchMatchmakeSession.Attributes {
attribs[i] = uint32(value)
}
endpoint := connection.Endpoint().(*nex.PRUDPEndPoint)
searchStatement := `SELECT
g.id,
g.owner_pid,
g.host_pid,
g.min_participants,
g.max_participants,
g.participation_policy,
g.policy_argument,
g.flags,
g.state,
g.description,
array_length(g.participants, 1),
g.started_time,
ms.game_mode,
ms.attribs,
ms.open_participation,
ms.matchmake_system_type,
ms.application_buffer,
ms.progress_score,
ms.session_key,
ms.option_zero,
ms.matchmake_param,
ms.user_password,
ms.refer_gid,
ms.user_password_enabled,
ms.system_password_enabled,
ms.codeword
FROM matchmaking.gatherings AS g
INNER JOIN matchmaking.matchmake_sessions AS ms ON ms.id = g.id
WHERE
g.registered=true AND
g.type='MatchmakeSession' AND
g.host_pid <> 0 AND
ms.open_participation=true AND
array_length(g.participants, 1) < g.max_participants AND
ms.user_password_enabled=false AND
ms.system_password_enabled=false AND
g.max_participants=$1 AND
g.min_participants=$2 AND
ms.game_mode=$3 AND
ms.attribs[1]=$4 AND
ms.attribs[3]=$6 AND
ms.attribs[4]=$7 AND
ms.attribs[5]=$8 AND
ms.attribs[6]=$9 AND
ms.matchmake_system_type=$10 AND
ms.codeword=$11 AND (CASE WHEN g.participation_policy=98 THEN g.owner_pid=ANY($12) ELSE true END)
ORDER BY abs($5 - ms.attribs[2])` // * Use "Closest attribute" selection method, guessing from Mario Kart 7
var friendList []uint32
// * Prevent access to friend rooms if not implemented
if manager.GetUserFriendPIDs != nil {
friendList = manager.GetUserFriendPIDs(uint32(connection.PID()))
}
resultMatchmakeSession := match_making_types.NewMatchmakeSession()
var startedTime time.Time
var resultAttribs []uint32
var resultMatchmakeParam []byte
// * For simplicity, we will only compare the values that exist on a MatchmakeSessionSearchCriteria
err := manager.Database.QueryRow(searchStatement,
searchMatchmakeSession.Gathering.MaximumParticipants,
searchMatchmakeSession.Gathering.MinimumParticipants,
searchMatchmakeSession.GameMode,
attribs[0],
attribs[1],
attribs[2],
attribs[3],
attribs[4],
attribs[5],
searchMatchmakeSession.MatchmakeSystemType,
searchMatchmakeSession.CodeWord,
pqextended.Array(friendList),
).Scan(
&resultMatchmakeSession.Gathering.ID,
&resultMatchmakeSession.Gathering.OwnerPID,
&resultMatchmakeSession.Gathering.HostPID,
&resultMatchmakeSession.Gathering.MinimumParticipants,
&resultMatchmakeSession.Gathering.MaximumParticipants,
&resultMatchmakeSession.Gathering.ParticipationPolicy,
&resultMatchmakeSession.Gathering.PolicyArgument,
&resultMatchmakeSession.Gathering.Flags,
&resultMatchmakeSession.Gathering.State,
&resultMatchmakeSession.Gathering.Description,
&resultMatchmakeSession.ParticipationCount,
&startedTime,
&resultMatchmakeSession.GameMode,
pqextended.Array(&resultAttribs),
&resultMatchmakeSession.OpenParticipation,
&resultMatchmakeSession.MatchmakeSystemType,
&resultMatchmakeSession.ApplicationBuffer,
&resultMatchmakeSession.ProgressScore,
&resultMatchmakeSession.SessionKey,
&resultMatchmakeSession.Option,
&resultMatchmakeParam,
&resultMatchmakeSession.UserPassword,
&resultMatchmakeSession.ReferGID,
&resultMatchmakeSession.UserPasswordEnabled,
&resultMatchmakeSession.SystemPasswordEnabled,
&resultMatchmakeSession.CodeWord,
)
if err != nil {
if err == sql.ErrNoRows {
return nil, nil
} else {
return nil, nex.NewError(nex.ResultCodes.Core.Unknown, err.Error())
}
}
resultMatchmakeSession.StartedTime = resultMatchmakeSession.StartedTime.FromTimestamp(startedTime)
attributesSlice := make([]types.UInt32, len(resultAttribs))
for i, value := range resultAttribs {
attributesSlice[i] = types.NewUInt32(value)
}
resultMatchmakeSession.Attributes = attributesSlice
matchmakeParamBytes := nex.NewByteStreamIn(resultMatchmakeParam, endpoint.LibraryVersions(), endpoint.ByteStreamSettings())
resultMatchmakeSession.MatchmakeParam.ExtractFrom(matchmakeParamBytes)
return &resultMatchmakeSession, nil
}

View file

@ -0,0 +1,360 @@
package database
import (
"fmt"
"math"
"strconv"
"strings"
"time"
"github.com/PretendoNetwork/nex-go/v2"
"github.com/PretendoNetwork/nex-go/v2/types"
common_globals "github.com/PretendoNetwork/nex-protocols-common-go/v2/globals"
"github.com/PretendoNetwork/nex-protocols-go/v2/match-making/constants"
match_making_types "github.com/PretendoNetwork/nex-protocols-go/v2/match-making/types"
pqextended "github.com/PretendoNetwork/pq-extended"
)
// FindMatchmakeSessionBySearchCriteria finds matchmake sessions with the given search criterias
func FindMatchmakeSessionBySearchCriteria(manager *common_globals.MatchmakingManager, connection *nex.PRUDPConnection, searchCriterias []match_making_types.MatchmakeSessionSearchCriteria, resultRange types.ResultRange, sourceMatchmakeSession *match_making_types.MatchmakeSession) ([]match_making_types.MatchmakeSession, *nex.Error) {
resultMatchmakeSessions := make([]match_making_types.MatchmakeSession, 0)
endpoint := connection.Endpoint().(*nex.PRUDPEndPoint)
var friendList []uint32
if manager.GetUserFriendPIDs != nil {
friendList = manager.GetUserFriendPIDs(uint32(connection.PID()))
}
if resultRange.Offset == math.MaxUint32 {
resultRange.Offset = 0
}
for _, searchCriteria := range searchCriterias {
searchStatement := `SELECT
g.id,
g.owner_pid,
g.host_pid,
g.min_participants,
g.max_participants,
g.participation_policy,
g.policy_argument,
g.flags,
g.state,
g.description,
array_length(g.participants, 1),
g.started_time,
ms.game_mode,
ms.attribs,
ms.open_participation,
ms.matchmake_system_type,
ms.application_buffer,
ms.progress_score,
ms.session_key,
ms.option_zero,
ms.matchmake_param,
ms.user_password,
ms.refer_gid,
ms.user_password_enabled,
ms.system_password_enabled,
ms.codeword
FROM matchmaking.gatherings AS g
INNER JOIN matchmaking.matchmake_sessions AS ms ON ms.id = g.id
WHERE
g.registered=true AND
g.type='MatchmakeSession' AND
ms.refer_gid=$1 AND
ms.codeword=$2 AND
array_length(ms.attribs, 1)=$3 AND
(CASE WHEN g.participation_policy=98 THEN g.owner_pid=ANY($4) ELSE true END) AND
(CASE WHEN $5=true THEN ms.open_participation=true ELSE true END) AND
(CASE WHEN $6=true THEN g.host_pid <> 0 ELSE true END) AND
(CASE WHEN $7=true THEN ms.user_password_enabled=false ELSE true END) AND
(CASE WHEN $8=true THEN ms.system_password_enabled=false ELSE true END)`
var valid bool = true
for i, attrib := range searchCriteria.Attribs {
// * Ignore attribute 1 here, reserved for the selection method
if i == 1 {
continue;
}
if attrib != "" {
before, after, found := strings.Cut(string(attrib), ",")
if found {
min, err := strconv.ParseUint(before, 10, 32)
if err != nil {
valid = false
break
}
max, err := strconv.ParseUint(after, 10, 32)
if err != nil {
valid = false
break
}
searchStatement += fmt.Sprintf(` AND ms.attribs[%d] BETWEEN %d AND %d`, i + 1, min, max)
} else {
value, err := strconv.ParseUint(before, 10, 32)
if err != nil {
valid = false
break
}
searchStatement += fmt.Sprintf(` AND ms.attribs[%d]=%d`, i + 1, value)
}
}
}
// * Search criteria is invalid, continue to next one
if !valid {
continue
}
if searchCriteria.MaxParticipants != "" {
before, after, found := strings.Cut(string(searchCriteria.MaxParticipants), ",")
if found {
min, err := strconv.ParseUint(before, 10, 16)
if err != nil {
continue
}
max, err := strconv.ParseUint(after, 10, 16)
if err != nil {
continue
}
searchStatement += fmt.Sprintf(` AND g.max_participants BETWEEN %d AND %d`, min, max)
} else {
value, err := strconv.ParseUint(before, 10, 16)
if err != nil {
continue
}
searchStatement += fmt.Sprintf(` AND g.max_participants=%d`, value)
}
}
if searchCriteria.MinParticipants != "" {
before, after, found := strings.Cut(string(searchCriteria.MinParticipants), ",")
if found {
min, err := strconv.ParseUint(before, 10, 16)
if err != nil {
continue
}
max, err := strconv.ParseUint(after, 10, 16)
if err != nil {
continue
}
searchStatement += fmt.Sprintf(` AND g.min_participants BETWEEN %d AND %d`, min, max)
} else {
value, err := strconv.ParseUint(before, 10, 16)
if err != nil {
continue
}
searchStatement += fmt.Sprintf(` AND g.min_participants=%d`, value)
}
}
if searchCriteria.GameMode != "" {
before, after, found := strings.Cut(string(searchCriteria.GameMode), ",")
if found {
min, err := strconv.ParseUint(before, 10, 32)
if err != nil {
continue
}
max, err := strconv.ParseUint(after, 10, 32)
if err != nil {
continue
}
searchStatement += fmt.Sprintf(` AND ms.game_mode BETWEEN %d AND %d`, min, max)
} else {
value, err := strconv.ParseUint(before, 10, 32)
if err != nil {
continue
}
searchStatement += fmt.Sprintf(` AND ms.game_mode=%d`, value)
}
}
if searchCriteria.MatchmakeSystemType != "" {
before, after, found := strings.Cut(string(searchCriteria.MatchmakeSystemType), ",")
if found {
min, err := strconv.ParseUint(before, 10, 32)
if err != nil {
continue
}
max, err := strconv.ParseUint(after, 10, 32)
if err != nil {
continue
}
searchStatement += fmt.Sprintf(` AND ms.matchmake_system_type BETWEEN %d AND %d`, min, max)
} else {
value, err := strconv.ParseUint(before, 10, 32)
if err != nil {
continue
}
searchStatement += fmt.Sprintf(` AND ms.matchmake_system_type=%d`, value)
}
}
// * Filter full sessions if necessary
if searchCriteria.VacantOnly {
// * Account for the VacantParticipants when searching for sessions (if given)
if searchCriteria.VacantParticipants == 0 {
searchStatement += ` AND array_length(g.participants, 1) + 1 <= g.max_participants`
} else {
searchStatement += fmt.Sprintf(` AND array_length(g.participants, 1) + %d <= g.max_participants`, searchCriteria.VacantParticipants)
}
}
switch constants.SelectionMethod(searchCriteria.SelectionMethod) {
case constants.SelectionMethodRandom:
// * Random global
searchStatement += ` ORDER BY RANDOM()`
case constants.SelectionMethodNearestNeighbor:
// * Closest attribute
attribute1, err := strconv.ParseUint(string(searchCriteria.Attribs[1]), 10, 32)
if err != nil {
common_globals.Logger.Error(err.Error())
continue
}
searchStatement += fmt.Sprintf(` ORDER BY abs(%d - ms.attribs[2])`, attribute1)
case constants.SelectionMethodBroadenRange:
// * Ranked
// TODO - Actually implement ranked matchmaking, using closest attribute at the moment
attribute1, err := strconv.ParseUint(string(searchCriteria.Attribs[1]), 10, 32)
if err != nil {
common_globals.Logger.Error(err.Error())
continue
}
searchStatement += fmt.Sprintf(` ORDER BY abs(%d - ms.attribs[2])`, attribute1)
case constants.SelectionMethodProgressScore:
// * Progress Score
// * We can only use this when doing auto-matchmake
if sourceMatchmakeSession == nil {
continue
}
searchStatement += fmt.Sprintf(` ORDER BY abs(%d - ms.progress_score)`, sourceMatchmakeSession.ProgressScore)
case constants.SelectionMethodBroadenRangeWithProgressScore:
// * Ranked + Progress
// TODO - Actually implement ranked matchmaking, using closest attribute at the moment
// * We can only use this when doing auto-matchmake
if sourceMatchmakeSession == nil {
continue
}
if searchCriteria.Attribs[1] != "" {
attribute1, err := strconv.ParseUint(string(searchCriteria.Attribs[1]), 10, 32)
if err != nil {
common_globals.Logger.Error(err.Error())
continue
}
// TODO - Should the attribute and the progress score actually weigh the same?
searchStatement += fmt.Sprintf(` ORDER BY abs(%d - ms.attribs[2] + %d - ms.progress_score)`, attribute1, sourceMatchmakeSession.ProgressScore)
}
// case constants.SelectionMethodScoreBased: // * According to notes this is related with the MatchmakeParam. TODO - Implement this
}
// * If the ResultRange inside the MatchmakeSessionSearchCriteria is valid (only present on NEX 4.0+), use that
// * Otherwise, use the one given as argument
if searchCriteria.ResultRange.Length != 0 {
searchStatement += fmt.Sprintf(` LIMIT %d OFFSET %d`, uint32(searchCriteria.ResultRange.Length), uint32(searchCriteria.ResultRange.Offset))
} else {
// * Since we use one ResultRange for all searches, limit the total length to the one specified
// * but apply the same offset to all queries
searchStatement += fmt.Sprintf(` LIMIT %d OFFSET %d`, uint32(resultRange.Length) - uint32(len(resultMatchmakeSessions)), uint32(resultRange.Offset))
}
rows, err := manager.Database.Query(searchStatement,
searchCriteria.ReferGID,
searchCriteria.CodeWord,
len(searchCriteria.Attribs),
pqextended.Array(friendList),
searchCriteria.ExcludeLocked,
searchCriteria.ExcludeNonHostPID,
searchCriteria.ExcludeUserPasswordSet,
searchCriteria.ExcludeSystemPasswordSet,
)
if err != nil {
common_globals.Logger.Critical(err.Error())
continue
}
for rows.Next() {
resultMatchmakeSession := match_making_types.NewMatchmakeSession()
var startedTime time.Time
var resultAttribs []uint32
var resultMatchmakeParam []byte
err = rows.Scan(
&resultMatchmakeSession.Gathering.ID,
&resultMatchmakeSession.Gathering.OwnerPID,
&resultMatchmakeSession.Gathering.HostPID,
&resultMatchmakeSession.Gathering.MinimumParticipants,
&resultMatchmakeSession.Gathering.MaximumParticipants,
&resultMatchmakeSession.Gathering.ParticipationPolicy,
&resultMatchmakeSession.Gathering.PolicyArgument,
&resultMatchmakeSession.Gathering.Flags,
&resultMatchmakeSession.Gathering.State,
&resultMatchmakeSession.Gathering.Description,
&resultMatchmakeSession.ParticipationCount,
&startedTime,
&resultMatchmakeSession.GameMode,
pqextended.Array(&resultAttribs),
&resultMatchmakeSession.OpenParticipation,
&resultMatchmakeSession.MatchmakeSystemType,
&resultMatchmakeSession.ApplicationBuffer,
&resultMatchmakeSession.ProgressScore,
&resultMatchmakeSession.SessionKey,
&resultMatchmakeSession.Option,
&resultMatchmakeParam,
&resultMatchmakeSession.UserPassword,
&resultMatchmakeSession.ReferGID,
&resultMatchmakeSession.UserPasswordEnabled,
&resultMatchmakeSession.SystemPasswordEnabled,
&resultMatchmakeSession.CodeWord,
)
if err != nil {
common_globals.Logger.Critical(err.Error())
continue
}
resultMatchmakeSession.StartedTime = resultMatchmakeSession.StartedTime.FromTimestamp(startedTime)
attributesSlice := make([]types.UInt32, len(resultAttribs))
for i, value := range resultAttribs {
attributesSlice[i] = types.NewUInt32(value)
}
resultMatchmakeSession.Attributes = attributesSlice
matchmakeParamBytes := nex.NewByteStreamIn(resultMatchmakeParam, endpoint.LibraryVersions(), endpoint.ByteStreamSettings())
resultMatchmakeSession.MatchmakeParam.ExtractFrom(matchmakeParamBytes)
resultMatchmakeSessions = append(resultMatchmakeSessions, resultMatchmakeSession)
}
rows.Close()
}
return resultMatchmakeSessions, nil
}

View file

@ -0,0 +1,24 @@
package database
import (
"github.com/PretendoNetwork/nex-go/v2"
"github.com/PretendoNetwork/nex-go/v2/types"
common_globals "github.com/PretendoNetwork/nex-protocols-common-go/v2/globals"
)
// GetCreatedPersistentGatherings returns the number of active persistent gatherings that a given PID owns
func GetCreatedPersistentGatherings(manager *common_globals.MatchmakingManager, ownerPID types.PID) (int, *nex.Error) {
var createdPersistentGatherings int
err := manager.Database.QueryRow(`SELECT
COUNT(pg.id)
FROM matchmaking.persistent_gatherings AS pg
INNER JOIN matchmaking.gatherings AS g ON g.id = pg.id
WHERE
g.registered=true AND
g.owner_pid=$1`, ownerPID).Scan(&createdPersistentGatherings)
if err != nil {
return 0, nex.NewError(nex.ResultCodes.Core.Unknown, err.Error())
}
return createdPersistentGatherings, nil
}

View file

@ -0,0 +1,47 @@
package database
import (
"github.com/PretendoNetwork/nex-go/v2"
"github.com/PretendoNetwork/nex-go/v2/types"
common_globals "github.com/PretendoNetwork/nex-protocols-common-go/v2/globals"
match_making_database "github.com/PretendoNetwork/nex-protocols-common-go/v2/match-making/database"
)
// GetDetailedGatheringByID returns a Gathering as an RVType by its gathering ID
func GetDetailedGatheringByID(manager *common_globals.MatchmakingManager, sourcePID uint64, gatheringID uint32) (types.RVType, string, *nex.Error) {
gathering, gatheringType, participants, startedTime, nexError := match_making_database.FindGatheringByID(manager, gatheringID)
if nexError != nil {
return nil, "", nexError
}
if gatheringType == "Gathering" {
return gathering, gatheringType, nil
}
if gatheringType == "MatchmakeSession" {
matchmakeSession, nexError := GetMatchmakeSessionByGathering(manager, manager.Endpoint, gathering, uint32(len(participants)), startedTime)
if nexError != nil {
return nil, "", nexError
}
// * Scrap session key and user password
matchmakeSession.SessionKey = make([]byte, 0)
matchmakeSession.UserPassword = ""
return matchmakeSession, gatheringType, nil
}
if gatheringType == "PersistentGathering" {
persistentGathering, nexError := GetPersistentGatheringByGathering(manager, gathering, sourcePID)
if nexError != nil {
return nil, "", nexError
}
// * Scrap persistent gathering password
persistentGathering.Password = ""
return persistentGathering, gatheringType, nil
}
return nil, "", nex.NewError(nex.ResultCodes.Core.Exception, "change_error")
}

View file

@ -0,0 +1,74 @@
package database
import (
"database/sql"
"github.com/PretendoNetwork/nex-go/v2"
"github.com/PretendoNetwork/nex-go/v2/types"
common_globals "github.com/PretendoNetwork/nex-protocols-common-go/v2/globals"
match_making_types "github.com/PretendoNetwork/nex-protocols-go/v2/match-making/types"
pqextended "github.com/PretendoNetwork/pq-extended"
)
// GetMatchmakeSessionByGathering gets a matchmake session with the given gathering data
func GetMatchmakeSessionByGathering(manager *common_globals.MatchmakingManager, endpoint *nex.PRUDPEndPoint, gathering match_making_types.Gathering, participationCount uint32, startedTime types.DateTime) (match_making_types.MatchmakeSession, *nex.Error) {
resultMatchmakeSession := match_making_types.NewMatchmakeSession()
var resultAttribs []uint32
var resultMatchmakeParam []byte
err := manager.Database.QueryRow(`SELECT
game_mode,
attribs,
open_participation,
matchmake_system_type,
application_buffer,
progress_score,
session_key,
option_zero,
matchmake_param,
user_password,
refer_gid,
user_password_enabled,
system_password_enabled,
codeword
FROM matchmaking.matchmake_sessions WHERE id=$1`,
uint32(gathering.ID),
).Scan(
&resultMatchmakeSession.GameMode,
pqextended.Array(&resultAttribs),
&resultMatchmakeSession.OpenParticipation,
&resultMatchmakeSession.MatchmakeSystemType,
&resultMatchmakeSession.ApplicationBuffer,
&resultMatchmakeSession.ProgressScore,
&resultMatchmakeSession.SessionKey,
&resultMatchmakeSession.Option,
&resultMatchmakeParam,
&resultMatchmakeSession.UserPassword,
&resultMatchmakeSession.ReferGID,
&resultMatchmakeSession.UserPasswordEnabled,
&resultMatchmakeSession.SystemPasswordEnabled,
&resultMatchmakeSession.CodeWord,
)
if err != nil {
if err == sql.ErrNoRows {
return match_making_types.NewMatchmakeSession(), nex.NewError(nex.ResultCodes.RendezVous.SessionVoid, "change_error")
} else {
return match_making_types.NewMatchmakeSession(), nex.NewError(nex.ResultCodes.Core.Unknown, err.Error())
}
}
resultMatchmakeSession.Gathering = gathering
resultMatchmakeSession.ParticipationCount = types.NewUInt32(participationCount)
resultMatchmakeSession.StartedTime = startedTime
attributesSlice := make([]types.UInt32, len(resultAttribs))
for i, value := range resultAttribs {
attributesSlice[i] = types.NewUInt32(value)
}
resultMatchmakeSession.Attributes = attributesSlice
matchmakeParamBytes := nex.NewByteStreamIn(resultMatchmakeParam, endpoint.LibraryVersions(), endpoint.ByteStreamSettings())
resultMatchmakeSession.MatchmakeParam.ExtractFrom(matchmakeParamBytes)
return resultMatchmakeSession, nil
}

View file

@ -0,0 +1,107 @@
package database
import (
"database/sql"
"time"
"github.com/PretendoNetwork/nex-go/v2"
"github.com/PretendoNetwork/nex-go/v2/types"
common_globals "github.com/PretendoNetwork/nex-protocols-common-go/v2/globals"
match_making_types "github.com/PretendoNetwork/nex-protocols-go/v2/match-making/types"
pqextended "github.com/PretendoNetwork/pq-extended"
)
// GetMatchmakeSessionByID gets a matchmake session with the given gathering ID and the system password
func GetMatchmakeSessionByID(manager *common_globals.MatchmakingManager, endpoint *nex.PRUDPEndPoint, gatheringID uint32) (match_making_types.MatchmakeSession, string, *nex.Error) {
resultMatchmakeSession := match_making_types.NewMatchmakeSession()
var startedTime time.Time
var resultAttribs []uint32
var resultMatchmakeParam []byte
var systemPassword string
// * For simplicity, we will only compare the values that exist on a MatchmakeSessionSearchCriteria
err := manager.Database.QueryRow(`SELECT
g.id,
g.owner_pid,
g.host_pid,
g.min_participants,
g.max_participants,
g.participation_policy,
g.policy_argument,
g.flags,
g.state,
g.description,
array_length(g.participants, 1),
g.started_time,
ms.game_mode,
ms.attribs,
ms.open_participation,
ms.matchmake_system_type,
ms.application_buffer,
ms.progress_score,
ms.session_key,
ms.option_zero,
ms.matchmake_param,
ms.user_password,
ms.refer_gid,
ms.user_password_enabled,
ms.system_password_enabled,
ms.codeword,
ms.system_password
FROM matchmaking.gatherings AS g
INNER JOIN matchmaking.matchmake_sessions AS ms ON ms.id = g.id
WHERE
g.registered=true AND
g.type='MatchmakeSession' AND
g.id=$1`,
gatheringID,
).Scan(
&resultMatchmakeSession.Gathering.ID,
&resultMatchmakeSession.Gathering.OwnerPID,
&resultMatchmakeSession.Gathering.HostPID,
&resultMatchmakeSession.Gathering.MinimumParticipants,
&resultMatchmakeSession.Gathering.MaximumParticipants,
&resultMatchmakeSession.Gathering.ParticipationPolicy,
&resultMatchmakeSession.Gathering.PolicyArgument,
&resultMatchmakeSession.Gathering.Flags,
&resultMatchmakeSession.Gathering.State,
&resultMatchmakeSession.Gathering.Description,
&resultMatchmakeSession.ParticipationCount,
&startedTime,
&resultMatchmakeSession.GameMode,
pqextended.Array(&resultAttribs),
&resultMatchmakeSession.OpenParticipation,
&resultMatchmakeSession.MatchmakeSystemType,
&resultMatchmakeSession.ApplicationBuffer,
&resultMatchmakeSession.ProgressScore,
&resultMatchmakeSession.SessionKey,
&resultMatchmakeSession.Option,
&resultMatchmakeParam,
&resultMatchmakeSession.UserPassword,
&resultMatchmakeSession.ReferGID,
&resultMatchmakeSession.UserPasswordEnabled,
&resultMatchmakeSession.SystemPasswordEnabled,
&resultMatchmakeSession.CodeWord,
&systemPassword,
)
if err != nil {
if err == sql.ErrNoRows {
return match_making_types.NewMatchmakeSession(), "", nex.NewError(nex.ResultCodes.RendezVous.SessionVoid, "change_error")
} else {
return match_making_types.NewMatchmakeSession(), "", nex.NewError(nex.ResultCodes.Core.Unknown, err.Error())
}
}
resultMatchmakeSession.StartedTime = resultMatchmakeSession.StartedTime.FromTimestamp(startedTime)
attributesSlice := make([]types.UInt32, len(resultAttribs))
for i, value := range resultAttribs {
attributesSlice[i] = types.NewUInt32(value)
}
resultMatchmakeSession.Attributes = attributesSlice
matchmakeParamBytes := nex.NewByteStreamIn(resultMatchmakeParam, endpoint.LibraryVersions(), endpoint.ByteStreamSettings())
resultMatchmakeSession.MatchmakeParam.ExtractFrom(matchmakeParamBytes)
return resultMatchmakeSession, systemPassword, nil
}

View file

@ -0,0 +1,60 @@
package database
import (
"github.com/PretendoNetwork/nex-go/v2"
"github.com/PretendoNetwork/nex-go/v2/types"
common_globals "github.com/PretendoNetwork/nex-protocols-common-go/v2/globals"
notifications_types "github.com/PretendoNetwork/nex-protocols-go/v2/notifications/types"
pqextended "github.com/PretendoNetwork/pq-extended"
)
// GetNotificationDatas gets the notification datas that belong to friends of the user and match with any of the given types
func GetNotificationDatas(manager *common_globals.MatchmakingManager, sourcePID types.PID, notificationTypes []uint32) ([]notifications_types.NotificationEvent, *nex.Error) {
dataList := make([]notifications_types.NotificationEvent, 0)
var friendList []uint32
if manager.GetUserFriendPIDs != nil {
friendList = manager.GetUserFriendPIDs(uint32(sourcePID))
} else {
common_globals.Logger.Warning("GetNotificationDatas missing manager.GetUserFriendPIDs!")
}
// * No friends to check
if len(friendList) == 0 {
return dataList, nil
}
rows, err := manager.Database.Query(`SELECT
source_pid,
type,
param_1,
param_2,
param_str
FROM matchmaking.notifications WHERE active=true AND source_pid=ANY($1) AND type=ANY($2)
`, pqextended.Array(friendList), pqextended.Array(notificationTypes))
if err != nil {
return nil, nex.NewError(nex.ResultCodes.Core.Unknown, err.Error())
}
for rows.Next() {
notificationData := notifications_types.NewNotificationEvent()
err = rows.Scan(
&notificationData.PIDSource,
&notificationData.Type,
&notificationData.Param1,
&notificationData.Param2,
&notificationData.StrParam,
)
if err != nil {
common_globals.Logger.Critical(err.Error())
continue
}
dataList = append(dataList, notificationData)
}
rows.Close()
return dataList, nil
}

View file

@ -0,0 +1,114 @@
package database
import (
"time"
"github.com/PretendoNetwork/nex-go/v2"
"github.com/PretendoNetwork/nex-go/v2/types"
common_globals "github.com/PretendoNetwork/nex-protocols-common-go/v2/globals"
match_making_types "github.com/PretendoNetwork/nex-protocols-go/v2/match-making/types"
pqextended "github.com/PretendoNetwork/pq-extended"
)
// GetOfficialCommunities returns the official communities based on the given parameters
func GetOfficialCommunities(manager *common_globals.MatchmakingManager, sourcePID types.PID, isAvailableOnly bool, resultRange types.ResultRange) ([]match_making_types.PersistentGathering, *nex.Error) {
persistentGatherings := make([]match_making_types.PersistentGathering, 0)
currentTime := time.Now().UTC()
timeNever := types.NewDateTime(0).Standard()
rows, err := manager.Database.Query(`SELECT
g.id,
g.owner_pid,
g.host_pid,
g.min_participants,
g.max_participants,
g.participation_policy,
g.policy_argument,
g.flags,
g.state,
g.description,
pg.community_type,
pg.password,
pg.attribs,
pg.application_buffer,
pg.participation_start_date,
pg.participation_end_date,
(SELECT COUNT(ms.id)
FROM matchmaking.matchmake_sessions AS ms
INNER JOIN matchmaking.gatherings AS gms ON ms.id = gms.id
WHERE gms.registered=true
AND ms.matchmake_system_type=5 -- matchmake_system_type=5 is only used in matchmake sessions attached to a persistent gathering
AND ms.attribs[1]=g.id) AS matchmake_session_count,
COALESCE((SELECT cp.participation_count
FROM matchmaking.community_participations AS cp
WHERE cp.user_pid=$5
AND cp.gathering_id=g.id), 0) AS participation_count
FROM matchmaking.gatherings AS g
INNER JOIN matchmaking.persistent_gatherings AS pg ON g.id = pg.id
WHERE
g.registered=true AND
g.type='PersistentGathering' AND
pg.community_type=2 AND
(CASE WHEN $1 THEN
(CASE WHEN pg.participation_start_date <> $6 THEN $2 >= pg.participation_start_date ELSE true END)
AND
(CASE WHEN pg.participation_end_date <> $6 THEN $2 <= pg.participation_end_date ELSE true END)
ELSE true END)
LIMIT $3 OFFSET $4`,
isAvailableOnly,
currentTime,
uint32(resultRange.Length),
uint32(resultRange.Offset),
sourcePID,
timeNever,
)
if err != nil {
return nil, nex.NewError(nex.ResultCodes.Core.Unknown, err.Error())
}
for rows.Next() {
resultPersistentGathering := match_making_types.NewPersistentGathering()
var resultAttribs []uint32
var resultParticipationStartDate time.Time
var resultParticipationEndDate time.Time
err = rows.Scan(
&resultPersistentGathering.Gathering.ID,
&resultPersistentGathering.Gathering.OwnerPID,
&resultPersistentGathering.Gathering.HostPID,
&resultPersistentGathering.Gathering.MinimumParticipants,
&resultPersistentGathering.Gathering.MaximumParticipants,
&resultPersistentGathering.Gathering.ParticipationPolicy,
&resultPersistentGathering.Gathering.PolicyArgument,
&resultPersistentGathering.Gathering.Flags,
&resultPersistentGathering.Gathering.State,
&resultPersistentGathering.Gathering.Description,
&resultPersistentGathering.CommunityType,
&resultPersistentGathering.Password,
pqextended.Array(&resultAttribs),
&resultPersistentGathering.ApplicationBuffer,
&resultParticipationStartDate,
&resultParticipationEndDate,
&resultPersistentGathering.MatchmakeSessionCount,
&resultPersistentGathering.ParticipationCount,
)
if err != nil {
common_globals.Logger.Critical(err.Error())
continue
}
attributesSlice := make([]types.UInt32, len(resultAttribs))
for i, value := range resultAttribs {
attributesSlice[i] = types.NewUInt32(value)
}
resultPersistentGathering.Attribs = attributesSlice
resultPersistentGathering.ParticipationStartDate = resultPersistentGathering.ParticipationStartDate.FromTimestamp(resultParticipationStartDate)
resultPersistentGathering.ParticipationEndDate = resultPersistentGathering.ParticipationEndDate.FromTimestamp(resultParticipationEndDate)
persistentGatherings = append(persistentGatherings, resultPersistentGathering)
}
rows.Close()
return persistentGatherings, nil
}

View file

@ -0,0 +1,72 @@
package database
import (
"database/sql"
"time"
"github.com/PretendoNetwork/nex-go/v2"
"github.com/PretendoNetwork/nex-go/v2/types"
common_globals "github.com/PretendoNetwork/nex-protocols-common-go/v2/globals"
match_making_types "github.com/PretendoNetwork/nex-protocols-go/v2/match-making/types"
pqextended "github.com/PretendoNetwork/pq-extended"
)
// GetPersistentGatheringByGathering gets a persistent gathering with the given gathering data
func GetPersistentGatheringByGathering(manager *common_globals.MatchmakingManager, gathering match_making_types.Gathering, sourcePID uint64) (match_making_types.PersistentGathering, *nex.Error) {
resultPersistentGathering := match_making_types.NewPersistentGathering()
var resultAttribs []uint32
var resultParticipationStartDate time.Time
var resultParticipationEndDate time.Time
err := manager.Database.QueryRow(`SELECT
community_type,
password,
attribs,
application_buffer,
participation_start_date,
participation_end_date,
(SELECT COUNT(ms.id)
FROM matchmaking.matchmake_sessions AS ms
INNER JOIN matchmaking.gatherings AS gms ON ms.id = gms.id
WHERE gms.registered=true
AND ms.matchmake_system_type=5 -- matchmake_system_type=5 is only used in matchmake sessions attached to a persistent gathering
AND ms.attribs[1]=g.id) AS matchmake_session_count,
COALESCE((SELECT cp.participation_count
FROM matchmaking.community_participations AS cp
WHERE cp.user_pid=$2
AND cp.gathering_id=g.id), 0) AS participation_count
FROM matchmaking.persistent_gatherings
WHERE id=$1`,
gathering.ID,
sourcePID,
).Scan(
&resultPersistentGathering.CommunityType,
&resultPersistentGathering.Password,
pqextended.Array(&resultAttribs),
&resultPersistentGathering.ApplicationBuffer,
&resultParticipationStartDate,
&resultParticipationEndDate,
&resultPersistentGathering.MatchmakeSessionCount,
&resultPersistentGathering.ParticipationCount,
)
if err != nil {
if err == sql.ErrNoRows {
return match_making_types.NewPersistentGathering(), nex.NewError(nex.ResultCodes.RendezVous.InvalidGID, "change_error")
} else {
return match_making_types.NewPersistentGathering(), nex.NewError(nex.ResultCodes.Core.Unknown, err.Error())
}
}
resultPersistentGathering.Gathering = gathering
attributesSlice := make([]types.UInt32, len(resultAttribs))
for i, value := range resultAttribs {
attributesSlice[i] = types.NewUInt32(value)
}
resultPersistentGathering.Attribs = attributesSlice
resultPersistentGathering.ParticipationStartDate = resultPersistentGathering.ParticipationStartDate.FromTimestamp(resultParticipationStartDate)
resultPersistentGathering.ParticipationEndDate = resultPersistentGathering.ParticipationEndDate.FromTimestamp(resultParticipationEndDate)
return resultPersistentGathering, nil
}

View file

@ -0,0 +1,89 @@
package database
import (
"time"
"github.com/PretendoNetwork/nex-go/v2"
"github.com/PretendoNetwork/nex-go/v2/types"
common_globals "github.com/PretendoNetwork/nex-protocols-common-go/v2/globals"
match_making_types "github.com/PretendoNetwork/nex-protocols-go/v2/match-making/types"
pqextended "github.com/PretendoNetwork/pq-extended"
)
// GetPersistentGatheringByID gets the persistent gatherings from the given gathering IDs
func GetPersistentGatheringByID(manager *common_globals.MatchmakingManager, sourcePID types.PID, gatheringID uint32) (match_making_types.PersistentGathering, *nex.Error) {
resultPersistentGathering := match_making_types.NewPersistentGathering()
var resultAttribs []uint32
var resultParticipationStartDate time.Time
var resultParticipationEndDate time.Time
err := manager.Database.QueryRow(`SELECT
g.id,
g.owner_pid,
g.host_pid,
g.min_participants,
g.max_participants,
g.participation_policy,
g.policy_argument,
g.flags,
g.state,
g.description,
pg.community_type,
pg.password,
pg.attribs,
pg.application_buffer,
pg.participation_start_date,
pg.participation_end_date,
(SELECT COUNT(ms.id)
FROM matchmaking.matchmake_sessions AS ms
INNER JOIN matchmaking.gatherings AS gms ON ms.id = gms.id
WHERE gms.registered=true
AND ms.matchmake_system_type=5 -- matchmake_system_type=5 is only used in matchmake sessions attached to a persistent gathering
AND ms.attribs[1]=g.id) AS matchmake_session_count,
COALESCE((SELECT cp.participation_count
FROM matchmaking.community_participations AS cp
WHERE cp.user_pid=$2
AND cp.gathering_id=g.id), 0) AS participation_count
FROM matchmaking.gatherings AS g
INNER JOIN matchmaking.persistent_gatherings AS pg ON g.id = pg.id
WHERE
g.registered=true AND
g.type='PersistentGathering' AND
g.id=$1`,
gatheringID,
sourcePID,
).Scan(
&resultPersistentGathering.Gathering.ID,
&resultPersistentGathering.Gathering.OwnerPID,
&resultPersistentGathering.Gathering.HostPID,
&resultPersistentGathering.Gathering.MinimumParticipants,
&resultPersistentGathering.Gathering.MaximumParticipants,
&resultPersistentGathering.Gathering.ParticipationPolicy,
&resultPersistentGathering.Gathering.PolicyArgument,
&resultPersistentGathering.Gathering.Flags,
&resultPersistentGathering.Gathering.State,
&resultPersistentGathering.Gathering.Description,
&resultPersistentGathering.CommunityType,
&resultPersistentGathering.Password,
pqextended.Array(&resultAttribs),
&resultPersistentGathering.ApplicationBuffer,
&resultParticipationStartDate,
&resultParticipationEndDate,
&resultPersistentGathering.MatchmakeSessionCount,
&resultPersistentGathering.ParticipationCount,
)
if err != nil {
return match_making_types.NewPersistentGathering(), nil
}
attributesSlice := make([]types.UInt32, len(resultAttribs))
for i, value := range resultAttribs {
attributesSlice[i] = types.NewUInt32(value)
}
resultPersistentGathering.Attribs = attributesSlice
resultPersistentGathering.ParticipationStartDate = resultPersistentGathering.ParticipationStartDate.FromTimestamp(resultParticipationStartDate)
resultPersistentGathering.ParticipationEndDate = resultPersistentGathering.ParticipationEndDate.FromTimestamp(resultParticipationEndDate)
return resultPersistentGathering, nil
}

View file

@ -0,0 +1,102 @@
package database
import (
"time"
"github.com/PretendoNetwork/nex-go/v2"
"github.com/PretendoNetwork/nex-go/v2/types"
common_globals "github.com/PretendoNetwork/nex-protocols-common-go/v2/globals"
match_making_types "github.com/PretendoNetwork/nex-protocols-go/v2/match-making/types"
pqextended "github.com/PretendoNetwork/pq-extended"
)
// GetPersistentGatheringsByID gets the persistent gatherings from the given gathering IDs
func GetPersistentGatheringsByID(manager *common_globals.MatchmakingManager, sourcePID types.PID, gatheringIDs []uint32) ([]match_making_types.PersistentGathering, *nex.Error) {
persistentGatherings := make([]match_making_types.PersistentGathering, 0)
rows, err := manager.Database.Query(`SELECT
g.id,
g.owner_pid,
g.host_pid,
g.min_participants,
g.max_participants,
g.participation_policy,
g.policy_argument,
g.flags,
g.state,
g.description,
pg.community_type,
pg.password,
pg.attribs,
pg.application_buffer,
pg.participation_start_date,
pg.participation_end_date,
(SELECT COUNT(ms.id)
FROM matchmaking.matchmake_sessions AS ms
INNER JOIN matchmaking.gatherings AS gms ON ms.id = gms.id
WHERE gms.registered=true
AND ms.matchmake_system_type=5 -- matchmake_system_type=5 is only used in matchmake sessions attached to a persistent gathering
AND ms.attribs[1]=g.id) AS matchmake_session_count,
COALESCE((SELECT cp.participation_count
FROM matchmaking.community_participations AS cp
WHERE cp.user_pid=$2
AND cp.gathering_id=g.id), 0) AS participation_count
FROM matchmaking.gatherings AS g
INNER JOIN matchmaking.persistent_gatherings AS pg ON g.id = pg.id
WHERE
g.registered=true AND
g.type='PersistentGathering' AND
g.id=ANY($1)`,
pqextended.Array(gatheringIDs),
sourcePID,
)
if err != nil {
return nil, nex.NewError(nex.ResultCodes.Core.Unknown, err.Error())
}
for rows.Next() {
resultPersistentGathering := match_making_types.NewPersistentGathering()
var resultAttribs []uint32
var resultParticipationStartDate time.Time
var resultParticipationEndDate time.Time
err = rows.Scan(
&resultPersistentGathering.Gathering.ID,
&resultPersistentGathering.Gathering.OwnerPID,
&resultPersistentGathering.Gathering.HostPID,
&resultPersistentGathering.Gathering.MinimumParticipants,
&resultPersistentGathering.Gathering.MaximumParticipants,
&resultPersistentGathering.Gathering.ParticipationPolicy,
&resultPersistentGathering.Gathering.PolicyArgument,
&resultPersistentGathering.Gathering.Flags,
&resultPersistentGathering.Gathering.State,
&resultPersistentGathering.Gathering.Description,
&resultPersistentGathering.CommunityType,
&resultPersistentGathering.Password,
pqextended.Array(&resultAttribs),
&resultPersistentGathering.ApplicationBuffer,
&resultParticipationStartDate,
&resultParticipationEndDate,
&resultPersistentGathering.MatchmakeSessionCount,
&resultPersistentGathering.ParticipationCount,
)
if err != nil {
common_globals.Logger.Critical(err.Error())
continue
}
attributesSlice := make([]types.UInt32, len(resultAttribs))
for i, value := range resultAttribs {
attributesSlice[i] = types.NewUInt32(value)
}
resultPersistentGathering.Attribs = attributesSlice
resultPersistentGathering.ParticipationStartDate = resultPersistentGathering.ParticipationStartDate.FromTimestamp(resultParticipationStartDate)
resultPersistentGathering.ParticipationEndDate = resultPersistentGathering.ParticipationEndDate.FromTimestamp(resultParticipationEndDate)
persistentGatherings = append(persistentGatherings, resultPersistentGathering)
}
rows.Close()
return persistentGatherings, nil
}

View file

@ -0,0 +1,105 @@
package database
import (
"time"
"github.com/PretendoNetwork/nex-go/v2"
"github.com/PretendoNetwork/nex-go/v2/types"
common_globals "github.com/PretendoNetwork/nex-protocols-common-go/v2/globals"
match_making_types "github.com/PretendoNetwork/nex-protocols-go/v2/match-making/types"
pqextended "github.com/PretendoNetwork/pq-extended"
)
// GetPersistentGatheringsByParticipant finds the active persistent gatherings that a user is participating on
func GetPersistentGatheringsByParticipant(manager *common_globals.MatchmakingManager, sourcePID types.PID, participant types.PID, resultRange types.ResultRange) ([]match_making_types.PersistentGathering, *nex.Error) {
persistentGatherings := make([]match_making_types.PersistentGathering, 0)
rows, err := manager.Database.Query(`SELECT
g.id,
g.owner_pid,
g.host_pid,
g.min_participants,
g.max_participants,
g.participation_policy,
g.policy_argument,
g.flags,
g.state,
g.description,
pg.community_type,
pg.password,
pg.attribs,
pg.application_buffer,
pg.participation_start_date,
pg.participation_end_date,
(SELECT COUNT(ms.id)
FROM matchmaking.matchmake_sessions AS ms
INNER JOIN matchmaking.gatherings AS gms ON ms.id = gms.id
WHERE gms.registered=true
AND ms.matchmake_system_type=5 -- matchmake_system_type=5 is only used in matchmake sessions attached to a persistent gathering
AND ms.attribs[1]=g.id) AS matchmake_session_count,
COALESCE((SELECT cp.participation_count
FROM matchmaking.community_participations AS cp
WHERE cp.user_pid=$4
AND cp.gathering_id=g.id), 0) AS participation_count
FROM matchmaking.gatherings AS g
INNER JOIN matchmaking.persistent_gatherings AS pg ON g.id = pg.id
WHERE
g.registered=true AND
g.type='PersistentGathering' AND
$1=ANY(g.participants)
LIMIT $2 OFFSET $3`,
participant,
resultRange.Length,
resultRange.Offset,
sourcePID,
)
if err != nil {
return nil, nex.NewError(nex.ResultCodes.Core.Unknown, err.Error())
}
for rows.Next() {
resultPersistentGathering := match_making_types.NewPersistentGathering()
var resultAttribs []uint32
var resultParticipationStartDate time.Time
var resultParticipationEndDate time.Time
err = rows.Scan(
&resultPersistentGathering.Gathering.ID,
&resultPersistentGathering.Gathering.OwnerPID,
&resultPersistentGathering.Gathering.HostPID,
&resultPersistentGathering.Gathering.MinimumParticipants,
&resultPersistentGathering.Gathering.MaximumParticipants,
&resultPersistentGathering.Gathering.ParticipationPolicy,
&resultPersistentGathering.Gathering.PolicyArgument,
&resultPersistentGathering.Gathering.Flags,
&resultPersistentGathering.Gathering.State,
&resultPersistentGathering.Gathering.Description,
&resultPersistentGathering.CommunityType,
&resultPersistentGathering.Password,
pqextended.Array(&resultAttribs),
&resultPersistentGathering.ApplicationBuffer,
&resultParticipationStartDate,
&resultParticipationEndDate,
&resultPersistentGathering.MatchmakeSessionCount,
&resultPersistentGathering.ParticipationCount,
)
if err != nil {
common_globals.Logger.Critical(err.Error())
continue
}
attributesSlice := make([]types.UInt32, len(resultAttribs))
for i, value := range resultAttribs {
attributesSlice[i] = types.NewUInt32(value)
}
resultPersistentGathering.Attribs = attributesSlice
resultPersistentGathering.ParticipationStartDate = resultPersistentGathering.ParticipationStartDate.FromTimestamp(resultParticipationStartDate)
resultPersistentGathering.ParticipationEndDate = resultPersistentGathering.ParticipationEndDate.FromTimestamp(resultParticipationEndDate)
persistentGatherings = append(persistentGatherings, resultPersistentGathering)
}
rows.Close()
return persistentGatherings, nil
}

View file

@ -0,0 +1,52 @@
package database
import (
"github.com/PretendoNetwork/nex-go/v2"
common_globals "github.com/PretendoNetwork/nex-protocols-common-go/v2/globals"
match_making_types "github.com/PretendoNetwork/nex-protocols-go/v2/match-making/types"
pqextended "github.com/PretendoNetwork/pq-extended"
)
// GetSimpleCommunities returns a slice of SimpleCommunity using information from the given gathering IDs
func GetSimpleCommunities(manager *common_globals.MatchmakingManager, gatheringIDList []uint32) ([]match_making_types.SimpleCommunity, *nex.Error) {
simpleCommunities := make([]match_making_types.SimpleCommunity, 0)
rows, err := manager.Database.Query(`SELECT
pg.id,
(SELECT COUNT(ms.id)
FROM matchmaking.matchmake_sessions AS ms
INNER JOIN matchmaking.gatherings AS gms ON ms.id = gms.id
WHERE gms.registered=true
AND ms.matchmake_system_type=5 -- matchmake_system_type=5 is only used in matchmake sessions attached to a persistent gathering
AND ms.attribs[1]=g.id) AS matchmake_session_count
FROM matchmaking.persistent_gatherings AS pg
INNER JOIN matchmaking.gatherings AS g ON g.id = pg.id
WHERE
g.registered=true AND
g.type='PersistentGathering' AND
pg.id=ANY($1)`,
pqextended.Array(gatheringIDList),
)
if err != nil {
return nil, nex.NewError(nex.ResultCodes.Core.Unknown, err.Error())
}
for rows.Next() {
resultSimpleCommunity := match_making_types.NewSimpleCommunity()
err = rows.Scan(
&resultSimpleCommunity.GatheringID,
&resultSimpleCommunity.MatchmakeSessionCount,
)
if err != nil {
common_globals.Logger.Critical(err.Error())
continue
}
simpleCommunities = append(simpleCommunities, resultSimpleCommunity)
}
rows.Close()
return simpleCommunities, nil
}

View file

@ -0,0 +1,44 @@
package database
import (
"database/sql"
"github.com/PretendoNetwork/nex-go/v2"
"github.com/PretendoNetwork/nex-go/v2/types"
common_globals "github.com/PretendoNetwork/nex-protocols-common-go/v2/globals"
match_making_types "github.com/PretendoNetwork/nex-protocols-go/v2/match-making/types"
)
// GetSimplePlayingSession returns the simple playing sessions of the given PIDs
func GetSimplePlayingSession(manager *common_globals.MatchmakingManager, listPID []types.PID) ([]match_making_types.SimplePlayingSession, *nex.Error) {
simplePlayingSessions := make([]match_making_types.SimplePlayingSession, 0)
for _, pid := range listPID {
simplePlayingSession := match_making_types.NewSimplePlayingSession()
err := manager.Database.QueryRow(`SELECT
g.id,
ms.attribs[1],
ms.game_mode
FROM matchmaking.gatherings AS g
INNER JOIN matchmaking.matchmake_sessions AS ms ON ms.id = g.id
WHERE
g.registered=true AND
g.type='MatchmakeSession' AND
$1=ANY(g.participants)`, pid).Scan(
&simplePlayingSession.GatheringID,
&simplePlayingSession.Attribute0,
&simplePlayingSession.GameMode)
if err != nil {
if err != sql.ErrNoRows {
common_globals.Logger.Critical(err.Error())
}
continue
}
simplePlayingSession.PrincipalID = pid
simplePlayingSessions = append(simplePlayingSessions, simplePlayingSession)
}
return simplePlayingSessions, nil
}

View file

@ -0,0 +1,18 @@
package database
import (
"github.com/PretendoNetwork/nex-go/v2"
"github.com/PretendoNetwork/nex-go/v2/types"
common_globals "github.com/PretendoNetwork/nex-protocols-common-go/v2/globals"
)
// InactivateNotificationDatas marks the notifications of a given user as inactive
func InactivateNotificationDatas(manager *common_globals.MatchmakingManager, sourcePID types.PID) *nex.Error {
_, err := manager.Database.Exec(`UPDATE matchmaking.notifications SET active=false WHERE source_pid=$1`, sourcePID)
if err != nil {
common_globals.Logger.Error(err.Error())
return nex.NewError(nex.ResultCodes.Core.Unknown, err.Error())
}
return nil
}

View file

@ -0,0 +1,42 @@
package database
import (
"github.com/PretendoNetwork/nex-go/v2"
common_globals "github.com/PretendoNetwork/nex-protocols-common-go/v2/globals"
match_making_database "github.com/PretendoNetwork/nex-protocols-common-go/v2/match-making/database"
"github.com/PretendoNetwork/nex-protocols-common-go/v2/matchmake-extension/tracking"
"github.com/PretendoNetwork/nex-protocols-go/v2/match-making/constants"
match_making_types "github.com/PretendoNetwork/nex-protocols-go/v2/match-making/types"
)
// JoinMatchmakeSession joins participants from the same connection into a MatchmakeSession. Returns the new number of participants
func JoinMatchmakeSession(manager *common_globals.MatchmakingManager, matchmakeSession match_making_types.MatchmakeSession, connection *nex.PRUDPConnection, vacantParticipants uint16, joinMessage string) (uint32, *nex.Error) {
newParticipants, nexError := match_making_database.JoinGathering(manager, uint32(matchmakeSession.ID), connection, vacantParticipants, joinMessage)
if nexError != nil {
return 0, nexError
}
// TODO - Should we return the error in these cases?
if uint32(matchmakeSession.MatchmakeSystemType) == uint32(constants.MatchmakeSystemTypePersistentGathering) { // * Attached to a persistent gathering
persistentGatheringID := uint32(matchmakeSession.Attributes[0])
_, nexError = GetPersistentGatheringByID(manager, connection.PID(), persistentGatheringID)
if nexError != nil {
common_globals.Logger.Error(nexError.Error())
return newParticipants, nil
}
participationCount, nexError := UpdatePersistentGatheringParticipationCount(manager, connection.PID(), persistentGatheringID)
if nexError != nil {
common_globals.Logger.Error(nexError.Error())
return newParticipants, nil
}
nexError = tracking.LogParticipateCommunity(manager.Database, connection.PID(), persistentGatheringID, uint32(matchmakeSession.ID), participationCount)
if nexError != nil {
common_globals.Logger.Error(nexError.Error())
return newParticipants, nil
}
}
return newParticipants, nil
}

View file

@ -0,0 +1,47 @@
package database
import (
"github.com/PretendoNetwork/nex-go/v2"
"github.com/PretendoNetwork/nex-go/v2/types"
common_globals "github.com/PretendoNetwork/nex-protocols-common-go/v2/globals"
match_making_database "github.com/PretendoNetwork/nex-protocols-common-go/v2/match-making/database"
"github.com/PretendoNetwork/nex-protocols-common-go/v2/matchmake-extension/tracking"
"github.com/PretendoNetwork/nex-protocols-go/v2/match-making/constants"
match_making_types "github.com/PretendoNetwork/nex-protocols-go/v2/match-making/types"
)
// JoinMatchmakeSessionWithParticipants joins participants into a gathering. Returns the new number of participants
func JoinMatchmakeSessionWithParticipants(manager *common_globals.MatchmakingManager, matchmakeSession match_making_types.MatchmakeSession, connection *nex.PRUDPConnection, additionalParticipants []types.PID, joinMessage string, joinMatchmakeSessionBehavior constants.JoinMatchmakeSessionBehavior) (uint32, *nex.Error) {
newParticipants, nexError := match_making_database.JoinGatheringWithParticipants(manager, uint32(matchmakeSession.ID), connection, additionalParticipants, joinMessage, joinMatchmakeSessionBehavior)
if nexError != nil {
return 0, nexError
}
// TODO - Should we return the error in these cases?
if uint32(matchmakeSession.MatchmakeSystemType) == uint32(constants.MatchmakeSystemTypePersistentGathering) { // * Attached to a persistent gathering
persistentGatheringID := uint32(matchmakeSession.Attributes[0])
participantList := append(additionalParticipants, connection.PID())
for _, participant := range participantList {
_, nexError = GetPersistentGatheringByID(manager, participant, persistentGatheringID)
if nexError != nil {
common_globals.Logger.Error(nexError.Error())
continue
}
participationCount, nexError := UpdatePersistentGatheringParticipationCount(manager, participant, persistentGatheringID)
if nexError != nil {
common_globals.Logger.Error(nexError.Error())
continue
}
nexError = tracking.LogParticipateCommunity(manager.Database, participant, persistentGatheringID, uint32(matchmakeSession.ID), participationCount)
if nexError != nil {
common_globals.Logger.Error(nexError.Error())
continue
}
}
}
return newParticipants, nil
}

View file

@ -0,0 +1,26 @@
package database
import (
"github.com/PretendoNetwork/nex-go/v2"
"github.com/PretendoNetwork/nex-go/v2/types"
common_globals "github.com/PretendoNetwork/nex-protocols-common-go/v2/globals"
)
// UpdateApplicationBuffer updates the application buffer of a matchmake session
func UpdateApplicationBuffer(manager *common_globals.MatchmakingManager, gatheringID uint32, applicationBuffer types.Buffer) *nex.Error {
result, err := manager.Database.Exec(`UPDATE matchmaking.matchmake_sessions SET application_buffer=$1 WHERE id=$2`, []byte(applicationBuffer), gatheringID)
if err != nil {
return nex.NewError(nex.ResultCodes.Core.Unknown, err.Error())
}
rowsAffected, err := result.RowsAffected()
if err != nil {
return nex.NewError(nex.ResultCodes.Core.Unknown, err.Error())
}
if rowsAffected == 0 {
return nex.NewError(nex.ResultCodes.RendezVous.SessionVoid, "change_error")
}
return nil
}

View file

@ -0,0 +1,25 @@
package database
import (
"github.com/PretendoNetwork/nex-go/v2"
common_globals "github.com/PretendoNetwork/nex-protocols-common-go/v2/globals"
)
// UpdateGameAttribute updates an attribute on a matchmake session
func UpdateGameAttribute(manager *common_globals.MatchmakingManager, gatheringID uint32, attributeIndex uint32, newValue uint32) *nex.Error {
result, err := manager.Database.Exec(`UPDATE matchmaking.matchmake_sessions SET attribs[$1]=$2 WHERE id=$3`, attributeIndex + 1, newValue, gatheringID)
if err != nil {
return nex.NewError(nex.ResultCodes.Core.Unknown, err.Error())
}
rowsAffected, err := result.RowsAffected()
if err != nil {
return nex.NewError(nex.ResultCodes.Core.Unknown, err.Error())
}
if rowsAffected == 0 {
return nex.NewError(nex.ResultCodes.RendezVous.SessionVoid, "change_error")
}
return nil
}

View file

@ -0,0 +1,36 @@
package database
import (
"github.com/PretendoNetwork/nex-go/v2"
notifications_types "github.com/PretendoNetwork/nex-protocols-go/v2/notifications/types"
common_globals "github.com/PretendoNetwork/nex-protocols-common-go/v2/globals"
)
// UpdateNotificationData updates the notification data of the specified user and type
func UpdateNotificationData(manager *common_globals.MatchmakingManager, notificationData notifications_types.NotificationEvent) *nex.Error {
_, err := manager.Database.Exec(`INSERT INTO matchmaking.notifications AS n (
source_pid,
type,
param_1,
param_2,
param_str
) VALUES (
$1,
$2,
$3,
$4,
$5
) ON CONFLICT (source_pid, type) DO UPDATE SET
param_1=$3, param_2=$4, param_str=$5, active=true WHERE n.source_pid=$1 AND n.type=$2`,
notificationData.PIDSource,
notificationData.Type,
notificationData.Param1,
notificationData.Param2,
notificationData.StrParam,
)
if err != nil {
return nex.NewError(nex.ResultCodes.Core.Unknown, err.Error())
}
return nil
}

View file

@ -0,0 +1,25 @@
package database
import (
"github.com/PretendoNetwork/nex-go/v2"
common_globals "github.com/PretendoNetwork/nex-protocols-common-go/v2/globals"
)
// UpdateParticipation updates the participation of a matchmake session
func UpdateParticipation(manager *common_globals.MatchmakingManager, gatheringID uint32, participation bool) *nex.Error {
result, err := manager.Database.Exec(`UPDATE matchmaking.matchmake_sessions SET open_participation=$1 WHERE id=$2`, participation, gatheringID)
if err != nil {
return nex.NewError(nex.ResultCodes.Core.Unknown, err.Error())
}
rowsAffected, err := result.RowsAffected()
if err != nil {
return nex.NewError(nex.ResultCodes.Core.Unknown, err.Error())
}
if rowsAffected == 0 {
return nex.NewError(nex.ResultCodes.RendezVous.SessionVoid, "change_error")
}
return nil
}

View file

@ -0,0 +1,30 @@
package database
import (
"github.com/PretendoNetwork/nex-go/v2"
"github.com/PretendoNetwork/nex-go/v2/types"
common_globals "github.com/PretendoNetwork/nex-protocols-common-go/v2/globals"
)
// UpdatePersistentGatheringParticipationCount updates the participation count of a user in a persistent gathering. Returns the participation count
func UpdatePersistentGatheringParticipationCount(manager *common_globals.MatchmakingManager, userPID types.PID, gatheringID uint32) (uint32, *nex.Error) {
var participationCount uint32
err := manager.Database.QueryRow(`INSERT INTO matchmaking.community_participations AS cp (
user_pid,
gathering_id,
participation_count
) VALUES (
$1,
$2,
1
) ON CONFLICT (user_pid, gathering_id) DO UPDATE SET
participation_count=cp.participation_count+1 RETURNING participation_count`,
uint64(userPID),
gatheringID,
).Scan(&participationCount)
if err != nil {
return 0, nex.NewError(nex.ResultCodes.Core.Unknown, err.Error())
}
return participationCount, nil
}

View file

@ -0,0 +1,25 @@
package database
import (
"github.com/PretendoNetwork/nex-go/v2"
common_globals "github.com/PretendoNetwork/nex-protocols-common-go/v2/globals"
)
// UpdateProgressScore updates the progress score on a matchmake session
func UpdateProgressScore(manager *common_globals.MatchmakingManager, gatheringID uint32, progressScore uint8) *nex.Error {
result, err := manager.Database.Exec(`UPDATE matchmaking.matchmake_sessions SET progress_score=$1 WHERE id=$2`, progressScore, gatheringID)
if err != nil {
return nex.NewError(nex.ResultCodes.Core.Unknown, err.Error())
}
rowsAffected, err := result.RowsAffected()
if err != nil {
return nex.NewError(nex.ResultCodes.Core.Unknown, err.Error())
}
if rowsAffected == 0 {
return nex.NewError(nex.ResultCodes.RendezVous.SessionVoid, "change_error")
}
return nil
}

View file

@ -0,0 +1,61 @@
package matchmake_extension
import (
"github.com/PretendoNetwork/nex-go/v2"
"github.com/PretendoNetwork/nex-go/v2/types"
match_making_types "github.com/PretendoNetwork/nex-protocols-go/v2/match-making/types"
common_globals "github.com/PretendoNetwork/nex-protocols-common-go/v2/globals"
"github.com/PretendoNetwork/nex-protocols-common-go/v2/matchmake-extension/database"
matchmake_extension "github.com/PretendoNetwork/nex-protocols-go/v2/matchmake-extension"
)
func (commonProtocol *CommonProtocol) findCommunityByGatheringID(err error, packet nex.PacketInterface, callID uint32, lstGID types.List[types.UInt32]) (*nex.RMCMessage, *nex.Error) {
if err != nil {
common_globals.Logger.Error(err.Error())
return nil, nex.NewError(nex.ResultCodes.Core.InvalidArgument, "change_error")
}
connection := packet.Sender().(*nex.PRUDPConnection)
endpoint := connection.Endpoint().(*nex.PRUDPEndPoint)
commonProtocol.manager.Mutex.RLock()
var gatheringIDs []uint32
for _, gatheringID := range lstGID {
gatheringIDs = append(gatheringIDs, uint32(gatheringID))
}
communities, nexError := database.GetPersistentGatheringsByID(commonProtocol.manager, connection.PID(), gatheringIDs)
if nexError != nil {
commonProtocol.manager.Mutex.RUnlock()
return nil, nexError
}
commonProtocol.manager.Mutex.RUnlock()
lstCommunity := types.NewList[match_making_types.PersistentGathering]()
for i := range communities {
// * Scrap persistent gathering password
communities[i].Password = ""
}
lstCommunity = communities
rmcResponseStream := nex.NewByteStreamOut(endpoint.LibraryVersions(), endpoint.ByteStreamSettings())
lstCommunity.WriteTo(rmcResponseStream)
rmcResponseBody := rmcResponseStream.Bytes()
rmcResponse := nex.NewRMCSuccess(endpoint, rmcResponseBody)
rmcResponse.ProtocolID = matchmake_extension.ProtocolID
rmcResponse.MethodID = matchmake_extension.MethodFindCommunityByGatheringID
rmcResponse.CallID = callID
if commonProtocol.OnAfterFindCommunityByGatheringID != nil {
go commonProtocol.OnAfterFindCommunityByGatheringID(packet, lstGID)
}
return rmcResponse, nil
}

View file

@ -0,0 +1,56 @@
package matchmake_extension
import (
"github.com/PretendoNetwork/nex-go/v2"
"github.com/PretendoNetwork/nex-go/v2/types"
match_making_types "github.com/PretendoNetwork/nex-protocols-go/v2/match-making/types"
common_globals "github.com/PretendoNetwork/nex-protocols-common-go/v2/globals"
"github.com/PretendoNetwork/nex-protocols-common-go/v2/matchmake-extension/database"
matchmake_extension "github.com/PretendoNetwork/nex-protocols-go/v2/matchmake-extension"
)
func (commonProtocol *CommonProtocol) findCommunityByParticipant(err error, packet nex.PacketInterface, callID uint32, pid types.PID, resultRange types.ResultRange) (*nex.RMCMessage, *nex.Error) {
if err != nil {
common_globals.Logger.Error(err.Error())
return nil, nex.NewError(nex.ResultCodes.Core.InvalidArgument, "change_error")
}
connection := packet.Sender().(*nex.PRUDPConnection)
endpoint := connection.Endpoint().(*nex.PRUDPEndPoint)
commonProtocol.manager.Mutex.RLock()
communities, nexError := database.GetPersistentGatheringsByParticipant(commonProtocol.manager, connection.PID(), pid, resultRange)
if nexError != nil {
commonProtocol.manager.Mutex.RUnlock()
return nil, nexError
}
commonProtocol.manager.Mutex.RUnlock()
lstCommunity := types.NewList[match_making_types.PersistentGathering]()
for i := range communities {
// * Scrap persistent gathering password
communities[i].Password = ""
}
lstCommunity = communities
rmcResponseStream := nex.NewByteStreamOut(endpoint.LibraryVersions(), endpoint.ByteStreamSettings())
lstCommunity.WriteTo(rmcResponseStream)
rmcResponseBody := rmcResponseStream.Bytes()
rmcResponse := nex.NewRMCSuccess(endpoint, rmcResponseBody)
rmcResponse.ProtocolID = matchmake_extension.ProtocolID
rmcResponse.MethodID = matchmake_extension.MethodFindCommunityByParticipant
rmcResponse.CallID = callID
if commonProtocol.OnAfterFindCommunityByParticipant != nil {
go commonProtocol.OnAfterFindCommunityByParticipant(packet, pid, resultRange)
}
return rmcResponse, nil
}

View file

@ -0,0 +1,56 @@
package matchmake_extension
import (
"github.com/PretendoNetwork/nex-go/v2"
"github.com/PretendoNetwork/nex-go/v2/types"
common_globals "github.com/PretendoNetwork/nex-protocols-common-go/v2/globals"
"github.com/PretendoNetwork/nex-protocols-common-go/v2/matchmake-extension/database"
matchmake_extension "github.com/PretendoNetwork/nex-protocols-go/v2/matchmake-extension"
match_making_types "github.com/PretendoNetwork/nex-protocols-go/v2/match-making/types"
)
func (commonProtocol *CommonProtocol) findOfficialCommunity(err error, packet nex.PacketInterface, callID uint32, isAvailableOnly types.Bool, resultRange types.ResultRange) (*nex.RMCMessage, *nex.Error) {
if err != nil {
common_globals.Logger.Error(err.Error())
return nil, nex.NewError(nex.ResultCodes.Core.InvalidArgument, "change_error")
}
connection := packet.Sender().(*nex.PRUDPConnection)
endpoint := connection.Endpoint().(*nex.PRUDPEndPoint)
commonProtocol.manager.Mutex.RLock()
communities, nexError := database.GetOfficialCommunities(commonProtocol.manager, connection.PID(), bool(isAvailableOnly), resultRange)
if nexError != nil {
commonProtocol.manager.Mutex.RUnlock()
return nil, nexError
}
commonProtocol.manager.Mutex.RUnlock()
lstCommunity := types.NewList[match_making_types.PersistentGathering]()
for i := range communities {
// * Scrap persistent gathering password
communities[i].Password = ""
}
lstCommunity = communities
rmcResponseStream := nex.NewByteStreamOut(endpoint.LibraryVersions(), endpoint.ByteStreamSettings())
lstCommunity.WriteTo(rmcResponseStream)
rmcResponseBody := rmcResponseStream.Bytes()
rmcResponse := nex.NewRMCSuccess(endpoint, rmcResponseBody)
rmcResponse.ProtocolID = matchmake_extension.ProtocolID
rmcResponse.MethodID = matchmake_extension.MethodFindOfficialCommunity
rmcResponse.CallID = callID
if commonProtocol.OnAfterFindOfficialCommunity != nil {
go commonProtocol.OnAfterFindOfficialCommunity(packet, isAvailableOnly, resultRange)
}
return rmcResponse, nil
}

View file

@ -0,0 +1,56 @@
package matchmake_extension
import (
"github.com/PretendoNetwork/nex-go/v2"
"github.com/PretendoNetwork/nex-go/v2/types"
common_globals "github.com/PretendoNetwork/nex-protocols-common-go/v2/globals"
"github.com/PretendoNetwork/nex-protocols-common-go/v2/matchmake-extension/database"
matchmake_extension "github.com/PretendoNetwork/nex-protocols-go/v2/matchmake-extension"
notifications "github.com/PretendoNetwork/nex-protocols-go/v2/notifications"
notifications_types "github.com/PretendoNetwork/nex-protocols-go/v2/notifications/types"
)
func (commonProtocol *CommonProtocol) getFriendNotificationData(err error, packet nex.PacketInterface, callID uint32, uiType types.Int32) (*nex.RMCMessage, *nex.Error) {
if err != nil {
common_globals.Logger.Error(err.Error())
return nil, nex.NewError(nex.ResultCodes.Core.InvalidArgument, err.Error())
}
// * This method can only receive notifications within the range 101-108, which are reserved for game-specific notifications
if uiType < 101 || uiType > 108 {
return nil, nex.NewError(nex.ResultCodes.Core.InvalidArgument, "change_error")
}
connection := packet.Sender().(*nex.PRUDPConnection)
endpoint := connection.Endpoint().(*nex.PRUDPEndPoint)
commonProtocol.manager.Mutex.RLock()
notificationDatas, nexError := database.GetNotificationDatas(commonProtocol.manager, connection.PID(), []uint32{notifications.BuildNotificationType(uint32(uiType), 0)})
if nexError != nil {
commonProtocol.manager.Mutex.RUnlock()
return nil, nexError
}
commonProtocol.manager.Mutex.RUnlock()
dataList := types.NewList[notifications_types.NotificationEvent]()
dataList = notificationDatas
rmcResponseStream := nex.NewByteStreamOut(endpoint.LibraryVersions(), endpoint.ByteStreamSettings())
dataList.WriteTo(rmcResponseStream)
rmcResponseBody := rmcResponseStream.Bytes()
rmcResponse := nex.NewRMCSuccess(endpoint, rmcResponseBody)
rmcResponse.ProtocolID = matchmake_extension.ProtocolID
rmcResponse.MethodID = matchmake_extension.MethodGetFriendNotificationData
rmcResponse.CallID = callID
if commonProtocol.OnAfterGetFriendNotificationData != nil {
go commonProtocol.OnAfterGetFriendNotificationData(packet, uiType)
}
return rmcResponse, nil
}

View file

@ -0,0 +1,61 @@
package matchmake_extension
import (
"github.com/PretendoNetwork/nex-go/v2"
"github.com/PretendoNetwork/nex-go/v2/types"
matchmake_extension "github.com/PretendoNetwork/nex-protocols-go/v2/matchmake-extension"
notifications "github.com/PretendoNetwork/nex-protocols-go/v2/notifications"
notifications_types "github.com/PretendoNetwork/nex-protocols-go/v2/notifications/types"
common_globals "github.com/PretendoNetwork/nex-protocols-common-go/v2/globals"
"github.com/PretendoNetwork/nex-protocols-common-go/v2/matchmake-extension/database"
)
func (commonProtocol *CommonProtocol) getlstFriendNotificationData(err error, packet nex.PacketInterface, callID uint32, lstTypes types.List[types.UInt32]) (*nex.RMCMessage, *nex.Error) {
if err != nil {
common_globals.Logger.Error(err.Error())
return nil, nex.NewError(nex.ResultCodes.Core.InvalidArgument, err.Error())
}
connection := packet.Sender().(*nex.PRUDPConnection)
endpoint := connection.Endpoint().(*nex.PRUDPEndPoint)
notificationTypes := make([]uint32, len(lstTypes))
for i, notificationType := range lstTypes {
// * This method can only receive notifications within the range 101-108, which are reserved for game-specific notifications
if notificationType < 101 || notificationType > 108 {
return nil, nex.NewError(nex.ResultCodes.Core.InvalidArgument, "change_error")
}
notificationTypes[i] = notifications.BuildNotificationType(uint32(notificationType), 0)
}
commonProtocol.manager.Mutex.RLock()
notificationDatas, nexError := database.GetNotificationDatas(commonProtocol.manager, connection.PID(), notificationTypes)
if nexError != nil {
commonProtocol.manager.Mutex.RUnlock()
return nil, nexError
}
commonProtocol.manager.Mutex.RUnlock()
dataList := types.NewList[notifications_types.NotificationEvent]()
dataList = notificationDatas
rmcResponseStream := nex.NewByteStreamOut(endpoint.LibraryVersions(), endpoint.ByteStreamSettings())
dataList.WriteTo(rmcResponseStream)
rmcResponseBody := rmcResponseStream.Bytes()
rmcResponse := nex.NewRMCSuccess(endpoint, rmcResponseBody)
rmcResponse.ProtocolID = matchmake_extension.ProtocolID
rmcResponse.MethodID = matchmake_extension.MethodGetlstFriendNotificationData
rmcResponse.CallID = callID
if commonProtocol.OnAfterGetlstFriendNotificationData != nil {
go commonProtocol.OnAfterGetlstFriendNotificationData(packet, lstTypes)
}
return rmcResponse, nil
}

View file

@ -0,0 +1,55 @@
package matchmake_extension
import (
"github.com/PretendoNetwork/nex-go/v2"
"github.com/PretendoNetwork/nex-go/v2/types"
match_making_types "github.com/PretendoNetwork/nex-protocols-go/v2/match-making/types"
common_globals "github.com/PretendoNetwork/nex-protocols-common-go/v2/globals"
"github.com/PretendoNetwork/nex-protocols-common-go/v2/matchmake-extension/database"
matchmake_extension "github.com/PretendoNetwork/nex-protocols-go/v2/matchmake-extension"
)
func (commonProtocol *CommonProtocol) getSimpleCommunity(err error, packet nex.PacketInterface, callID uint32, gatheringIDList types.List[types.UInt32]) (*nex.RMCMessage, *nex.Error) {
if err != nil {
common_globals.Logger.Error(err.Error())
return nil, nex.NewError(nex.ResultCodes.Core.InvalidArgument, "change_error")
}
connection := packet.Sender().(*nex.PRUDPConnection)
endpoint := connection.Endpoint().(*nex.PRUDPEndPoint)
var gatheringIDs []uint32
for _, gatheringID := range gatheringIDList {
gatheringIDs = append(gatheringIDs, uint32(gatheringID))
}
commonProtocol.manager.Mutex.RLock()
simpleCommunities, nexError := database.GetSimpleCommunities(commonProtocol.manager, gatheringIDs)
if nexError != nil {
commonProtocol.manager.Mutex.RUnlock()
return nil, nexError
}
commonProtocol.manager.Mutex.RUnlock()
lstSimpleCommunityList := types.NewList[match_making_types.SimpleCommunity]()
lstSimpleCommunityList = simpleCommunities
rmcResponseStream := nex.NewByteStreamOut(endpoint.LibraryVersions(), endpoint.ByteStreamSettings())
lstSimpleCommunityList.WriteTo(rmcResponseStream)
rmcResponseBody := rmcResponseStream.Bytes()
rmcResponse := nex.NewRMCSuccess(endpoint, rmcResponseBody)
rmcResponse.ProtocolID = matchmake_extension.ProtocolID
rmcResponse.MethodID = matchmake_extension.MethodGetSimpleCommunity
rmcResponse.CallID = callID
if commonProtocol.OnAfterGetSimpleCommunity != nil {
go commonProtocol.OnAfterGetSimpleCommunity(packet, gatheringIDList)
}
return rmcResponse, nil
}

View file

@ -0,0 +1,61 @@
package matchmake_extension
import (
"slices"
"github.com/PretendoNetwork/nex-go/v2"
"github.com/PretendoNetwork/nex-go/v2/types"
common_globals "github.com/PretendoNetwork/nex-protocols-common-go/v2/globals"
"github.com/PretendoNetwork/nex-protocols-common-go/v2/matchmake-extension/database"
match_making_types "github.com/PretendoNetwork/nex-protocols-go/v2/match-making/types"
matchmake_extension "github.com/PretendoNetwork/nex-protocols-go/v2/matchmake-extension"
)
func (commonProtocol *CommonProtocol) getSimplePlayingSession(err error, packet nex.PacketInterface, callID uint32, listPID types.List[types.PID], includeLoginUser types.Bool) (*nex.RMCMessage, *nex.Error) {
if err != nil {
common_globals.Logger.Error(err.Error())
return nil, nex.NewError(nex.ResultCodes.Core.InvalidArgument, "change_error")
}
connection := packet.Sender().(*nex.PRUDPConnection)
endpoint := connection.Endpoint().(*nex.PRUDPEndPoint)
// * Does nothing if element is not present in the List
listPID = slices.DeleteFunc(listPID, func(pid types.PID) bool {
return pid == connection.PID()
})
if includeLoginUser {
listPID = append(listPID, connection.PID())
}
commonProtocol.manager.Mutex.RLock()
simplePlayingSessions, nexError := database.GetSimplePlayingSession(commonProtocol.manager, listPID)
if nexError != nil {
commonProtocol.manager.Mutex.RUnlock()
return nil, nexError
}
commonProtocol.manager.Mutex.RUnlock()
lstSimplePlayingSession := types.NewList[match_making_types.SimplePlayingSession]()
lstSimplePlayingSession = simplePlayingSessions
rmcResponseStream := nex.NewByteStreamOut(endpoint.LibraryVersions(), endpoint.ByteStreamSettings())
lstSimplePlayingSession.WriteTo(rmcResponseStream)
rmcResponseBody := rmcResponseStream.Bytes()
rmcResponse := nex.NewRMCSuccess(endpoint, rmcResponseBody)
rmcResponse.ProtocolID = matchmake_extension.ProtocolID
rmcResponse.MethodID = matchmake_extension.MethodGetSimplePlayingSession
rmcResponse.CallID = callID
if commonProtocol.OnAfterGetSimplePlayingSession != nil {
go commonProtocol.OnAfterGetSimplePlayingSession(packet, listPID, includeLoginUser)
}
return rmcResponse, nil
}

View file

@ -0,0 +1,78 @@
package matchmake_extension
import (
"github.com/PretendoNetwork/nex-go/v2"
"github.com/PretendoNetwork/nex-go/v2/types"
common_globals "github.com/PretendoNetwork/nex-protocols-common-go/v2/globals"
"github.com/PretendoNetwork/nex-protocols-common-go/v2/matchmake-extension/database"
matchmake_extension "github.com/PretendoNetwork/nex-protocols-go/v2/matchmake-extension"
)
func (commonProtocol *CommonProtocol) joinMatchmakeSession(err error, packet nex.PacketInterface, callID uint32, gid types.UInt32, strMessage types.String) (*nex.RMCMessage, *nex.Error) {
if err != nil {
common_globals.Logger.Error(err.Error())
return nil, nex.NewError(nex.ResultCodes.Core.InvalidArgument, "change_error")
}
if len(strMessage) > 256 {
return nil, nex.NewError(nex.ResultCodes.Core.InvalidArgument, "change_error")
}
commonProtocol.manager.Mutex.Lock()
connection := packet.Sender().(*nex.PRUDPConnection)
endpoint := connection.Endpoint().(*nex.PRUDPEndPoint)
server := endpoint.Server
joinedMatchmakeSession, _, nexError := database.GetMatchmakeSessionByID(commonProtocol.manager, endpoint, uint32(gid))
if nexError != nil {
common_globals.Logger.Error(nexError.Error())
commonProtocol.manager.Mutex.Unlock()
return nil, nexError
}
// TODO - Is this the correct error code?
if joinedMatchmakeSession.UserPasswordEnabled || joinedMatchmakeSession.SystemPasswordEnabled {
commonProtocol.manager.Mutex.Unlock()
return nil, nex.NewError(nex.ResultCodes.RendezVous.PermissionDenied, "change_error")
}
// * Allow game servers to do their own permissions checks
if commonProtocol.CanJoinMatchmakeSession != nil {
nexError = commonProtocol.CanJoinMatchmakeSession(commonProtocol.manager, connection.PID(), joinedMatchmakeSession)
} else {
nexError = common_globals.CanJoinMatchmakeSession(commonProtocol.manager, connection.PID(), joinedMatchmakeSession)
}
if nexError != nil {
commonProtocol.manager.Mutex.Unlock()
return nil, nexError
}
_, nexError = database.JoinMatchmakeSession(commonProtocol.manager, joinedMatchmakeSession, connection, 1, string(strMessage))
if nexError != nil {
common_globals.Logger.Error(nexError.Error())
commonProtocol.manager.Mutex.Unlock()
return nil, nexError
}
commonProtocol.manager.Mutex.Unlock()
rmcResponseStream := nex.NewByteStreamOut(endpoint.LibraryVersions(), endpoint.ByteStreamSettings())
if server.LibraryVersions.MatchMaking.GreaterOrEqual("3.0.0") {
joinedMatchmakeSession.SessionKey.WriteTo(rmcResponseStream)
}
rmcResponseBody := rmcResponseStream.Bytes()
rmcResponse := nex.NewRMCSuccess(endpoint, rmcResponseBody)
rmcResponse.ProtocolID = matchmake_extension.ProtocolID
rmcResponse.MethodID = matchmake_extension.MethodJoinMatchmakeSession
rmcResponse.CallID = callID
if commonProtocol.OnAfterJoinMatchmakeSession != nil {
go commonProtocol.OnAfterJoinMatchmakeSession(packet, gid, strMessage)
}
return rmcResponse, nil
}

View file

@ -0,0 +1,78 @@
package matchmake_extension
import (
"github.com/PretendoNetwork/nex-go/v2"
"github.com/PretendoNetwork/nex-go/v2/types"
common_globals "github.com/PretendoNetwork/nex-protocols-common-go/v2/globals"
"github.com/PretendoNetwork/nex-protocols-common-go/v2/matchmake-extension/database"
matchmake_extension "github.com/PretendoNetwork/nex-protocols-go/v2/matchmake-extension"
)
func (commonProtocol *CommonProtocol) joinMatchmakeSessionEx(err error, packet nex.PacketInterface, callID uint32, gid types.UInt32, strMessage types.String, dontCareMyBlockList types.Bool, participationCount types.UInt16) (*nex.RMCMessage, *nex.Error) {
if err != nil {
common_globals.Logger.Error(err.Error())
return nil, nex.NewError(nex.ResultCodes.Core.InvalidArgument, "change_error")
}
if len(strMessage) > 256 {
return nil, nex.NewError(nex.ResultCodes.Core.InvalidArgument, "change_error")
}
commonProtocol.manager.Mutex.Lock()
connection := packet.Sender().(*nex.PRUDPConnection)
endpoint := connection.Endpoint().(*nex.PRUDPEndPoint)
server := endpoint.Server
joinedMatchmakeSession, _, nexError := database.GetMatchmakeSessionByID(commonProtocol.manager, endpoint, uint32(gid))
if nexError != nil {
common_globals.Logger.Error(nexError.Error())
commonProtocol.manager.Mutex.Unlock()
return nil, nexError
}
// TODO - Is this the correct error code?
if joinedMatchmakeSession.UserPasswordEnabled || joinedMatchmakeSession.SystemPasswordEnabled {
commonProtocol.manager.Mutex.Unlock()
return nil, nex.NewError(nex.ResultCodes.RendezVous.PermissionDenied, "change_error")
}
// * Allow game servers to do their own permissions checks
if commonProtocol.CanJoinMatchmakeSession != nil {
nexError = commonProtocol.CanJoinMatchmakeSession(commonProtocol.manager, connection.PID(), joinedMatchmakeSession)
} else {
nexError = common_globals.CanJoinMatchmakeSession(commonProtocol.manager, connection.PID(), joinedMatchmakeSession)
}
if nexError != nil {
commonProtocol.manager.Mutex.Unlock()
return nil, nexError
}
_, nexError = database.JoinMatchmakeSession(commonProtocol.manager, joinedMatchmakeSession, connection, uint16(participationCount), string(strMessage))
if nexError != nil {
common_globals.Logger.Error(nexError.Error())
commonProtocol.manager.Mutex.Unlock()
return nil, nexError
}
commonProtocol.manager.Mutex.Unlock()
rmcResponseStream := nex.NewByteStreamOut(endpoint.LibraryVersions(), endpoint.ByteStreamSettings())
if server.LibraryVersions.MatchMaking.GreaterOrEqual("3.0.0") {
joinedMatchmakeSession.SessionKey.WriteTo(rmcResponseStream)
}
rmcResponseBody := rmcResponseStream.Bytes()
rmcResponse := nex.NewRMCSuccess(endpoint, rmcResponseBody)
rmcResponse.ProtocolID = matchmake_extension.ProtocolID
rmcResponse.MethodID = matchmake_extension.MethodJoinMatchmakeSessionEx
rmcResponse.CallID = callID
if commonProtocol.OnAfterJoinMatchmakeSessionEx != nil {
go commonProtocol.OnAfterJoinMatchmakeSessionEx(packet, gid, strMessage, dontCareMyBlockList, participationCount)
}
return rmcResponse, nil
}

View file

@ -0,0 +1,90 @@
package matchmake_extension
import (
"github.com/PretendoNetwork/nex-go/v2"
common_globals "github.com/PretendoNetwork/nex-protocols-common-go/v2/globals"
"github.com/PretendoNetwork/nex-protocols-common-go/v2/matchmake-extension/database"
"github.com/PretendoNetwork/nex-protocols-go/v2/match-making/constants"
match_making_types "github.com/PretendoNetwork/nex-protocols-go/v2/match-making/types"
matchmake_extension "github.com/PretendoNetwork/nex-protocols-go/v2/matchmake-extension"
)
func (commonProtocol *CommonProtocol) joinMatchmakeSessionWithParam(err error, packet nex.PacketInterface, callID uint32, joinMatchmakeSessionParam match_making_types.JoinMatchmakeSessionParam) (*nex.RMCMessage, *nex.Error) {
if err != nil {
common_globals.Logger.Error(err.Error())
return nil, nex.NewError(nex.ResultCodes.Core.InvalidArgument, "change_error")
}
if len(joinMatchmakeSessionParam.JoinMessage) > 256 {
return nil, nex.NewError(nex.ResultCodes.Core.InvalidArgument, "change_error")
}
commonProtocol.manager.Mutex.Lock()
connection := packet.Sender().(*nex.PRUDPConnection)
endpoint := connection.Endpoint().(*nex.PRUDPEndPoint)
if joinMatchmakeSessionParam.GIDForParticipationCheck != 0 {
// * Check that all new participants are participating in the specified gathering ID
nexError := database.CheckGatheringForParticipation(commonProtocol.manager, uint32(joinMatchmakeSessionParam.GIDForParticipationCheck), append(joinMatchmakeSessionParam.AdditionalParticipants, connection.PID()))
if nexError != nil {
commonProtocol.manager.Mutex.Unlock()
return nil, nexError
}
}
joinedMatchmakeSession, systemPassword, nexError := database.GetMatchmakeSessionByID(commonProtocol.manager, endpoint, uint32(joinMatchmakeSessionParam.GID))
if nexError != nil {
common_globals.Logger.Error(nexError.Error())
commonProtocol.manager.Mutex.Unlock()
return nil, nexError
}
// TODO - Are these the correct error codes?
if bool(joinedMatchmakeSession.UserPasswordEnabled) && !joinMatchmakeSessionParam.StrUserPassword.Equals(joinedMatchmakeSession.UserPassword) {
commonProtocol.manager.Mutex.Unlock()
return nil, nex.NewError(nex.ResultCodes.RendezVous.InvalidPassword, "change_error")
}
if bool(joinedMatchmakeSession.SystemPasswordEnabled) && string(joinMatchmakeSessionParam.StrSystemPassword) != systemPassword {
commonProtocol.manager.Mutex.Unlock()
return nil, nex.NewError(nex.ResultCodes.RendezVous.InvalidPassword, "change_error")
}
// * Allow game servers to do their own permissions checks
if commonProtocol.CanJoinMatchmakeSession != nil {
nexError = commonProtocol.CanJoinMatchmakeSession(commonProtocol.manager, connection.PID(), joinedMatchmakeSession)
} else {
nexError = common_globals.CanJoinMatchmakeSession(commonProtocol.manager, connection.PID(), joinedMatchmakeSession)
}
if nexError != nil {
commonProtocol.manager.Mutex.Unlock()
return nil, nexError
}
_, nexError = database.JoinMatchmakeSessionWithParticipants(commonProtocol.manager, joinedMatchmakeSession, connection, joinMatchmakeSessionParam.AdditionalParticipants, string(joinMatchmakeSessionParam.JoinMessage), constants.JoinMatchmakeSessionBehavior(joinMatchmakeSessionParam.JoinMatchmakeSessionBehavior))
if nexError != nil {
common_globals.Logger.Error(nexError.Error())
commonProtocol.manager.Mutex.Unlock()
return nil, nexError
}
commonProtocol.manager.Mutex.Unlock()
rmcResponseStream := nex.NewByteStreamOut(endpoint.LibraryVersions(), endpoint.ByteStreamSettings())
joinedMatchmakeSession.WriteTo(rmcResponseStream)
rmcResponseBody := rmcResponseStream.Bytes()
rmcResponse := nex.NewRMCSuccess(endpoint, rmcResponseBody)
rmcResponse.ProtocolID = matchmake_extension.ProtocolID
rmcResponse.MethodID = matchmake_extension.MethodJoinMatchmakeSessionWithParam
rmcResponse.CallID = callID
if commonProtocol.OnAfterJoinMatchmakeSessionWithParam != nil {
go commonProtocol.OnAfterJoinMatchmakeSessionWithParam(packet, joinMatchmakeSessionParam)
}
return rmcResponse, nil
}

View file

@ -0,0 +1,58 @@
package matchmake_extension
import (
"github.com/PretendoNetwork/nex-go/v2"
"github.com/PretendoNetwork/nex-go/v2/types"
common_globals "github.com/PretendoNetwork/nex-protocols-common-go/v2/globals"
"github.com/PretendoNetwork/nex-protocols-common-go/v2/matchmake-extension/database"
matchmake_extension "github.com/PretendoNetwork/nex-protocols-go/v2/matchmake-extension"
)
func (commonProtocol *CommonProtocol) modifyCurrentGameAttribute(err error, packet nex.PacketInterface, callID uint32, gid types.UInt32, attribIndex types.UInt32, newValue types.UInt32) (*nex.RMCMessage, *nex.Error) {
if err != nil {
common_globals.Logger.Error(err.Error())
return nil, nex.NewError(nex.ResultCodes.Core.InvalidArgument, "change_error")
}
connection := packet.Sender().(*nex.PRUDPConnection)
endpoint := connection.Endpoint().(*nex.PRUDPEndPoint)
commonProtocol.manager.Mutex.Lock()
session, _, nexError := database.GetMatchmakeSessionByID(commonProtocol.manager, endpoint, uint32(gid))
if nexError != nil {
commonProtocol.manager.Mutex.Unlock()
return nil, nexError
}
if !session.Gathering.OwnerPID.Equals(connection.PID()) {
commonProtocol.manager.Mutex.Unlock()
return nil, nex.NewError(nex.ResultCodes.RendezVous.PermissionDenied, "change_error")
}
index := int(attribIndex)
if index >= len(session.Attributes) {
commonProtocol.manager.Mutex.Unlock()
return nil, nex.NewError(nex.ResultCodes.Core.InvalidIndex, "change_error")
}
nexError = database.UpdateGameAttribute(commonProtocol.manager, uint32(gid), uint32(attribIndex), uint32(newValue))
if nexError != nil {
commonProtocol.manager.Mutex.Unlock()
return nil, nexError
}
commonProtocol.manager.Mutex.Unlock()
rmcResponse := nex.NewRMCSuccess(endpoint, nil)
rmcResponse.ProtocolID = matchmake_extension.ProtocolID
rmcResponse.MethodID = matchmake_extension.MethodModifyCurrentGameAttribute
rmcResponse.CallID = callID
if commonProtocol.OnAfterModifyCurrentGameAttribute != nil {
go commonProtocol.OnAfterModifyCurrentGameAttribute(packet, gid, attribIndex, newValue)
}
return rmcResponse, nil
}

Some files were not shown because too many files have changed in this diff Show more