Compare commits

...

120 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
136 changed files with 7106 additions and 4208 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,96 +1,81 @@
package datastore
import (
nex "github.com/PretendoNetwork/nex-go"
common_globals "github.com/PretendoNetwork/nex-protocols-common-go/globals"
datastore "github.com/PretendoNetwork/nex-protocols-go/datastore"
datastore_types "github.com/PretendoNetwork/nex-protocols-go/datastore/types"
"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 changeMeta(err error, packet nex.PacketInterface, callID uint32, param *datastore_types.DataStoreChangeMetaParam) uint32 {
if commonDataStoreProtocol.getObjectInfoByDataIDHandler == nil {
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 nex.Errors.Core.NotImplemented
return nil, nex.NewError(nex.ResultCodes.Core.NotImplemented, "change_error")
}
if commonDataStoreProtocol.updateObjectPeriodByDataIDWithPasswordHandler == nil {
if commonProtocol.UpdateObjectPeriodByDataIDWithPassword == nil {
common_globals.Logger.Warning("UpdateObjectPeriodByDataIDWithPassword not defined")
return nex.Errors.Core.NotImplemented
return nil, nex.NewError(nex.ResultCodes.Core.NotImplemented, "change_error")
}
if commonDataStoreProtocol.updateObjectMetaBinaryByDataIDWithPasswordHandler == nil {
if commonProtocol.UpdateObjectMetaBinaryByDataIDWithPassword == nil {
common_globals.Logger.Warning("UpdateObjectMetaBinaryByDataIDWithPassword not defined")
return nex.Errors.Core.NotImplemented
return nil, nex.NewError(nex.ResultCodes.Core.NotImplemented, "change_error")
}
if commonDataStoreProtocol.updateObjectDataTypeByDataIDWithPasswordHandler == nil {
if commonProtocol.UpdateObjectDataTypeByDataIDWithPassword == nil {
common_globals.Logger.Warning("UpdateObjectDataTypeByDataIDWithPassword not defined")
return nex.Errors.Core.NotImplemented
return nil, nex.NewError(nex.ResultCodes.Core.NotImplemented, "change_error")
}
if err != nil {
common_globals.Logger.Error(err.Error())
return nex.Errors.DataStore.Unknown
return nil, nex.NewError(nex.ResultCodes.DataStore.Unknown, "change_error")
}
client := packet.Sender()
connection := packet.Sender()
endpoint := connection.Endpoint()
metaInfo, errCode := commonDataStoreProtocol.getObjectInfoByDataIDHandler(param.DataID)
if errCode != 0 {
return errCode
metaInfo, errCode := commonProtocol.GetObjectInfoByDataID(param.DataID)
if errCode != nil {
return nil, errCode
}
// TODO - Is this the right permission?
errCode = commonDataStoreProtocol.VerifyObjectPermission(metaInfo.OwnerID, client.PID(), metaInfo.DelPermission)
if errCode != 0 {
return errCode
errCode = commonProtocol.VerifyObjectPermission(metaInfo.OwnerID, connection.PID(), metaInfo.DelPermission)
if errCode != nil {
return nil, errCode
}
if param.ModifiesFlag&0x08 != 0 {
errCode = commonDataStoreProtocol.updateObjectPeriodByDataIDWithPasswordHandler(param.DataID, param.Period, param.UpdatePassword)
if errCode != 0 {
return errCode
if uint32(param.ModifiesFlag) & 0x08 != 0 {
errCode = commonProtocol.UpdateObjectPeriodByDataIDWithPassword(param.DataID, param.Period, param.UpdatePassword)
if errCode != nil {
return nil, errCode
}
}
if param.ModifiesFlag&0x10 != 0 {
errCode = commonDataStoreProtocol.updateObjectMetaBinaryByDataIDWithPasswordHandler(param.DataID, param.MetaBinary, param.UpdatePassword)
if errCode != 0 {
return errCode
if uint32(param.ModifiesFlag) & 0x10 != 0 {
errCode = commonProtocol.UpdateObjectMetaBinaryByDataIDWithPassword(param.DataID, param.MetaBinary, param.UpdatePassword)
if errCode != nil {
return nil, errCode
}
}
if param.ModifiesFlag&0x80 != 0 {
errCode = commonDataStoreProtocol.updateObjectDataTypeByDataIDWithPasswordHandler(param.DataID, param.DataType, param.UpdatePassword)
if errCode != 0 {
return errCode
if uint32(param.ModifiesFlag) & 0x80 != 0 {
errCode = commonProtocol.UpdateObjectDataTypeByDataIDWithPassword(param.DataID, param.DataType, param.UpdatePassword)
if errCode != nil {
return nil, errCode
}
}
rmcResponse := nex.NewRMCResponse(datastore.ProtocolID, callID)
rmcResponse.SetSuccess(datastore.MethodChangeMeta, nil)
rmcResponse := nex.NewRMCSuccess(endpoint, nil)
rmcResponse.ProtocolID = datastore.ProtocolID
rmcResponse.MethodID = datastore.MethodChangeMeta
rmcResponse.CallID = callID
rmcResponseBytes := rmcResponse.Bytes()
var responsePacket nex.PacketInterface
if commonDataStoreProtocol.server.PRUDPVersion() == 0 {
responsePacket, _ = nex.NewPacketV0(client, nil)
responsePacket.SetVersion(0)
} else {
responsePacket, _ = nex.NewPacketV1(client, nil)
responsePacket.SetVersion(1)
if commonProtocol.OnAfterChangeMeta != nil {
go commonProtocol.OnAfterChangeMeta(packet, param)
}
responsePacket.SetSource(packet.Destination())
responsePacket.SetDestination(packet.Source())
responsePacket.SetType(nex.DataPacket)
responsePacket.SetPayload(rmcResponseBytes)
responsePacket.AddFlag(nex.FlagNeedsAck)
responsePacket.AddFlag(nex.FlagReliable)
commonDataStoreProtocol.server.Send(responsePacket)
return 0
return rmcResponse, nil
}

View file

@ -3,124 +3,109 @@ package datastore
import (
"fmt"
nex "github.com/PretendoNetwork/nex-go"
common_globals "github.com/PretendoNetwork/nex-protocols-common-go/globals"
datastore "github.com/PretendoNetwork/nex-protocols-go/datastore"
datastore_types "github.com/PretendoNetwork/nex-protocols-go/datastore/types"
"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 completePostObject(err error, packet nex.PacketInterface, callID uint32, param *datastore_types.DataStoreCompletePostParam) uint32 {
if commonDataStoreProtocol.minIOClient == nil {
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 nex.Errors.Core.NotImplemented
return nil, nex.NewError(nex.ResultCodes.Core.NotImplemented, "change_error")
}
if commonDataStoreProtocol.getObjectInfoByDataIDHandler == nil {
if commonProtocol.GetObjectInfoByDataID == nil {
common_globals.Logger.Warning("GetObjectInfoByDataID not defined")
return nex.Errors.Core.NotImplemented
return nil, nex.NewError(nex.ResultCodes.Core.NotImplemented, "change_error")
}
if commonDataStoreProtocol.getObjectOwnerByDataIDHandler == nil {
if commonProtocol.GetObjectOwnerByDataID == nil {
common_globals.Logger.Warning("GetObjectOwnerByDataID not defined")
return nex.Errors.Core.NotImplemented
return nil, nex.NewError(nex.ResultCodes.Core.NotImplemented, "change_error")
}
if commonDataStoreProtocol.getObjectSizeByDataIDHandler == nil {
if commonProtocol.GetObjectSizeByDataID == nil {
common_globals.Logger.Warning("GetObjectSizeByDataID not defined")
return nex.Errors.Core.NotImplemented
return nil, nex.NewError(nex.ResultCodes.Core.NotImplemented, "change_error")
}
if commonDataStoreProtocol.updateObjectUploadCompletedByDataIDHandler == nil {
if commonProtocol.UpdateObjectUploadCompletedByDataID == nil {
common_globals.Logger.Warning("UpdateObjectUploadCompletedByDataID not defined")
return nex.Errors.Core.NotImplemented
return nil, nex.NewError(nex.ResultCodes.Core.NotImplemented, "change_error")
}
if commonDataStoreProtocol.deleteObjectByDataIDHandler == nil {
if commonProtocol.DeleteObjectByDataID == nil {
common_globals.Logger.Warning("DeleteObjectByDataID not defined")
return nex.Errors.Core.NotImplemented
return nil, nex.NewError(nex.ResultCodes.Core.NotImplemented, "change_error")
}
if err != nil {
common_globals.Logger.Error(err.Error())
return nex.Errors.DataStore.Unknown
return nil, nex.NewError(nex.ResultCodes.DataStore.Unknown, "change_error")
}
client := packet.Sender()
connection := packet.Sender()
endpoint := connection.Endpoint()
// * If GetObjectInfoByDataID returns data then that means
// * the object has already been marked as uploaded. So do
// * nothing
objectInfo, _ := commonDataStoreProtocol.getObjectInfoByDataIDHandler(param.DataID)
if objectInfo != nil {
return nex.Errors.DataStore.PermissionDenied
_, 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 := commonDataStoreProtocol.getObjectOwnerByDataIDHandler(param.DataID)
if errCode != 0 {
return errCode
ownerPID, errCode := commonProtocol.GetObjectOwnerByDataID(param.DataID)
if errCode != nil {
return nil, errCode
}
if ownerPID != client.PID() {
return nex.Errors.DataStore.PermissionDenied
if ownerPID != uint32(connection.PID()) {
return nil, nex.NewError(nex.ResultCodes.DataStore.PermissionDenied, "change_error")
}
bucket := commonDataStoreProtocol.s3Bucket
key := fmt.Sprintf("%s/%d.bin", commonDataStoreProtocol.s3DataKeyBase, param.DataID)
bucket := commonProtocol.S3Bucket
key := fmt.Sprintf("%s/%d.bin", commonProtocol.s3DataKeyBase, param.DataID)
if param.IsSuccess {
objectSizeS3, err := commonDataStoreProtocol.S3ObjectSize(bucket, key)
objectSizeS3, err := commonProtocol.S3ObjectSize(bucket, key)
if err != nil {
common_globals.Logger.Error(err.Error())
return nex.Errors.DataStore.NotFound
return nil, nex.NewError(nex.ResultCodes.DataStore.NotFound, "change_error")
}
objectSizeDB, errCode := commonDataStoreProtocol.getObjectSizeByDataIDHandler(param.DataID)
if errCode != 0 {
return errCode
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 nex.Errors.DataStore.Unknown
return nil, nex.NewError(nex.ResultCodes.DataStore.Unknown, "change_error")
}
errCode = commonDataStoreProtocol.updateObjectUploadCompletedByDataIDHandler(param.DataID, true)
if errCode != 0 {
return errCode
errCode = commonProtocol.UpdateObjectUploadCompletedByDataID(param.DataID, true)
if errCode != nil {
return nil, errCode
}
} else {
errCode := commonDataStoreProtocol.deleteObjectByDataIDHandler(param.DataID)
if errCode != 0 {
return errCode
errCode := commonProtocol.DeleteObjectByDataID(param.DataID)
if errCode != nil {
return nil, errCode
}
}
rmcResponse := nex.NewRMCResponse(datastore.ProtocolID, callID)
rmcResponse.SetSuccess(datastore.MethodCompletePostObject, nil)
rmcResponse := nex.NewRMCSuccess(endpoint, nil)
rmcResponse.ProtocolID = datastore.ProtocolID
rmcResponse.MethodID = datastore.MethodCompletePostObject
rmcResponse.CallID = callID
rmcResponseBytes := rmcResponse.Bytes()
var responsePacket nex.PacketInterface
if commonDataStoreProtocol.server.PRUDPVersion() == 0 {
responsePacket, _ = nex.NewPacketV0(client, nil)
responsePacket.SetVersion(0)
} else {
responsePacket, _ = nex.NewPacketV1(client, nil)
responsePacket.SetVersion(1)
if commonProtocol.OnAfterCompletePostObject != nil {
go commonProtocol.OnAfterCompletePostObject(packet, param)
}
responsePacket.SetSource(packet.Destination())
responsePacket.SetDestination(packet.Source())
responsePacket.SetType(nex.DataPacket)
responsePacket.SetPayload(rmcResponseBytes)
responsePacket.AddFlag(nex.FlagNeedsAck)
responsePacket.AddFlag(nex.FlagReliable)
commonDataStoreProtocol.server.Send(responsePacket)
return 0
return rmcResponse, nil
}

View file

@ -3,85 +3,81 @@ package datastore
import (
"fmt"
"github.com/PretendoNetwork/nex-go"
common_globals "github.com/PretendoNetwork/nex-protocols-common-go/globals"
datastore "github.com/PretendoNetwork/nex-protocols-go/datastore"
"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 completePostObjects(err error, packet nex.PacketInterface, callID uint32, dataIDs []uint64) uint32 {
if commonDataStoreProtocol.minIOClient == nil {
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 nex.Errors.Core.NotImplemented
return nil, nex.NewError(nex.ResultCodes.Core.NotImplemented, "change_error")
}
if commonDataStoreProtocol.getObjectSizeByDataIDHandler == nil {
if commonProtocol.GetObjectSizeByDataID == nil {
common_globals.Logger.Warning("GetObjectSizeByDataID not defined")
return nex.Errors.Core.NotImplemented
return nil, nex.NewError(nex.ResultCodes.Core.NotImplemented, "change_error")
}
if commonDataStoreProtocol.updateObjectUploadCompletedByDataIDHandler == nil {
if commonProtocol.UpdateObjectUploadCompletedByDataID == nil {
common_globals.Logger.Warning("UpdateObjectUploadCompletedByDataID not defined")
return nex.Errors.Core.NotImplemented
return nil, nex.NewError(nex.ResultCodes.Core.NotImplemented, "change_error")
}
if err != nil {
common_globals.Logger.Error(err.Error())
return nex.Errors.DataStore.Unknown
return nil, nex.NewError(nex.ResultCodes.DataStore.Unknown, "change_error")
}
client := packet.Sender()
connection := packet.Sender()
endpoint := connection.Endpoint()
var errorCode *nex.Error
for _, dataID := range dataIDs {
bucket := commonDataStoreProtocol.s3Bucket
key := fmt.Sprintf("%s/%d.bin", commonDataStoreProtocol.s3DataKeyBase, dataID)
bucket := commonProtocol.S3Bucket
key := fmt.Sprintf("%s/%d.bin", commonProtocol.s3DataKeyBase, dataID)
objectSizeS3, err := commonDataStoreProtocol.S3ObjectSize(bucket, key)
objectSizeS3, err := commonProtocol.S3ObjectSize(bucket, key)
if err != nil {
common_globals.Logger.Error(err.Error())
return nex.Errors.DataStore.NotFound
errorCode = nex.NewError(nex.ResultCodes.DataStore.NotFound, "change_error")
break
}
objectSizeDB, errCode := commonDataStoreProtocol.getObjectSizeByDataIDHandler(dataID)
if errCode != 0 {
return errCode
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?
return nex.Errors.DataStore.Unknown
errorCode = nex.NewError(nex.ResultCodes.DataStore.Unknown, "change_error")
break
}
errCode = commonDataStoreProtocol.updateObjectUploadCompletedByDataIDHandler(dataID, true)
if errCode != 0 {
return errCode
errCode = commonProtocol.UpdateObjectUploadCompletedByDataID(dataID, true)
if errCode != nil {
errorCode = errCode
break
}
}
rmcResponse := nex.NewRMCResponse(datastore.ProtocolID, callID)
rmcResponse.SetSuccess(datastore.MethodCompletePostObjects, nil)
rmcResponseBytes := rmcResponse.Bytes()
var responsePacket nex.PacketInterface
if commonDataStoreProtocol.server.PRUDPVersion() == 0 {
responsePacket, _ = nex.NewPacketV0(client, nil)
responsePacket.SetVersion(0)
} else {
responsePacket, _ = nex.NewPacketV1(client, nil)
responsePacket.SetVersion(1)
if errorCode != nil {
return nil, errorCode
}
responsePacket.SetSource(packet.Destination())
responsePacket.SetDestination(packet.Source())
responsePacket.SetType(nex.DataPacket)
responsePacket.SetPayload(rmcResponseBytes)
rmcResponse := nex.NewRMCSuccess(endpoint, nil)
rmcResponse.ProtocolID = datastore.ProtocolID
rmcResponse.MethodID = datastore.MethodCompletePostObjects
rmcResponse.CallID = callID
responsePacket.AddFlag(nex.FlagNeedsAck)
responsePacket.AddFlag(nex.FlagReliable)
if commonProtocol.OnAfterCompletePostObjects != nil {
go commonProtocol.OnAfterCompletePostObjects(packet, dataIDs)
}
commonDataStoreProtocol.server.Send(responsePacket)
return 0
return rmcResponse, nil
}

View file

@ -1,69 +1,54 @@
package datastore
import (
"github.com/PretendoNetwork/nex-go"
common_globals "github.com/PretendoNetwork/nex-protocols-common-go/globals"
datastore "github.com/PretendoNetwork/nex-protocols-go/datastore"
datastore_types "github.com/PretendoNetwork/nex-protocols-go/datastore/types"
"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 deleteObject(err error, packet nex.PacketInterface, callID uint32, param *datastore_types.DataStoreDeleteParam) uint32 {
if commonDataStoreProtocol.getObjectInfoByDataIDHandler == nil {
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 nex.Errors.Core.NotImplemented
return nil, nex.NewError(nex.ResultCodes.Core.NotImplemented, "change_error")
}
if commonDataStoreProtocol.deleteObjectByDataIDWithPasswordHandler == nil {
if commonProtocol.DeleteObjectByDataIDWithPassword == nil {
common_globals.Logger.Warning("DeleteObjectByDataIDWithPassword not defined")
return nex.Errors.Core.NotImplemented
return nil, nex.NewError(nex.ResultCodes.Core.NotImplemented, "change_error")
}
if err != nil {
common_globals.Logger.Error(err.Error())
return nex.Errors.DataStore.Unknown
return nil, nex.NewError(nex.ResultCodes.DataStore.Unknown, "change_error")
}
client := packet.Sender()
connection := packet.Sender()
endpoint := connection.Endpoint()
metaInfo, errCode := commonDataStoreProtocol.getObjectInfoByDataIDHandler(param.DataID)
if errCode != 0 {
return errCode
metaInfo, errCode := commonProtocol.GetObjectInfoByDataID(param.DataID)
if errCode != nil {
return nil, errCode
}
errCode = commonDataStoreProtocol.VerifyObjectPermission(metaInfo.OwnerID, client.PID(), metaInfo.DelPermission)
if errCode != 0 {
return errCode
errCode = commonProtocol.VerifyObjectPermission(metaInfo.OwnerID, connection.PID(), metaInfo.DelPermission)
if errCode != nil {
return nil, errCode
}
errCode = commonDataStoreProtocol.deleteObjectByDataIDWithPasswordHandler(param.DataID, param.UpdatePassword)
if errCode != 0 {
return errCode
errCode = commonProtocol.DeleteObjectByDataIDWithPassword(param.DataID, param.UpdatePassword)
if errCode != nil {
return nil, errCode
}
rmcResponse := nex.NewRMCResponse(datastore.ProtocolID, callID)
rmcResponse.SetSuccess(datastore.MethodDeleteObject, nil)
rmcResponse := nex.NewRMCSuccess(endpoint, nil)
rmcResponse.ProtocolID = datastore.ProtocolID
rmcResponse.MethodID = datastore.MethodDeleteObject
rmcResponse.CallID = callID
rmcResponseBytes := rmcResponse.Bytes()
var responsePacket nex.PacketInterface
if commonDataStoreProtocol.server.PRUDPVersion() == 0 {
responsePacket, _ = nex.NewPacketV0(client, nil)
responsePacket.SetVersion(0)
} else {
responsePacket, _ = nex.NewPacketV1(client, nil)
responsePacket.SetVersion(1)
if commonProtocol.OnAfterDeleteObject != nil {
go commonProtocol.OnAfterDeleteObject(packet, param)
}
responsePacket.SetSource(packet.Destination())
responsePacket.SetDestination(packet.Source())
responsePacket.SetType(nex.DataPacket)
responsePacket.SetPayload(rmcResponseBytes)
responsePacket.AddFlag(nex.FlagNeedsAck)
responsePacket.AddFlag(nex.FlagReliable)
commonDataStoreProtocol.server.Send(responsePacket)
return 0
return rmcResponse, nil
}

View file

@ -1,80 +1,66 @@
package datastore
import (
nex "github.com/PretendoNetwork/nex-go"
common_globals "github.com/PretendoNetwork/nex-protocols-common-go/globals"
datastore "github.com/PretendoNetwork/nex-protocols-go/datastore"
datastore_types "github.com/PretendoNetwork/nex-protocols-go/datastore/types"
"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 getMeta(err error, packet nex.PacketInterface, callID uint32, param *datastore_types.DataStoreGetMetaParam) uint32 {
if commonDataStoreProtocol.getObjectInfoByPersistenceTargetWithPasswordHandler == nil {
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 nex.Errors.Core.NotImplemented
return nil, nex.NewError(nex.ResultCodes.Core.NotImplemented, "change_error")
}
if commonDataStoreProtocol.getObjectInfoByDataIDWithPasswordHandler == nil {
if commonProtocol.GetObjectInfoByDataIDWithPassword == nil {
common_globals.Logger.Warning("GetObjectInfoByDataIDWithPassword not defined")
return nex.Errors.Core.NotImplemented
return nil, nex.NewError(nex.ResultCodes.Core.NotImplemented, "change_error")
}
if err != nil {
common_globals.Logger.Error(err.Error())
return nex.Errors.DataStore.Unknown
return nil, nex.NewError(nex.ResultCodes.DataStore.Unknown, "change_error")
}
client := packet.Sender()
connection := packet.Sender()
endpoint := connection.Endpoint()
var pMetaInfo *datastore_types.DataStoreMetaInfo
var errCode uint32
var pMetaInfo datastore_types.DataStoreMetaInfo
var errCode *nex.Error
// * Real server ignores PersistenceTarget if DataID is set
if param.DataID == 0 {
pMetaInfo, errCode = commonDataStoreProtocol.getObjectInfoByPersistenceTargetWithPasswordHandler(param.PersistenceTarget, param.AccessPassword)
pMetaInfo, errCode = commonProtocol.GetObjectInfoByPersistenceTargetWithPassword(param.PersistenceTarget, param.AccessPassword)
} else {
pMetaInfo, errCode = commonDataStoreProtocol.getObjectInfoByDataIDWithPasswordHandler(param.DataID, param.AccessPassword)
pMetaInfo, errCode = commonProtocol.GetObjectInfoByDataIDWithPassword(param.DataID, param.AccessPassword)
}
if errCode != 0 {
return errCode
if errCode != nil {
return nil, errCode
}
errCode = commonDataStoreProtocol.VerifyObjectPermission(pMetaInfo.OwnerID, client.PID(), pMetaInfo.Permission)
if errCode != 0 {
return errCode
errCode = commonProtocol.VerifyObjectPermission(pMetaInfo.OwnerID, connection.PID(), pMetaInfo.Permission)
if errCode != nil {
return nil, errCode
}
pMetaInfo.FilterPropertiesByResultOption(param.ResultOption)
rmcResponseStream := nex.NewStreamOut(commonDataStoreProtocol.server)
rmcResponseStream.WriteStructure(pMetaInfo)
rmcResponseStream := nex.NewByteStreamOut(endpoint.LibraryVersions(), endpoint.ByteStreamSettings())
pMetaInfo.WriteTo(rmcResponseStream)
rmcResponseBody := rmcResponseStream.Bytes()
rmcResponse := nex.NewRMCResponse(datastore.ProtocolID, callID)
rmcResponse.SetSuccess(datastore.MethodGetMeta, rmcResponseBody)
rmcResponse := nex.NewRMCSuccess(endpoint, rmcResponseBody)
rmcResponse.ProtocolID = datastore.ProtocolID
rmcResponse.MethodID = datastore.MethodGetMeta
rmcResponse.CallID = callID
rmcResponseBytes := rmcResponse.Bytes()
var responsePacket nex.PacketInterface
if commonDataStoreProtocol.server.PRUDPVersion() == 0 {
responsePacket, _ = nex.NewPacketV0(client, nil)
responsePacket.SetVersion(0)
} else {
responsePacket, _ = nex.NewPacketV1(client, nil)
responsePacket.SetVersion(1)
if commonProtocol.OnAfterGetMeta != nil {
go commonProtocol.OnAfterGetMeta(packet, param)
}
responsePacket.SetSource(packet.Destination())
responsePacket.SetDestination(packet.Source())
responsePacket.SetType(nex.DataPacket)
responsePacket.SetPayload(rmcResponseBytes)
responsePacket.AddFlag(nex.FlagNeedsAck)
responsePacket.AddFlag(nex.FlagReliable)
commonDataStoreProtocol.server.Send(responsePacket)
return 0
return rmcResponse, nil
}

View file

@ -1,50 +1,52 @@
package datastore
import (
"github.com/PretendoNetwork/nex-go"
common_globals "github.com/PretendoNetwork/nex-protocols-common-go/globals"
datastore "github.com/PretendoNetwork/nex-protocols-go/datastore"
datastore_types "github.com/PretendoNetwork/nex-protocols-go/datastore/types"
"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 getMetas(err error, packet nex.PacketInterface, callID uint32, dataIDs []uint64, param *datastore_types.DataStoreGetMetaParam) uint32 {
if commonDataStoreProtocol.getObjectInfoByDataIDHandler == nil {
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 nex.Errors.Core.NotImplemented
return nil, nex.NewError(nex.ResultCodes.Core.NotImplemented, "change_error")
}
if err != nil {
common_globals.Logger.Error(err.Error())
return nex.Errors.DataStore.Unknown
return nil, nex.NewError(nex.ResultCodes.DataStore.Unknown, "change_error")
}
client := packet.Sender()
connection := packet.Sender()
endpoint := connection.Endpoint()
// TODO - Verify if param.PersistenceTarget is respected? It wouldn't make sense here but who knows
pMetaInfo := make([]*datastore_types.DataStoreMetaInfo, 0, len(dataIDs))
pResults := make([]*nex.Result, 0, len(dataIDs))
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 i := 0; i < len(dataIDs); i++ {
objectInfo, errCode := commonDataStoreProtocol.getObjectInfoByDataIDHandler(dataIDs[i])
for _, dataID := range dataIDs {
objectInfo, errCode := commonProtocol.GetObjectInfoByDataID(dataID)
if errCode != 0 {
if errCode != nil {
objectInfo = datastore_types.NewDataStoreMetaInfo()
pResults = append(pResults, nex.NewResultError(errCode))
pResults = append(pResults, types.NewQResultError(errCode.ResultCode))
} else {
errCode = commonDataStoreProtocol.VerifyObjectPermission(objectInfo.OwnerID, client.PID(), objectInfo.Permission)
if errCode != 0 {
errCode = commonProtocol.VerifyObjectPermission(objectInfo.OwnerID, connection.PID(), objectInfo.Permission)
if errCode != nil {
objectInfo = datastore_types.NewDataStoreMetaInfo()
pResults = append(pResults, nex.NewResultError(errCode))
pResults = append(pResults, types.NewQResultError(errCode.ResultCode))
} else {
pResults = append(pResults, nex.NewResultSuccess(nex.Errors.DataStore.Unknown))
pResults = append(pResults, types.NewQResultSuccess(nex.ResultCodes.DataStore.Unknown))
}
objectInfo.FilterPropertiesByResultOption(param.ResultOption)
@ -53,37 +55,21 @@ func getMetas(err error, packet nex.PacketInterface, callID uint32, dataIDs []ui
pMetaInfo = append(pMetaInfo, objectInfo)
}
rmcResponseStream := nex.NewStreamOut(commonDataStoreProtocol.server)
rmcResponseStream := nex.NewByteStreamOut(endpoint.LibraryVersions(), endpoint.ByteStreamSettings())
rmcResponseStream.WriteListStructure(pMetaInfo)
rmcResponseStream.WriteListResult(pResults)
pMetaInfo.WriteTo(rmcResponseStream)
pResults.WriteTo(rmcResponseStream)
rmcResponseBody := rmcResponseStream.Bytes()
rmcResponse := nex.NewRMCResponse(datastore.ProtocolID, callID)
rmcResponse.SetSuccess(datastore.MethodGetMetas, rmcResponseBody)
rmcResponse := nex.NewRMCSuccess(endpoint, rmcResponseBody)
rmcResponse.ProtocolID = datastore.ProtocolID
rmcResponse.MethodID = datastore.MethodGetMetas
rmcResponse.CallID = callID
rmcResponseBytes := rmcResponse.Bytes()
var responsePacket nex.PacketInterface
if commonDataStoreProtocol.server.PRUDPVersion() == 0 {
responsePacket, _ = nex.NewPacketV0(client, nil)
responsePacket.SetVersion(0)
} else {
responsePacket, _ = nex.NewPacketV1(client, nil)
responsePacket.SetVersion(1)
if commonProtocol.OnAfterGetMetas != nil {
go commonProtocol.OnAfterGetMetas(packet, dataIDs, param)
}
responsePacket.SetSource(packet.Destination())
responsePacket.SetDestination(packet.Source())
responsePacket.SetType(nex.DataPacket)
responsePacket.SetPayload(rmcResponseBytes)
responsePacket.AddFlag(nex.FlagNeedsAck)
responsePacket.AddFlag(nex.FlagReliable)
commonDataStoreProtocol.server.Send(responsePacket)
return 0
return rmcResponse, nil
}

View file

@ -1,56 +1,58 @@
package datastore
import (
nex "github.com/PretendoNetwork/nex-go"
common_globals "github.com/PretendoNetwork/nex-protocols-common-go/globals"
datastore "github.com/PretendoNetwork/nex-protocols-go/datastore"
datastore_types "github.com/PretendoNetwork/nex-protocols-go/datastore/types"
"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 getMetasMultipleParam(err error, packet nex.PacketInterface, callID uint32, params []*datastore_types.DataStoreGetMetaParam) uint32 {
if commonDataStoreProtocol.getObjectInfoByPersistenceTargetWithPasswordHandler == nil {
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 nex.Errors.Core.NotImplemented
return nil, nex.NewError(nex.ResultCodes.Core.NotImplemented, "change_error")
}
if commonDataStoreProtocol.getObjectInfoByDataIDWithPasswordHandler == nil {
if commonProtocol.GetObjectInfoByDataIDWithPassword == nil {
common_globals.Logger.Warning("GetObjectInfoByDataIDWithPassword not defined")
return nex.Errors.Core.NotImplemented
return nil, nex.NewError(nex.ResultCodes.Core.NotImplemented, "change_error")
}
if err != nil {
common_globals.Logger.Error(err.Error())
return nex.Errors.DataStore.Unknown
return nil, nex.NewError(nex.ResultCodes.DataStore.Unknown, "change_error")
}
client := packet.Sender()
connection := packet.Sender()
endpoint := connection.Endpoint()
pMetaInfo := make([]*datastore_types.DataStoreMetaInfo, 0, len(params))
pResults := make([]*nex.Result, 0, len(params))
pMetaInfo := types.NewList[datastore_types.DataStoreMetaInfo]()
pResults := types.NewList[types.QResult]()
for _, param := range params {
var objectInfo *datastore_types.DataStoreMetaInfo
var errCode uint32
var objectInfo datastore_types.DataStoreMetaInfo
var errCode *nex.Error
// * Real server ignores PersistenceTarget if DataID is set
if param.DataID == 0 {
objectInfo, errCode = commonDataStoreProtocol.getObjectInfoByPersistenceTargetWithPasswordHandler(param.PersistenceTarget, param.AccessPassword)
objectInfo, errCode = commonProtocol.GetObjectInfoByPersistenceTargetWithPassword(param.PersistenceTarget, param.AccessPassword)
} else {
objectInfo, errCode = commonDataStoreProtocol.getObjectInfoByDataIDWithPasswordHandler(param.DataID, param.AccessPassword)
objectInfo, errCode = commonProtocol.GetObjectInfoByDataIDWithPassword(param.DataID, param.AccessPassword)
}
if errCode != 0 {
if errCode != nil {
objectInfo = datastore_types.NewDataStoreMetaInfo()
pResults = append(pResults, nex.NewResultError(errCode))
pResults = append(pResults, types.NewQResultError(errCode.ResultCode))
} else {
errCode = commonDataStoreProtocol.VerifyObjectPermission(objectInfo.OwnerID, client.PID(), objectInfo.Permission)
if errCode != 0 {
errCode = commonProtocol.VerifyObjectPermission(objectInfo.OwnerID, connection.PID(), objectInfo.Permission)
if errCode != nil {
objectInfo = datastore_types.NewDataStoreMetaInfo()
pResults = append(pResults, nex.NewResultError(errCode))
pResults = append(pResults, types.NewQResultError(errCode.ResultCode))
} else {
pResults = append(pResults, nex.NewResultSuccess(nex.Errors.DataStore.Unknown))
pResults = append(pResults, types.NewQResultSuccess(nex.ResultCodes.DataStore.Unknown))
}
objectInfo.FilterPropertiesByResultOption(param.ResultOption)
@ -59,37 +61,21 @@ func getMetasMultipleParam(err error, packet nex.PacketInterface, callID uint32,
pMetaInfo = append(pMetaInfo, objectInfo)
}
rmcResponseStream := nex.NewStreamOut(commonDataStoreProtocol.server)
rmcResponseStream := nex.NewByteStreamOut(endpoint.LibraryVersions(), endpoint.ByteStreamSettings())
rmcResponseStream.WriteListStructure(pMetaInfo)
rmcResponseStream.WriteListResult(pResults)
pMetaInfo.WriteTo(rmcResponseStream)
pResults.WriteTo(rmcResponseStream)
rmcResponseBody := rmcResponseStream.Bytes()
rmcResponse := nex.NewRMCResponse(datastore.ProtocolID, callID)
rmcResponse.SetSuccess(datastore.MethodGetMetasMultipleParam, rmcResponseBody)
rmcResponse := nex.NewRMCSuccess(endpoint, rmcResponseBody)
rmcResponse.ProtocolID = datastore.ProtocolID
rmcResponse.MethodID = datastore.MethodGetMetasMultipleParam
rmcResponse.CallID = callID
rmcResponseBytes := rmcResponse.Bytes()
var responsePacket nex.PacketInterface
if commonDataStoreProtocol.server.PRUDPVersion() == 0 {
responsePacket, _ = nex.NewPacketV0(client, nil)
responsePacket.SetVersion(0)
} else {
responsePacket, _ = nex.NewPacketV1(client, nil)
responsePacket.SetVersion(1)
if commonProtocol.OnAfterGetMetasMultipleParam != nil {
go commonProtocol.OnAfterGetMetasMultipleParam(packet, params)
}
responsePacket.SetSource(packet.Destination())
responsePacket.SetDestination(packet.Source())
responsePacket.SetType(nex.DataPacket)
responsePacket.SetPayload(rmcResponseBytes)
responsePacket.AddFlag(nex.FlagNeedsAck)
responsePacket.AddFlag(nex.FlagReliable)
commonDataStoreProtocol.server.Send(responsePacket)
return 0
return rmcResponse, nil
}

View file

@ -1,13 +1,13 @@
package datastore
import (
"github.com/PretendoNetwork/nex-go"
common_globals "github.com/PretendoNetwork/nex-protocols-common-go/globals"
datastore "github.com/PretendoNetwork/nex-protocols-go/datastore"
datastore_types "github.com/PretendoNetwork/nex-protocols-go/datastore/types"
"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 postMetaBinary(err error, packet nex.PacketInterface, callID uint32, param *datastore_types.DataStorePreparePostParam) uint32 {
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
@ -15,69 +15,58 @@ func postMetaBinary(err error, packet nex.PacketInterface, callID uint32, param
// * 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 commonDataStoreProtocol.initializeObjectByPreparePostParamHandler == nil {
if commonProtocol.InitializeObjectByPreparePostParam == nil {
common_globals.Logger.Warning("InitializeObjectByPreparePostParam not defined")
return nex.Errors.Core.NotImplemented
return nil, nex.NewError(nex.ResultCodes.Core.NotImplemented, "change_error")
}
if commonDataStoreProtocol.initializeObjectRatingWithSlotHandler == nil {
if commonProtocol.InitializeObjectRatingWithSlot == nil {
common_globals.Logger.Warning("InitializeObjectRatingWithSlot not defined")
return nex.Errors.Core.NotImplemented
return nil, nex.NewError(nex.ResultCodes.Core.NotImplemented, "change_error")
}
if err != nil {
common_globals.Logger.Error(err.Error())
return nex.Errors.DataStore.Unknown
return nil, nex.NewError(nex.ResultCodes.DataStore.Unknown, "change_error")
}
client := packet.Sender()
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 := commonDataStoreProtocol.initializeObjectByPreparePostParamHandler(client.PID(), param)
if errCode != 0 {
common_globals.Logger.Errorf("Error code %d on object init", errCode)
return errCode
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 = commonDataStoreProtocol.initializeObjectRatingWithSlotHandler(dataID, ratingInitParamWithSlot)
if errCode != 0 {
common_globals.Logger.Errorf("Error code %d on rating init", errCode)
return errCode
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
}
}
rmcResponseStream := nex.NewStreamOut(commonDataStoreProtocol.server)
if errCode != nil {
return nil, errCode
}
rmcResponseStream.WriteUInt64LE(uint64(dataID))
rmcResponseStream := nex.NewByteStreamOut(endpoint.LibraryVersions(), endpoint.ByteStreamSettings())
rmcResponseStream.WriteUInt64LE(dataID)
rmcResponseBody := rmcResponseStream.Bytes()
rmcResponse := nex.NewRMCResponse(datastore.ProtocolID, callID)
rmcResponse.SetSuccess(datastore.MethodPostMetaBinary, rmcResponseBody)
rmcResponse := nex.NewRMCSuccess(endpoint, rmcResponseBody)
rmcResponse.ProtocolID = datastore.ProtocolID
rmcResponse.MethodID = datastore.MethodPostMetaBinary
rmcResponse.CallID = callID
rmcResponseBytes := rmcResponse.Bytes()
var responsePacket nex.PacketInterface
if commonDataStoreProtocol.server.PRUDPVersion() == 0 {
responsePacket, _ = nex.NewPacketV0(client, nil)
responsePacket.SetVersion(0)
} else {
responsePacket, _ = nex.NewPacketV1(client, nil)
responsePacket.SetVersion(1)
if commonProtocol.OnAfterPostMetaBinary != nil {
go commonProtocol.OnAfterPostMetaBinary(packet, param)
}
responsePacket.SetSource(packet.Destination())
responsePacket.SetDestination(packet.Source())
responsePacket.SetType(nex.DataPacket)
responsePacket.SetPayload(rmcResponseBytes)
responsePacket.AddFlag(nex.FlagNeedsAck)
responsePacket.AddFlag(nex.FlagReliable)
commonDataStoreProtocol.server.Send(responsePacket)
return 0
return rmcResponse, nil
}

View file

@ -4,92 +4,87 @@ import (
"fmt"
"time"
nex "github.com/PretendoNetwork/nex-go"
common_globals "github.com/PretendoNetwork/nex-protocols-common-go/globals"
datastore "github.com/PretendoNetwork/nex-protocols-go/datastore"
datastore_types "github.com/PretendoNetwork/nex-protocols-go/datastore/types"
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 prepareGetObject(err error, packet nex.PacketInterface, callID uint32, param *datastore_types.DataStorePrepareGetParam) uint32 {
if commonDataStoreProtocol.getObjectInfoByDataIDHandler == nil {
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 nex.Errors.Core.NotImplemented
return nil, nex.NewError(nex.ResultCodes.Core.NotImplemented, "change_error")
}
if commonDataStoreProtocol.S3Presigner == nil {
if commonProtocol.S3Presigner == nil {
common_globals.Logger.Warning("S3Presigner not defined")
return nex.Errors.Core.NotImplemented
return nil, nex.NewError(nex.ResultCodes.Core.NotImplemented, "change_error")
}
if err != nil {
common_globals.Logger.Error(err.Error())
return nex.Errors.Core.Unknown
return nil, nex.NewError(nex.ResultCodes.Core.Unknown, "change_error")
}
client := packet.Sender()
connection := packet.Sender()
endpoint := connection.Endpoint()
bucket := commonDataStoreProtocol.s3Bucket
key := fmt.Sprintf("%s/%d.bin", commonDataStoreProtocol.s3DataKeyBase, param.DataID)
var objectInfo datastore_types.DataStoreMetaInfo
var errCode *nex.Error
objectInfo, errCode := commonDataStoreProtocol.getObjectInfoByDataIDHandler(param.DataID)
if errCode != 0 {
return errCode
// * 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 = commonDataStoreProtocol.VerifyObjectPermission(objectInfo.OwnerID, client.PID(), objectInfo.Permission)
if errCode != 0 {
return errCode
errCode = commonProtocol.VerifyObjectPermission(objectInfo.OwnerID, connection.PID(), objectInfo.Permission)
if errCode != nil {
return nil, errCode
}
url, err := commonDataStoreProtocol.S3Presigner.GetObject(bucket, key, time.Minute*15)
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 nex.Errors.DataStore.OperationNotAllowed
return nil, nex.NewError(nex.ResultCodes.DataStore.OperationNotAllowed, "change_error")
}
requestHeaders, errCode := commonDataStoreProtocol.s3GetRequestHeadersHandler()
if errCode != 0 {
return errCode
requestHeaders, errCode := commonProtocol.S3GetRequestHeaders()
if errCode != nil {
return nil, errCode
}
pReqGetInfo := datastore_types.NewDataStoreReqGetInfo()
pReqGetInfo.URL = url.String()
pReqGetInfo.RequestHeaders = requestHeaders
pReqGetInfo.Size = objectInfo.Size
pReqGetInfo.RootCACert = commonDataStoreProtocol.rootCACert
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.NewStreamOut(commonDataStoreProtocol.server)
rmcResponseStream := nex.NewByteStreamOut(endpoint.LibraryVersions(), endpoint.ByteStreamSettings())
rmcResponseStream.WriteStructure(pReqGetInfo)
pReqGetInfo.WriteTo(rmcResponseStream)
rmcResponseBody := rmcResponseStream.Bytes()
rmcResponse := nex.NewRMCResponse(datastore.ProtocolID, callID)
rmcResponse.SetSuccess(datastore.MethodPrepareGetObject, rmcResponseBody)
rmcResponse := nex.NewRMCSuccess(endpoint, rmcResponseBody)
rmcResponse.ProtocolID = datastore.ProtocolID
rmcResponse.MethodID = datastore.MethodPrepareGetObject
rmcResponse.CallID = callID
rmcResponseBytes := rmcResponse.Bytes()
var responsePacket nex.PacketInterface
if commonDataStoreProtocol.server.PRUDPVersion() == 0 {
responsePacket, _ = nex.NewPacketV0(client, nil)
responsePacket.SetVersion(0)
} else {
responsePacket, _ = nex.NewPacketV1(client, nil)
responsePacket.SetVersion(1)
if commonProtocol.OnAfterPrepareGetObject != nil {
go commonProtocol.OnAfterPrepareGetObject(packet, param)
}
responsePacket.SetSource(packet.Destination())
responsePacket.SetDestination(packet.Source())
responsePacket.SetType(nex.DataPacket)
responsePacket.SetPayload(rmcResponseBytes)
responsePacket.AddFlag(nex.FlagNeedsAck)
responsePacket.AddFlag(nex.FlagReliable)
commonDataStoreProtocol.server.Send(responsePacket)
return 0
return rmcResponse, nil
}

View file

@ -4,111 +4,102 @@ import (
"fmt"
"time"
nex "github.com/PretendoNetwork/nex-go"
common_globals "github.com/PretendoNetwork/nex-protocols-common-go/globals"
datastore "github.com/PretendoNetwork/nex-protocols-go/datastore"
datastore_types "github.com/PretendoNetwork/nex-protocols-go/datastore/types"
"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 preparePostObject(err error, packet nex.PacketInterface, callID uint32, param *datastore_types.DataStorePreparePostParam) uint32 {
if commonDataStoreProtocol.initializeObjectByPreparePostParamHandler == nil {
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 nex.Errors.Core.NotImplemented
return nil, nex.NewError(nex.ResultCodes.Core.NotImplemented, "change_error")
}
if commonDataStoreProtocol.initializeObjectRatingWithSlotHandler == nil {
if commonProtocol.InitializeObjectRatingWithSlot == nil {
common_globals.Logger.Warning("InitializeObjectRatingWithSlot not defined")
return nex.Errors.Core.NotImplemented
return nil, nex.NewError(nex.ResultCodes.Core.NotImplemented, "change_error")
}
if commonDataStoreProtocol.S3Presigner == nil {
if commonProtocol.S3Presigner == nil {
common_globals.Logger.Warning("S3Presigner not defined")
return nex.Errors.Core.NotImplemented
return nil, nex.NewError(nex.ResultCodes.Core.NotImplemented, "change_error")
}
if err != nil {
common_globals.Logger.Error(err.Error())
return nex.Errors.DataStore.Unknown
return nil, nex.NewError(nex.ResultCodes.DataStore.Unknown, "change_error")
}
client := packet.Sender()
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 := commonDataStoreProtocol.initializeObjectByPreparePostParamHandler(client.PID(), param)
if errCode != 0 {
common_globals.Logger.Errorf("Error code %d on object init", errCode)
return errCode
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 = commonDataStoreProtocol.initializeObjectRatingWithSlotHandler(dataID, ratingInitParamWithSlot)
if errCode != 0 {
common_globals.Logger.Errorf("Error code %d on rating init", errCode)
return errCode
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
}
}
bucket := commonDataStoreProtocol.s3Bucket
key := fmt.Sprintf("%s/%d.bin", commonDataStoreProtocol.s3DataKeyBase, dataID)
URL, formData, err := commonDataStoreProtocol.S3Presigner.PostObject(bucket, key, time.Minute*15)
if err != nil {
common_globals.Logger.Error(err.Error())
return nex.Errors.DataStore.OperationNotAllowed
if errCode != nil {
return nil, errCode
}
requestHeaders, errCode := commonDataStoreProtocol.s3PostRequestHeadersHandler()
if errCode != 0 {
return 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 = dataID
pReqPostInfo.URL = URL.String()
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
pReqPostInfo.FormFields = make([]*datastore_types.DataStoreKeyValue, 0, len(formData))
pReqPostInfo.RootCACert = commonDataStoreProtocol.rootCACert
for key, value := range formData {
field := datastore_types.NewDataStoreKeyValue()
field.Key = key
field.Value = value
field.Key = types.NewString(key)
field.Value = types.NewString(value)
pReqPostInfo.FormFields = append(pReqPostInfo.FormFields, field)
}
rmcResponseStream := nex.NewStreamOut(commonDataStoreProtocol.server)
rmcResponseStream := nex.NewByteStreamOut(endpoint.LibraryVersions(), endpoint.ByteStreamSettings())
rmcResponseStream.WriteStructure(pReqPostInfo)
pReqPostInfo.WriteTo(rmcResponseStream)
rmcResponseBody := rmcResponseStream.Bytes()
rmcResponse := nex.NewRMCResponse(datastore.ProtocolID, callID)
rmcResponse.SetSuccess(datastore.MethodPreparePostObject, rmcResponseBody)
rmcResponse := nex.NewRMCSuccess(endpoint, rmcResponseBody)
rmcResponse.ProtocolID = datastore.ProtocolID
rmcResponse.MethodID = datastore.MethodPreparePostObject
rmcResponse.CallID = callID
rmcResponseBytes := rmcResponse.Bytes()
var responsePacket nex.PacketInterface
if commonDataStoreProtocol.server.PRUDPVersion() == 0 {
responsePacket, _ = nex.NewPacketV0(client, nil)
responsePacket.SetVersion(0)
} else {
responsePacket, _ = nex.NewPacketV1(client, nil)
responsePacket.SetVersion(1)
if commonProtocol.OnAfterPreparePostObject != nil {
go commonProtocol.OnAfterPreparePostObject(packet, param)
}
responsePacket.SetSource(packet.Destination())
responsePacket.SetDestination(packet.Source())
responsePacket.SetType(nex.DataPacket)
responsePacket.SetPayload(rmcResponseBytes)
responsePacket.AddFlag(nex.FlagNeedsAck)
responsePacket.AddFlag(nex.FlagReliable)
commonDataStoreProtocol.server.Send(responsePacket)
return 0
return rmcResponse, nil
}

View file

@ -5,52 +5,60 @@ import (
"slices"
"strings"
nex "github.com/PretendoNetwork/nex-go"
common_globals "github.com/PretendoNetwork/nex-protocols-common-go/globals"
datastore "github.com/PretendoNetwork/nex-protocols-go/datastore"
datastore_super_mario_maker "github.com/PretendoNetwork/nex-protocols-go/datastore/super-mario-maker"
datastore_types "github.com/PretendoNetwork/nex-protocols-go/datastore/types"
"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"
)
var commonDataStoreProtocol *CommonDataStoreProtocol
type CommonDataStoreProtocol struct {
server *nex.Server
DefaultProtocol *datastore.Protocol
SuperMarioMakerProtocol *datastore_super_mario_maker.Protocol
s3Bucket string
s3DataKeyBase string
s3NotifyKeyBase string
rootCACert []byte
minIOClient *minio.Client
S3Presigner S3PresignerInterface
getUserFriendPIDsHandler func(pid uint32) []uint32
getObjectInfoByDataIDHandler func(dataID uint64) (*datastore_types.DataStoreMetaInfo, uint32)
updateObjectPeriodByDataIDWithPasswordHandler func(dataID uint64, dataType uint16, password uint64) uint32
updateObjectMetaBinaryByDataIDWithPasswordHandler func(dataID uint64, metaBinary []byte, password uint64) uint32
updateObjectDataTypeByDataIDWithPasswordHandler func(dataID uint64, period uint16, password uint64) uint32
getObjectSizeByDataIDHandler func(dataID uint64) (uint32, uint32)
updateObjectUploadCompletedByDataIDHandler func(dataID uint64, uploadCompleted bool) uint32
getObjectInfoByPersistenceTargetWithPasswordHandler func(persistenceTarget *datastore_types.DataStorePersistenceTarget, password uint64) (*datastore_types.DataStoreMetaInfo, uint32)
getObjectInfoByDataIDWithPasswordHandler func(dataID uint64, password uint64) (*datastore_types.DataStoreMetaInfo, uint32)
s3GetRequestHeadersHandler func() ([]*datastore_types.DataStoreKeyValue, uint32)
s3PostRequestHeadersHandler func() ([]*datastore_types.DataStoreKeyValue, uint32)
initializeObjectByPreparePostParamHandler func(ownerPID uint32, param *datastore_types.DataStorePreparePostParam) (uint64, uint32)
initializeObjectRatingWithSlotHandler func(dataID uint64, param *datastore_types.DataStoreRatingInitParamWithSlot) uint32
rateObjectWithPasswordHandler func(dataID uint64, slot uint8, ratingValue int32, accessPassword uint64) (*datastore_types.DataStoreRatingInfo, uint32)
deleteObjectByDataIDWithPasswordHandler func(dataID uint64, password uint64) uint32
deleteObjectByDataIDHandler func(dataID uint64) uint32
getObjectInfosByDataStoreSearchParamHandler func(param *datastore_types.DataStoreSearchParam) ([]*datastore_types.DataStoreMetaInfo, uint32, uint32)
getObjectOwnerByDataIDHandler func(dataID uint64) (uint32, uint32)
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 *CommonDataStoreProtocol) S3StatObject(bucket, key string) (minio.ObjectInfo, error) {
func (c *CommonProtocol) S3StatObject(bucket, key string) (minio.ObjectInfo, error) {
return c.minIOClient.StatObject(context.TODO(), bucket, key, minio.StatObjectOptions{})
}
func (c *CommonDataStoreProtocol) S3ObjectSize(bucket, key string) (uint64, error) {
func (c *CommonProtocol) S3ObjectSize(bucket, key string) (uint64, error) {
info, err := c.S3StatObject(bucket, key)
if err != nil {
return 0, err
@ -59,54 +67,50 @@ func (c *CommonDataStoreProtocol) S3ObjectSize(bucket, key string) (uint64, erro
return uint64(info.Size), nil
}
func (c *CommonDataStoreProtocol) VerifyObjectPermission(ownerPID, accessorPID uint32, permission *datastore_types.DataStorePermission) uint32 {
func (c *CommonProtocol) VerifyObjectPermission(ownerPID, accessorPID types.PID, permission datastore_types.DataStorePermission) *nex.Error {
if permission.Permission > 3 {
return nex.Errors.DataStore.InvalidArgument
return nex.NewError(nex.ResultCodes.DataStore.InvalidArgument, "change_error")
}
// * Owner can always access their own objects
if ownerPID == accessorPID {
return 0
if ownerPID.Equals(accessorPID) {
return nil
}
// * Allow anyone
if permission.Permission == 0 {
return 0
return nil
}
// * Allow only friends of the owner
if permission.Permission == 1 {
friendsList := c.getUserFriendPIDsHandler(ownerPID)
// TODO - This assumes a legacy client. Will not work on the Switch
friendsList := c.GetUserFriendPIDs(uint32(ownerPID))
if !slices.Contains(friendsList, accessorPID) {
return nex.Errors.DataStore.PermissionDenied
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 !slices.Contains(permission.RecipientIDs, accessorPID) {
return nex.Errors.DataStore.PermissionDenied
if !permission.RecipientIDs.Contains(accessorPID) {
return nex.NewError(nex.ResultCodes.DataStore.PermissionDenied, "change_error")
}
}
// * Allow only the owner
if permission.Permission == 3 {
if ownerPID != accessorPID {
return nex.Errors.DataStore.PermissionDenied
if !ownerPID.Equals(accessorPID) {
return nex.NewError(nex.ResultCodes.DataStore.PermissionDenied, "change_error")
}
}
return 0
}
// SetS3Bucket sets the S3 bucket
func (c *CommonDataStoreProtocol) SetS3Bucket(bucket string) {
c.s3Bucket = bucket
return nil
}
// SetDataKeyBase sets the base for the key to be used when uploading standard DataStore objects
func (c *CommonDataStoreProtocol) SetDataKeyBase(base string) {
func (c *CommonProtocol) SetDataKeyBase(base string) {
// * Just in case someone passes a badly formatted key
base = strings.TrimPrefix(base, "/")
base = strings.TrimSuffix(base, "/")
@ -114,179 +118,46 @@ func (c *CommonDataStoreProtocol) SetDataKeyBase(base string) {
}
// SetNotifyKeyBase sets the base for the key to be used when uploading DataStore notification data
func (c *CommonDataStoreProtocol) SetNotifyKeyBase(base string) {
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
}
// SetRootCACert sets the S3 root CA
func (c *CommonDataStoreProtocol) SetRootCACert(rootCACert []byte) {
c.rootCACert = rootCACert
}
// SetMinIOClient sets the MinIO S3 client
func (c *CommonDataStoreProtocol) SetMinIOClient(client *minio.Client) {
func (c *CommonProtocol) SetMinIOClient(client *minio.Client) {
c.minIOClient = client
c.SetS3Presigner(NewS3Presigner(c.minIOClient))
c.S3Presigner = NewS3Presigner(c.minIOClient)
}
// SetS3Presigner sets the struct which creates presigned S3 URLs
func (c *CommonDataStoreProtocol) SetS3Presigner(presigner S3PresignerInterface) {
c.S3Presigner = presigner
}
// SetGetUserFriendPIDs sets the handler for a function which gets a list of a users friend PIDs
func (c *CommonDataStoreProtocol) SetGetUserFriendPIDs(handler func(pid uint32) []uint32) {
c.getUserFriendPIDsHandler = handler
}
// GetObjectInfoByDataID sets the GetObjectInfoByDataID handler function
func (c *CommonDataStoreProtocol) GetObjectInfoByDataID(handler func(dataID uint64) (*datastore_types.DataStoreMetaInfo, uint32)) {
c.getObjectInfoByDataIDHandler = handler
}
// UpdateObjectPeriodByDataIDWithPassword sets the UpdateObjectPeriodByDataIDWithPassword handler function
func (c *CommonDataStoreProtocol) UpdateObjectPeriodByDataIDWithPassword(handler func(dataID uint64, dataType uint16, password uint64) uint32) {
c.updateObjectPeriodByDataIDWithPasswordHandler = handler
}
// UpdateObjectMetaBinaryByDataIDWithPassword sets the UpdateObjectMetaBinaryByDataIDWithPassword handler function
func (c *CommonDataStoreProtocol) UpdateObjectMetaBinaryByDataIDWithPassword(handler func(dataID uint64, metaBinary []byte, password uint64) uint32) {
c.updateObjectMetaBinaryByDataIDWithPasswordHandler = handler
}
// UpdateObjectDataTypeByDataIDWithPassword sets the UpdateObjectDataTypeByDataIDWithPassword handler function
func (c *CommonDataStoreProtocol) UpdateObjectDataTypeByDataIDWithPassword(handler func(dataID uint64, period uint16, password uint64) uint32) {
c.updateObjectDataTypeByDataIDWithPasswordHandler = handler
}
// GetObjectSizeDataID sets the GetObjectSizeDataID handler function
func (c *CommonDataStoreProtocol) GetObjectSizeDataID(handler func(dataID uint64) (uint32, uint32)) {
c.getObjectSizeByDataIDHandler = handler
}
// UpdateObjectUploadCompletedByDataID sets the UpdateObjectUploadCompletedByDataID handler function
func (c *CommonDataStoreProtocol) UpdateObjectUploadCompletedByDataID(handler func(dataID uint64, uploadCompleted bool) uint32) {
c.updateObjectUploadCompletedByDataIDHandler = handler
}
// GetObjectInfoByPersistenceTargetWithPassword sets the GetObjectInfoByPersistenceTargetWithPassword handler function
func (c *CommonDataStoreProtocol) GetObjectInfoByPersistenceTargetWithPassword(handler func(persistenceTarget *datastore_types.DataStorePersistenceTarget, password uint64) (*datastore_types.DataStoreMetaInfo, uint32)) {
c.getObjectInfoByPersistenceTargetWithPasswordHandler = handler
}
// GetObjectInfoByDataIDWithPassword sets the GetObjectInfoByDataIDWithPassword handler function
func (c *CommonDataStoreProtocol) GetObjectInfoByDataIDWithPassword(handler func(dataID uint64, password uint64) (*datastore_types.DataStoreMetaInfo, uint32)) {
c.getObjectInfoByDataIDWithPasswordHandler = handler
}
// S3GetRequestHeaders sets the S3GetRequestHeaders handler function
func (c *CommonDataStoreProtocol) S3GetRequestHeaders(handler func() ([]*datastore_types.DataStoreKeyValue, uint32)) {
c.s3GetRequestHeadersHandler = handler
}
// S3PostRequestHeaders sets the S3PostRequestHeaders handler function
func (c *CommonDataStoreProtocol) S3PostRequestHeaders(handler func() ([]*datastore_types.DataStoreKeyValue, uint32)) {
c.s3PostRequestHeadersHandler = handler
}
// InitializeObjectByPreparePostParam sets the InitializeObjectByPreparePostParam handler function
func (c *CommonDataStoreProtocol) InitializeObjectByPreparePostParam(handler func(ownerPID uint32, param *datastore_types.DataStorePreparePostParam) (uint64, uint32)) {
c.initializeObjectByPreparePostParamHandler = handler
}
// InitializeObjectRatingWithSlot sets the InitializeObjectRatingWithSlot handler function
func (c *CommonDataStoreProtocol) InitializeObjectRatingWithSlot(handler func(dataID uint64, param *datastore_types.DataStoreRatingInitParamWithSlot) uint32) {
c.initializeObjectRatingWithSlotHandler = handler
}
// RateObjectWithPassword sets the RateObjectWithPassword handler function
func (c *CommonDataStoreProtocol) RateObjectWithPassword(handler func(dataID uint64, slot uint8, ratingValue int32, accessPassword uint64) (*datastore_types.DataStoreRatingInfo, uint32)) {
c.rateObjectWithPasswordHandler = handler
}
// DeleteObjectByDataIDWithPassword sets the DeleteObjectByDataIDWithPassword handler function
func (c *CommonDataStoreProtocol) DeleteObjectByDataIDWithPassword(handler func(dataID uint64, password uint64) uint32) {
c.deleteObjectByDataIDWithPasswordHandler = handler
}
// DeleteObjectByDataID sets the DeleteObjectByDataID handler function
func (c *CommonDataStoreProtocol) DeleteObjectByDataID(handler func(dataID uint64) uint32) {
c.deleteObjectByDataIDHandler = handler
}
// GetObjectInfosByDataStoreSearchParam sets the GetObjectInfosByDataStoreSearchParam handler function
func (c *CommonDataStoreProtocol) GetObjectInfosByDataStoreSearchParam(handler func(param *datastore_types.DataStoreSearchParam) ([]*datastore_types.DataStoreMetaInfo, uint32, uint32)) {
c.getObjectInfosByDataStoreSearchParamHandler = handler
}
// GetObjectOwnerByDataID sets the GetObjectOwnerByDataID handler function
func (c *CommonDataStoreProtocol) GetObjectOwnerByDataID(handler func(dataID uint64) (uint32, uint32)) {
c.getObjectOwnerByDataIDHandler = handler
}
func initDefault(c *CommonDataStoreProtocol) {
c.DefaultProtocol = datastore.NewProtocol(c.server)
c.DefaultProtocol.DeleteObject(deleteObject)
c.DefaultProtocol.GetMeta(getMeta)
c.DefaultProtocol.GetMetas(getMetas)
c.DefaultProtocol.SearchObject(searchObject)
c.DefaultProtocol.RateObject(rateObject)
c.DefaultProtocol.PostMetaBinary(postMetaBinary)
c.DefaultProtocol.PreparePostObject(preparePostObject)
c.DefaultProtocol.PrepareGetObject(prepareGetObject)
c.DefaultProtocol.CompletePostObject(completePostObject)
c.DefaultProtocol.GetMetasMultipleParam(getMetasMultipleParam)
c.DefaultProtocol.CompletePostObjects(completePostObjects)
c.DefaultProtocol.ChangeMeta(changeMeta)
c.DefaultProtocol.RateObjects(rateObjects)
}
func initSuperMarioMaker(c *CommonDataStoreProtocol) {
c.SuperMarioMakerProtocol = datastore_super_mario_maker.NewProtocol(c.server)
c.SuperMarioMakerProtocol.DeleteObject(deleteObject)
c.SuperMarioMakerProtocol.GetMeta(getMeta)
c.SuperMarioMakerProtocol.GetMetas(getMetas)
c.SuperMarioMakerProtocol.SearchObject(searchObject)
c.SuperMarioMakerProtocol.RateObject(rateObject)
c.SuperMarioMakerProtocol.PostMetaBinary(postMetaBinary)
c.SuperMarioMakerProtocol.PreparePostObject(preparePostObject)
c.SuperMarioMakerProtocol.PrepareGetObject(prepareGetObject)
c.SuperMarioMakerProtocol.CompletePostObject(completePostObject)
c.SuperMarioMakerProtocol.GetMetasMultipleParam(getMetasMultipleParam)
c.SuperMarioMakerProtocol.CompletePostObjects(completePostObjects)
c.SuperMarioMakerProtocol.ChangeMeta(changeMeta)
c.SuperMarioMakerProtocol.RateObjects(rateObjects)
}
// NewCommonDataStoreProtocol returns a new CommonDataStoreProtocol
func NewCommonDataStoreProtocol(server *nex.Server) *CommonDataStoreProtocol {
commonDataStoreProtocol = &CommonDataStoreProtocol{
server: server,
rootCACert: []byte{},
s3GetRequestHeadersHandler: func() ([]*datastore_types.DataStoreKeyValue, uint32) {
return []*datastore_types.DataStoreKeyValue{}, 0
// 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
},
s3PostRequestHeadersHandler: func() ([]*datastore_types.DataStoreKeyValue, uint32) {
return []*datastore_types.DataStoreKeyValue{}, 0
S3PostRequestHeaders: func() ([]datastore_types.DataStoreKeyValue, *nex.Error) {
return []datastore_types.DataStoreKeyValue{}, nil
},
}
patch := server.DataStoreProtocolVersion().GameSpecificPatch
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)
if strings.EqualFold(patch, "AMAJ") {
common_globals.Logger.Info("Using Super Mario Maker DataStore protocol")
initSuperMarioMaker(commonDataStoreProtocol)
} else {
if patch != "" {
common_globals.Logger.Infof("DataStore version patch %q not recognized", patch)
}
common_globals.Logger.Info("Using default DataStore protocol")
initDefault(commonDataStoreProtocol)
}
return commonDataStoreProtocol
return commonProtocol
}

View file

@ -1,43 +1,45 @@
package datastore
import (
"github.com/PretendoNetwork/nex-go"
common_globals "github.com/PretendoNetwork/nex-protocols-common-go/globals"
datastore "github.com/PretendoNetwork/nex-protocols-go/datastore"
datastore_types "github.com/PretendoNetwork/nex-protocols-go/datastore/types"
"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 rateObject(err error, packet nex.PacketInterface, callID uint32, target *datastore_types.DataStoreRatingTarget, param *datastore_types.DataStoreRateObjectParam, fetchRatings bool) uint32 {
if commonDataStoreProtocol.getObjectInfoByDataIDWithPasswordHandler == nil {
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 nex.Errors.Core.NotImplemented
return nil, nex.NewError(nex.ResultCodes.Core.NotImplemented, "change_error")
}
if commonDataStoreProtocol.rateObjectWithPasswordHandler == nil {
if commonProtocol.RateObjectWithPassword == nil {
common_globals.Logger.Warning("RateObjectWithPassword not defined")
return nex.Errors.Core.NotImplemented
return nil, nex.NewError(nex.ResultCodes.Core.NotImplemented, "change_error")
}
if err != nil {
common_globals.Logger.Error(err.Error())
return nex.Errors.DataStore.Unknown
return nil, nex.NewError(nex.ResultCodes.DataStore.Unknown, "change_error")
}
client := packet.Sender()
connection := packet.Sender()
endpoint := connection.Endpoint()
objectInfo, errCode := commonDataStoreProtocol.getObjectInfoByDataIDWithPasswordHandler(target.DataID, param.AccessPassword)
if errCode != 0 {
return errCode
objectInfo, errCode := commonProtocol.GetObjectInfoByDataIDWithPassword(target.DataID, param.AccessPassword)
if errCode != nil {
return nil, errCode
}
errCode = commonDataStoreProtocol.VerifyObjectPermission(objectInfo.OwnerID, client.PID(), objectInfo.Permission)
if errCode != 0 {
return errCode
errCode = commonProtocol.VerifyObjectPermission(objectInfo.OwnerID, connection.PID(), objectInfo.Permission)
if errCode != nil {
return nil, errCode
}
pRating, errCode := commonDataStoreProtocol.rateObjectWithPasswordHandler(target.DataID, target.Slot, param.RatingValue, param.AccessPassword)
if errCode != 0 {
return 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
@ -48,36 +50,20 @@ func rateObject(err error, packet nex.PacketInterface, callID uint32, target *da
pRating = datastore_types.NewDataStoreRatingInfo()
}
rmcResponseStream := nex.NewStreamOut(commonDataStoreProtocol.server)
rmcResponseStream := nex.NewByteStreamOut(endpoint.LibraryVersions(), endpoint.ByteStreamSettings())
rmcResponseStream.WriteStructure(pRating)
pRating.WriteTo(rmcResponseStream)
rmcResponseBody := rmcResponseStream.Bytes()
rmcResponse := nex.NewRMCResponse(datastore.ProtocolID, callID)
rmcResponse.SetSuccess(datastore.MethodRateObject, rmcResponseBody)
rmcResponse := nex.NewRMCSuccess(endpoint, rmcResponseBody)
rmcResponse.ProtocolID = datastore.ProtocolID
rmcResponse.MethodID = datastore.MethodRateObject
rmcResponse.CallID = callID
rmcResponseBytes := rmcResponse.Bytes()
var responsePacket nex.PacketInterface
if commonDataStoreProtocol.server.PRUDPVersion() == 0 {
responsePacket, _ = nex.NewPacketV0(client, nil)
responsePacket.SetVersion(0)
} else {
responsePacket, _ = nex.NewPacketV1(client, nil)
responsePacket.SetVersion(1)
if commonProtocol.OnAfterRateObject != nil {
go commonProtocol.OnAfterRateObject(packet, target, param, fetchRatings)
}
responsePacket.SetSource(packet.Destination())
responsePacket.SetDestination(packet.Source())
responsePacket.SetType(nex.DataPacket)
responsePacket.SetPayload(rmcResponseBytes)
responsePacket.AddFlag(nex.FlagNeedsAck)
responsePacket.AddFlag(nex.FlagReliable)
commonDataStoreProtocol.server.Send(responsePacket)
return 0
return rmcResponse, nil
}

View file

@ -1,58 +1,65 @@
package datastore
import (
nex "github.com/PretendoNetwork/nex-go"
common_globals "github.com/PretendoNetwork/nex-protocols-common-go/globals"
datastore "github.com/PretendoNetwork/nex-protocols-go/datastore"
datastore_types "github.com/PretendoNetwork/nex-protocols-go/datastore/types"
"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 rateObjects(err error, packet nex.PacketInterface, callID uint32, targets []*datastore_types.DataStoreRatingTarget, params []*datastore_types.DataStoreRateObjectParam, transactional bool, fetchRatings bool) uint32 {
if commonDataStoreProtocol.getObjectInfoByDataIDWithPasswordHandler == nil {
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 nex.Errors.Core.NotImplemented
return nil, nex.NewError(nex.ResultCodes.Core.NotImplemented, "change_error")
}
if commonDataStoreProtocol.rateObjectWithPasswordHandler == nil {
if commonProtocol.RateObjectWithPassword == nil {
common_globals.Logger.Warning("RateObjectWithPassword not defined")
return nex.Errors.Core.NotImplemented
return nil, nex.NewError(nex.ResultCodes.Core.NotImplemented, "change_error")
}
if err != nil {
common_globals.Logger.Error(err.Error())
return nex.Errors.DataStore.Unknown
return nil, nex.NewError(nex.ResultCodes.DataStore.Unknown, "change_error")
}
client := packet.Sender()
connection := packet.Sender()
endpoint := connection.Endpoint()
pRatings := make([]*datastore_types.DataStoreRatingInfo, 0)
pResults := make([]*nex.Result, 0)
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 nex.Errors.DataStore.InvalidArgument
return nil, nex.NewError(nex.ResultCodes.DataStore.InvalidArgument, "change_error")
}
for i := 0; i < len(targets); i++ {
target := targets[i]
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 := commonDataStoreProtocol.getObjectInfoByDataIDWithPasswordHandler(target.DataID, param.AccessPassword)
if errCode != 0 {
return errCode
objectInfo, errCode := commonProtocol.GetObjectInfoByDataIDWithPassword(target.DataID, param.AccessPassword)
if errCode != nil {
errorCode = errCode
break
}
errCode = commonDataStoreProtocol.VerifyObjectPermission(objectInfo.OwnerID, client.PID(), objectInfo.Permission)
if errCode != 0 {
return errCode
errCode = commonProtocol.VerifyObjectPermission(objectInfo.OwnerID, connection.PID(), objectInfo.Permission)
if errCode != nil {
errorCode = errCode
break
}
rating, errCode := commonDataStoreProtocol.rateObjectWithPasswordHandler(target.DataID, target.Slot, param.RatingValue, param.AccessPassword)
if errCode != 0 {
return errCode
rating, errCode := commonProtocol.RateObjectWithPassword(target.DataID, target.Slot, param.RatingValue, param.AccessPassword)
if errCode != nil {
errorCode = errCode
break
}
if fetchRatings {
@ -60,37 +67,25 @@ func rateObjects(err error, packet nex.PacketInterface, callID uint32, targets [
}
}
rmcResponseStream := nex.NewStreamOut(commonDataStoreProtocol.server)
if errorCode != nil {
return nil, errorCode
}
rmcResponseStream.WriteListStructure(pRatings)
rmcResponseStream.WriteListResult(pResults) // * pResults is ALWAYS empty in SMM?
rmcResponseStream := nex.NewByteStreamOut(endpoint.LibraryVersions(), endpoint.ByteStreamSettings())
pRatings.WriteTo(rmcResponseStream)
pResults.WriteTo(rmcResponseStream) // * pResults is ALWAYS empty in SMM?
rmcResponseBody := rmcResponseStream.Bytes()
rmcResponse := nex.NewRMCResponse(datastore.ProtocolID, callID)
rmcResponse.SetSuccess(datastore.MethodRateObjects, rmcResponseBody)
rmcResponse := nex.NewRMCSuccess(endpoint, rmcResponseBody)
rmcResponse.ProtocolID = datastore.ProtocolID
rmcResponse.MethodID = datastore.MethodRateObjects
rmcResponse.CallID = callID
rmcResponseBytes := rmcResponse.Bytes()
var responsePacket nex.PacketInterface
if commonDataStoreProtocol.server.PRUDPVersion() == 0 {
responsePacket, _ = nex.NewPacketV0(client, nil)
responsePacket.SetVersion(0)
} else {
responsePacket, _ = nex.NewPacketV1(client, nil)
responsePacket.SetVersion(1)
if commonProtocol.OnAfterRateObjects != nil {
go commonProtocol.OnAfterRateObjects(packet, targets, params, transactional, fetchRatings)
}
responsePacket.SetSource(packet.Destination())
responsePacket.SetDestination(packet.Source())
responsePacket.SetType(nex.DataPacket)
responsePacket.SetPayload(rmcResponseBytes)
responsePacket.AddFlag(nex.FlagNeedsAck)
responsePacket.AddFlag(nex.FlagReliable)
commonDataStoreProtocol.server.Send(responsePacket)
return 0
return rmcResponse, nil
}

View file

@ -1,24 +1,26 @@
package datastore
import (
"github.com/PretendoNetwork/nex-go"
common_globals "github.com/PretendoNetwork/nex-protocols-common-go/globals"
datastore "github.com/PretendoNetwork/nex-protocols-go/datastore"
datastore_types "github.com/PretendoNetwork/nex-protocols-go/datastore/types"
"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 searchObject(err error, packet nex.PacketInterface, callID uint32, param *datastore_types.DataStoreSearchParam) uint32 {
if commonDataStoreProtocol.getObjectInfosByDataStoreSearchParamHandler == nil {
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 nex.Errors.Core.NotImplemented
return nil, nex.NewError(nex.ResultCodes.Core.NotImplemented, "change_error")
}
if err != nil {
common_globals.Logger.Error(err.Error())
return nex.Errors.DataStore.Unknown
return nil, nex.NewError(nex.ResultCodes.DataStore.Unknown, "change_error")
}
client := packet.Sender()
connection := packet.Sender()
endpoint := connection.Endpoint()
// * This is likely game-specific. Also developer note:
// * Please keep in mind that no results is allowed. errCode
@ -27,18 +29,18 @@ func searchObject(err error, packet nex.PacketInterface, callID uint32, param *d
// * 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 := commonDataStoreProtocol.getObjectInfosByDataStoreSearchParamHandler(param)
if errCode != 0 {
return errCode
objects, totalCount, errCode := commonProtocol.GetObjectInfosByDataStoreSearchParam(param, connection.PID())
if errCode != nil {
return nil, errCode
}
pSearchResult := datastore_types.NewDataStoreSearchResult()
pSearchResult.Result = make([]*datastore_types.DataStoreMetaInfo, 0, len(objects))
pSearchResult.Result = types.NewList[datastore_types.DataStoreMetaInfo]()
for _, object := range objects {
errCode = commonDataStoreProtocol.VerifyObjectPermission(object.OwnerID, client.PID(), object.Permission)
if errCode != 0 {
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?
@ -66,46 +68,30 @@ func searchObject(err error, packet nex.PacketInterface, callID uint32, param *d
// *
// * Only seen in struct revision 3 or
// * NEX 4.0+
if param.StructureVersion() >= 3 || commonDataStoreProtocol.server.DataStoreProtocolVersion().GreaterOrEqual("4.0.0") {
if param.StructureVersion >= 3 || endpoint.LibraryVersions().DataStore.GreaterOrEqual("4.0.0") {
if !param.TotalCountEnabled {
totalCount = 0
totalCountType = 3
}
}
pSearchResult.TotalCount = totalCount
pSearchResult.TotalCountType = totalCountType
pSearchResult.TotalCount = types.NewUInt32(totalCount)
pSearchResult.TotalCountType = types.NewUInt8(totalCountType)
rmcResponseStream := nex.NewStreamOut(commonDataStoreProtocol.server)
rmcResponseStream := nex.NewByteStreamOut(endpoint.LibraryVersions(), endpoint.ByteStreamSettings())
rmcResponseStream.WriteStructure(pSearchResult)
pSearchResult.WriteTo(rmcResponseStream)
rmcResponseBody := rmcResponseStream.Bytes()
rmcResponse := nex.NewRMCResponse(datastore.ProtocolID, callID)
rmcResponse.SetSuccess(datastore.MethodSearchObject, rmcResponseBody)
rmcResponse := nex.NewRMCSuccess(endpoint, rmcResponseBody)
rmcResponse.ProtocolID = datastore.ProtocolID
rmcResponse.MethodID = datastore.MethodSearchObject
rmcResponse.CallID = callID
rmcResponseBytes := rmcResponse.Bytes()
var responsePacket nex.PacketInterface
if commonDataStoreProtocol.server.PRUDPVersion() == 0 {
responsePacket, _ = nex.NewPacketV0(client, nil)
responsePacket.SetVersion(0)
} else {
responsePacket, _ = nex.NewPacketV1(client, nil)
responsePacket.SetVersion(1)
if commonProtocol.OnAfterSearchObject != nil {
go commonProtocol.OnAfterSearchObject(packet, param)
}
responsePacket.SetSource(packet.Destination())
responsePacket.SetDestination(packet.Source())
responsePacket.SetType(nex.DataPacket)
responsePacket.SetPayload(rmcResponseBytes)
responsePacket.AddFlag(nex.FlagNeedsAck)
responsePacket.AddFlag(nex.FlagReliable)
commonDataStoreProtocol.server.Send(responsePacket)
return 0
return rmcResponse, nil
}

View file

@ -1,17 +1,27 @@
package common_globals
import (
"github.com/PretendoNetwork/nex-go"
match_making_types "github.com/PretendoNetwork/nex-protocols-go/match-making/types"
"database/sql"
"sync"
"github.com/PretendoNetwork/nex-go/v2"
"github.com/PretendoNetwork/nex-go/v2/types"
)
type CommonMatchmakeSession struct {
GameMatchmakeSession *match_making_types.MatchmakeSession // * Used by the game, contains the current state of the MatchmakeSession
SearchMatchmakeSession *match_making_types.MatchmakeSession // * Used by the server when searching for matches, contains the state of the MatchmakeSession during the search process for easy compares
ConnectionIDs []uint32 // * Players in the room, referenced by their connection IDs. This is used instead of the PID in order to ensure we're talking to the correct client (in case of e.g. multiple logins)
// 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)
}
var Sessions map[uint32]*CommonMatchmakeSession
var GetUserFriendPIDsHandler func(pid uint32) []uint32
var CurrentGatheringID = nex.NewCounter(0)
var CurrentMatchmakingCallID = nex.NewCounter(0)
// NewMatchmakingManager returns a new MatchmakingManager
func NewMatchmakingManager(endpoint *nex.PRUDPEndPoint, db *sql.DB) *MatchmakingManager {
return &MatchmakingManager{
Endpoint: endpoint,
Database: db,
Mutex: &sync.RWMutex{},
}
}

View file

@ -1,608 +0,0 @@
package common_globals
import (
"crypto/rand"
"fmt"
"math"
"strconv"
"strings"
nex "github.com/PretendoNetwork/nex-go"
match_making "github.com/PretendoNetwork/nex-protocols-go/match-making"
match_making_types "github.com/PretendoNetwork/nex-protocols-go/match-making/types"
notifications "github.com/PretendoNetwork/nex-protocols-go/notifications"
notifications_types "github.com/PretendoNetwork/nex-protocols-go/notifications/types"
"golang.org/x/exp/slices"
)
// GetAvailableGatheringID returns a gathering ID which doesn't belong to any session
// Returns 0 if no IDs are available (math.MaxUint32 has been reached)
func GetAvailableGatheringID() uint32 {
if CurrentGatheringID.Value() == math.MaxUint32 {
return 0
}
return CurrentGatheringID.Increment()
}
// FindOtherConnectionID searches a connection ID on the session that isn't the given one
// Returns 0 if no connection ID could be found
func FindOtherConnectionID(excludedConnectionID uint32, gatheringID uint32) uint32 {
for _, connectionID := range Sessions[gatheringID].ConnectionIDs {
if connectionID != excludedConnectionID {
return connectionID
}
}
return 0
}
// RemoveConnectionIDFromSession removes a client from the session
func RemoveConnectionIDFromSession(clientConnectionID uint32, gathering uint32) {
for index, connectionID := range Sessions[gathering].ConnectionIDs {
if connectionID == clientConnectionID {
Sessions[gathering].ConnectionIDs = DeleteIndex(Sessions[gathering].ConnectionIDs, index)
}
}
if len(Sessions[gathering].ConnectionIDs) == 0 {
delete(Sessions, gathering)
}
}
// FindClientSession searches for session the given connection ID is connected to
func FindClientSession(connectionID uint32) uint32 {
for gatheringID := range Sessions {
if slices.Contains(Sessions[gatheringID].ConnectionIDs, connectionID) {
return gatheringID
}
}
return 0
}
// RemoveClientFromAllSessions removes a client from every session
func RemoveClientFromAllSessions(client *nex.Client) {
// * Keep checking until no session is found
for gid := FindClientSession(client.ConnectionID()); gid != 0; {
session := Sessions[gid]
lenParticipants := len(session.ConnectionIDs)
RemoveConnectionIDFromSession(client.ConnectionID(), gid)
if lenParticipants <= 1 {
gid = FindClientSession(client.ConnectionID())
continue
}
ownerPID := session.GameMatchmakeSession.Gathering.OwnerPID
if client.PID() == 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 session.GameMatchmakeSession.Gathering.Flags&match_making.GatheringFlags.DisconnectChangeOwner == 0 {
delete(Sessions, gid)
} else {
ChangeSessionOwner(client, gid)
}
} else {
server := client.Server()
rmcMessage := nex.NewRMCRequest()
rmcMessage.SetProtocolID(notifications.ProtocolID)
rmcMessage.SetCallID(CurrentMatchmakingCallID.Increment())
rmcMessage.SetMethodID(notifications.MethodProcessNotificationEvent)
category := notifications.NotificationCategories.Participation
subtype := notifications.NotificationSubTypes.Participation.Disconnected
oEvent := notifications_types.NewNotificationEvent()
oEvent.PIDSource = client.PID()
oEvent.Type = notifications.BuildNotificationType(category, subtype)
oEvent.Param1 = gid
oEvent.Param2 = client.PID()
stream := nex.NewStreamOut(server)
stream.WriteStructure(oEvent)
rmcMessage.SetParameters(stream.Bytes())
rmcMessageBytes := rmcMessage.Bytes()
targetClient := server.FindClientFromPID(uint32(ownerPID))
if targetClient == nil {
Logger.Warning("Owner client not found")
gid = FindClientSession(client.ConnectionID())
continue
}
var messagePacket nex.PacketInterface
if server.PRUDPVersion() == 0 {
messagePacket, _ = nex.NewPacketV0(targetClient, nil)
messagePacket.SetVersion(0)
} else {
messagePacket, _ = nex.NewPacketV1(targetClient, nil)
messagePacket.SetVersion(1)
}
messagePacket.SetSource(0xA1)
messagePacket.SetDestination(0xAF)
messagePacket.SetType(nex.DataPacket)
messagePacket.SetPayload(rmcMessageBytes)
messagePacket.AddFlag(nex.FlagNeedsAck)
messagePacket.AddFlag(nex.FlagReliable)
server.Send(messagePacket)
}
gid = FindClientSession(client.ConnectionID())
}
}
// CreateSessionByMatchmakeSession creates a gathering from a MatchmakeSession
func CreateSessionByMatchmakeSession(matchmakeSession *match_making_types.MatchmakeSession, searchMatchmakeSession *match_making_types.MatchmakeSession, hostPID uint32) (*CommonMatchmakeSession, error, uint32) {
sessionIndex := GetAvailableGatheringID()
if sessionIndex == 0 {
CurrentGatheringID = nex.NewCounter(0)
sessionIndex = GetAvailableGatheringID()
}
session := CommonMatchmakeSession{
SearchMatchmakeSession: searchMatchmakeSession,
GameMatchmakeSession: matchmakeSession,
}
session.GameMatchmakeSession.Gathering.ID = sessionIndex
session.GameMatchmakeSession.Gathering.OwnerPID = hostPID
session.GameMatchmakeSession.Gathering.HostPID = hostPID
session.GameMatchmakeSession.StartedTime = nex.NewDateTime(0)
session.GameMatchmakeSession.StartedTime.UTC()
session.GameMatchmakeSession.SessionKey = make([]byte, 32)
rand.Read(session.GameMatchmakeSession.SessionKey)
if session.GameMatchmakeSession.MatchmakeParam == nil {
session.GameMatchmakeSession.MatchmakeParam = match_making_types.NewMatchmakeParam()
}
if session.GameMatchmakeSession.MatchmakeParam.Parameters == nil {
session.GameMatchmakeSession.MatchmakeParam.Parameters = make(map[string]*nex.Variant)
}
session.GameMatchmakeSession.MatchmakeParam.Parameters["@SR"] = nex.NewVariant()
session.GameMatchmakeSession.MatchmakeParam.Parameters["@SR"].TypeID = 3
session.GameMatchmakeSession.MatchmakeParam.Parameters["@SR"].Bool = true
session.GameMatchmakeSession.MatchmakeParam.Parameters["@GIR"] = nex.NewVariant()
session.GameMatchmakeSession.MatchmakeParam.Parameters["@GIR"].TypeID = 1
session.GameMatchmakeSession.MatchmakeParam.Parameters["@GIR"].Int64 = 3
Sessions[sessionIndex] = &session
return Sessions[sessionIndex], nil, 0
}
// FindSessionByMatchmakeSession finds a gathering that matches with a MatchmakeSession
func FindSessionByMatchmakeSession(pid uint32, searchMatchmakeSession *match_making_types.MatchmakeSession) uint32 {
// * This portion finds any sessions that match the search session
// * It does not care about anything beyond that, such as if the match is already full
// * This is handled below
candidateSessionIndexes := make([]uint32, 0, len(Sessions))
for index, session := range Sessions {
if session.SearchMatchmakeSession.Equals(searchMatchmakeSession) {
candidateSessionIndexes = append(candidateSessionIndexes, index)
}
}
var friendList []uint32
for _, sessionIndex := range candidateSessionIndexes {
sessionToCheck := Sessions[sessionIndex]
if len(sessionToCheck.ConnectionIDs) >= int(sessionToCheck.GameMatchmakeSession.MaximumParticipants) {
continue
}
if !sessionToCheck.GameMatchmakeSession.OpenParticipation {
continue
}
// If the session only allows friends, check if the owner is in the friend list of the PID
// TODO - Is this a flag or a constant?
if sessionToCheck.GameMatchmakeSession.ParticipationPolicy == 98 {
if GetUserFriendPIDsHandler == nil {
Logger.Warning("Missing GetUserFriendPIDsHandler!")
continue
}
if len(friendList) == 0 {
friendList = GetUserFriendPIDsHandler(pid)
}
if !slices.Contains(friendList, sessionToCheck.GameMatchmakeSession.OwnerPID) {
continue
}
}
return sessionIndex // * Found a match
}
return 0
}
// FindSessionsByMatchmakeSessionSearchCriterias finds a gathering that matches with the given search criteria
func FindSessionsByMatchmakeSessionSearchCriterias(pid uint32, lstSearchCriteria []*match_making_types.MatchmakeSessionSearchCriteria, gameSpecificChecks func(searchCriteria *match_making_types.MatchmakeSessionSearchCriteria, matchmakeSession *match_making_types.MatchmakeSession) bool) []*CommonMatchmakeSession {
candidateSessions := make([]*CommonMatchmakeSession, 0, len(Sessions))
var friendList []uint32
for _, session := range Sessions {
for _, searchCriteria := range lstSearchCriteria {
// * Check things like game specific attributes
if gameSpecificChecks != nil {
if !gameSpecificChecks(searchCriteria, session.GameMatchmakeSession) {
continue
}
} else {
if !compareAttributesSearchCriteria(session.GameMatchmakeSession.Attributes, searchCriteria.Attribs) {
continue
}
}
if !compareSearchCriteria(session.GameMatchmakeSession.MaximumParticipants, searchCriteria.MaxParticipants) {
continue
}
if !compareSearchCriteria(session.GameMatchmakeSession.MinimumParticipants, searchCriteria.MinParticipants) {
continue
}
if !compareSearchCriteria(session.GameMatchmakeSession.GameMode, searchCriteria.GameMode) {
continue
}
if len(session.ConnectionIDs) >= int(session.GameMatchmakeSession.MaximumParticipants) {
continue
}
if !session.GameMatchmakeSession.OpenParticipation {
continue
}
// If the session only allows friends, check if the owner is in the friend list of the PID
// TODO - Is this a flag or a constant?
if session.GameMatchmakeSession.ParticipationPolicy == 98 {
if GetUserFriendPIDsHandler == nil {
Logger.Warning("Missing GetUserFriendPIDsHandler!")
continue
}
if len(friendList) == 0 {
friendList = GetUserFriendPIDsHandler(pid)
}
if !slices.Contains(friendList, session.GameMatchmakeSession.OwnerPID) {
continue
}
}
candidateSessions = append(candidateSessions, session)
// We don't have to compare with other search criterias
break
}
}
return candidateSessions
}
func compareAttributesSearchCriteria(original []uint32, search []string) bool {
if len(original) != len(search) {
return false
}
for index, originalAttribute := range original {
searchAttribute := search[index]
if !compareSearchCriteria(originalAttribute, searchAttribute) {
return false
}
}
return true
}
func compareSearchCriteria[T ~uint16 | ~uint32](original T, search string) bool {
if search == "" { // Accept any value
return true
}
before, after, found := strings.Cut(search, ",")
if found {
min, err := strconv.ParseUint(before, 10, 64)
if err != nil {
return false
}
max, err := strconv.ParseUint(after, 10, 64)
if err != nil {
return false
}
return min <= uint64(original) && max >= uint64(original)
} else {
searchNum, err := strconv.ParseUint(before, 10, 64)
if err != nil {
return false
}
return searchNum == uint64(original)
}
}
// AddPlayersToSession updates the given sessions state to include the provided connection IDs
// Returns a NEX error code if failed
func AddPlayersToSession(session *CommonMatchmakeSession, connectionIDs []uint32, initiatingClient *nex.Client, joinMessage string) (error, uint32) {
if (len(session.ConnectionIDs) + len(connectionIDs)) > int(session.GameMatchmakeSession.Gathering.MaximumParticipants) {
return fmt.Errorf("Gathering %d is full", session.GameMatchmakeSession.Gathering.ID), nex.Errors.RendezVous.SessionFull
}
for _, connectedID := range connectionIDs {
if slices.Contains(session.ConnectionIDs, connectedID) {
return fmt.Errorf("Connection ID %d is already in gathering %d", connectedID, session.GameMatchmakeSession.Gathering.ID), nex.Errors.RendezVous.AlreadyParticipatedGathering
}
session.ConnectionIDs = append(session.ConnectionIDs, connectedID)
session.GameMatchmakeSession.ParticipationCount += 1
}
server := initiatingClient.Server()
for i := 0; i < len(session.ConnectionIDs); i++ {
target := server.FindClientFromConnectionID(session.ConnectionIDs[i])
if target == nil {
// TODO - Error here?
Logger.Warning("Player not found")
continue
}
notificationRequestMessage := nex.NewRMCRequest()
notificationRequestMessage.SetProtocolID(notifications.ProtocolID)
notificationRequestMessage.SetCallID(CurrentMatchmakingCallID.Increment())
notificationRequestMessage.SetMethodID(notifications.MethodProcessNotificationEvent)
notificationCategory := notifications.NotificationCategories.Participation
notificationSubtype := notifications.NotificationSubTypes.Participation.NewParticipant
oEvent := notifications_types.NewNotificationEvent()
oEvent.PIDSource = initiatingClient.PID()
oEvent.Type = notifications.BuildNotificationType(notificationCategory, notificationSubtype)
oEvent.Param1 = session.GameMatchmakeSession.ID
oEvent.Param2 = target.PID()
oEvent.StrParam = joinMessage
oEvent.Param3 = uint32(len(connectionIDs))
notificationStream := nex.NewStreamOut(server)
notificationStream.WriteStructure(oEvent)
notificationRequestMessage.SetParameters(notificationStream.Bytes())
notificationRequestBytes := notificationRequestMessage.Bytes()
var messagePacket nex.PacketInterface
if server.PRUDPVersion() == 0 {
messagePacket, _ = nex.NewPacketV0(target, nil)
messagePacket.SetVersion(0)
} else {
messagePacket, _ = nex.NewPacketV1(target, nil)
messagePacket.SetVersion(1)
}
messagePacket.SetSource(0xA1)
messagePacket.SetDestination(0xAF)
messagePacket.SetType(nex.DataPacket)
messagePacket.SetPayload(notificationRequestBytes)
messagePacket.AddFlag(nex.FlagNeedsAck)
messagePacket.AddFlag(nex.FlagReliable)
server.Send(messagePacket)
}
// This appears to be correct. Tri-Force Heroes uses 3.9.0, and has issues if these notifications are sent
// Minecraft, however, requires these to be sent
// TODO: Check other games both pre and post 3.10.0 and validate
if server.MatchMakingProtocolVersion().GreaterOrEqual("3.10.0") {
for i := 0; i < len(session.ConnectionIDs); i++ {
target := server.FindClientFromConnectionID(session.ConnectionIDs[i])
if target == nil {
// TODO - Error here?
Logger.Warning("Player not found")
continue
}
notificationRequestMessage := nex.NewRMCRequest()
notificationRequestMessage.SetProtocolID(notifications.ProtocolID)
notificationRequestMessage.SetCallID(CurrentMatchmakingCallID.Increment())
notificationRequestMessage.SetMethodID(notifications.MethodProcessNotificationEvent)
notificationCategory := notifications.NotificationCategories.Participation
notificationSubtype := notifications.NotificationSubTypes.Participation.NewParticipant
oEvent := notifications_types.NewNotificationEvent()
oEvent.PIDSource = initiatingClient.PID()
oEvent.Type = notifications.BuildNotificationType(notificationCategory, notificationSubtype)
oEvent.Param1 = session.GameMatchmakeSession.ID
oEvent.Param2 = target.PID()
oEvent.StrParam = joinMessage
oEvent.Param3 = uint32(len(connectionIDs))
notificationStream := nex.NewStreamOut(server)
notificationStream.WriteStructure(oEvent)
notificationRequestMessage.SetParameters(notificationStream.Bytes())
notificationRequestBytes := notificationRequestMessage.Bytes()
var messagePacket nex.PacketInterface
if server.PRUDPVersion() == 0 {
messagePacket, _ = nex.NewPacketV0(initiatingClient, nil)
messagePacket.SetVersion(0)
} else {
messagePacket, _ = nex.NewPacketV1(initiatingClient, nil)
messagePacket.SetVersion(1)
}
messagePacket.SetSource(0xA1)
messagePacket.SetDestination(0xAF)
messagePacket.SetType(nex.DataPacket)
messagePacket.SetPayload(notificationRequestBytes)
messagePacket.AddFlag(nex.FlagNeedsAck)
messagePacket.AddFlag(nex.FlagReliable)
server.Send(messagePacket)
}
notificationRequestMessage := nex.NewRMCRequest()
notificationRequestMessage.SetProtocolID(notifications.ProtocolID)
notificationRequestMessage.SetCallID(CurrentMatchmakingCallID.Increment())
notificationRequestMessage.SetMethodID(notifications.MethodProcessNotificationEvent)
notificationCategory := notifications.NotificationCategories.Participation
notificationSubtype := notifications.NotificationSubTypes.Participation.NewParticipant
oEvent := notifications_types.NewNotificationEvent()
oEvent.PIDSource = initiatingClient.PID()
oEvent.Type = notifications.BuildNotificationType(notificationCategory, notificationSubtype)
oEvent.Param1 = session.GameMatchmakeSession.ID
oEvent.Param2 = initiatingClient.PID()
oEvent.StrParam = joinMessage
oEvent.Param3 = uint32(len(connectionIDs))
notificationStream := nex.NewStreamOut(server)
notificationStream.WriteStructure(oEvent)
notificationRequestMessage.SetParameters(notificationStream.Bytes())
notificationRequestBytes := notificationRequestMessage.Bytes()
var messagePacket nex.PacketInterface
if server.PRUDPVersion() == 0 {
messagePacket, _ = nex.NewPacketV0(initiatingClient, nil)
messagePacket.SetVersion(0)
} else {
messagePacket, _ = nex.NewPacketV1(initiatingClient, nil)
messagePacket.SetVersion(1)
}
messagePacket.SetSource(0xA1)
messagePacket.SetDestination(0xAF)
messagePacket.SetType(nex.DataPacket)
messagePacket.SetPayload(notificationRequestBytes)
messagePacket.AddFlag(nex.FlagNeedsAck)
messagePacket.AddFlag(nex.FlagReliable)
server.Send(messagePacket)
target := server.FindClientFromPID(uint32(session.GameMatchmakeSession.Gathering.OwnerPID))
if target == nil {
// TODO - Error here?
Logger.Warning("Player not found")
return nil, 0
}
if server.PRUDPVersion() == 0 {
messagePacket, _ = nex.NewPacketV0(target, nil)
messagePacket.SetVersion(0)
} else {
messagePacket, _ = nex.NewPacketV1(target, nil)
messagePacket.SetVersion(1)
}
messagePacket.SetSource(0xA1)
messagePacket.SetDestination(0xAF)
messagePacket.SetType(nex.DataPacket)
messagePacket.SetPayload(notificationRequestBytes)
messagePacket.AddFlag(nex.FlagNeedsAck)
messagePacket.AddFlag(nex.FlagReliable)
server.Send(messagePacket)
}
return nil, 0
}
// ChangeSessionOwner changes the session owner to a different client
func ChangeSessionOwner(ownerClient *nex.Client, gathering uint32) {
server := ownerClient.Server()
var otherClient *nex.Client
otherConnectionID := FindOtherConnectionID(ownerClient.ConnectionID(), gathering)
if otherConnectionID != 0 {
otherClient = server.FindClientFromConnectionID(uint32(otherConnectionID))
if otherClient != nil {
Sessions[gathering].GameMatchmakeSession.Gathering.OwnerPID = otherClient.PID()
} else {
Logger.Warning("Other client not found")
return
}
} else {
return
}
rmcMessage := nex.NewRMCRequest()
rmcMessage.SetProtocolID(notifications.ProtocolID)
rmcMessage.SetCallID(CurrentMatchmakingCallID.Increment())
rmcMessage.SetMethodID(notifications.MethodProcessNotificationEvent)
category := notifications.NotificationCategories.OwnershipChanged
subtype := notifications.NotificationSubTypes.OwnershipChanged.None
oEvent := notifications_types.NewNotificationEvent()
oEvent.PIDSource = otherClient.PID()
oEvent.Type = notifications.BuildNotificationType(category, subtype)
oEvent.Param1 = gathering
oEvent.Param2 = otherClient.PID()
// 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)
stream := nex.NewStreamOut(server)
stream.WriteStructure(oEvent)
rmcMessage.SetParameters(stream.Bytes())
rmcRequestBytes := rmcMessage.Bytes()
for _, connectionID := range Sessions[gathering].ConnectionIDs {
targetClient := server.FindClientFromConnectionID(connectionID)
if targetClient != nil {
var messagePacket nex.PacketInterface
if server.PRUDPVersion() == 0 {
messagePacket, _ = nex.NewPacketV0(targetClient, nil)
messagePacket.SetVersion(0)
} else {
messagePacket, _ = nex.NewPacketV1(targetClient, nil)
messagePacket.SetVersion(1)
}
messagePacket.SetSource(0xA1)
messagePacket.SetDestination(0xAF)
messagePacket.SetType(nex.DataPacket)
messagePacket.SetPayload(rmcRequestBytes)
messagePacket.AddFlag(nex.FlagNeedsAck)
messagePacket.AddFlag(nex.FlagReliable)
server.Send(messagePacket)
} else {
Logger.Warning("Client not found")
}
}
}

View file

@ -1,7 +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)
}
}

51
go.mod
View file

@ -1,37 +1,40 @@
module github.com/PretendoNetwork/nex-protocols-common-go
module github.com/PretendoNetwork/nex-protocols-common-go/v2
go 1.19
go 1.22.1
toolchain go1.22.4
require (
github.com/PretendoNetwork/nex-go v1.0.41
github.com/PretendoNetwork/nex-protocols-go v1.0.58
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/minio/minio-go/v7 v7.0.63
golang.org/x/exp v0.0.0-20231006140011-7918f672742d
github.com/PretendoNetwork/pq-extended v1.0.0
github.com/minio/minio-go/v7 v7.0.83
)
require (
github.com/dolthub/maphash v0.1.0 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/fatih/color v1.15.0 // indirect
github.com/google/uuid v1.4.0 // indirect
github.com/json-iterator/go v1.1.12 // 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.2 // indirect
github.com/klauspost/cpuid/v2 v2.2.5 // indirect
github.com/mattn/go-colorable v0.1.13 // 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/minio/sha256-simd v1.0.1 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/rs/xid v1.5.0 // indirect
github.com/sirupsen/logrus v1.9.3 // 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.14.0 // indirect
golang.org/x/mod v0.13.0 // indirect
golang.org/x/net v0.17.0 // indirect
golang.org/x/sys v0.13.0 // indirect
golang.org/x/term v0.13.0 // indirect
golang.org/x/text v0.13.0 // indirect
gopkg.in/ini.v1 v1.67.0 // 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
)

113
go.sum
View file

@ -1,80 +1,75 @@
github.com/PretendoNetwork/nex-go v1.0.41 h1:TcBb1Bpe2KAB+AXaGX1K9NVQBRtZJIoy4yCvRde2xbI=
github.com/PretendoNetwork/nex-go v1.0.41/go.mod h1:QwHEa165DeVd0xIuthrgc3j6NWXT8lyPSG6t5kC/Shk=
github.com/PretendoNetwork/nex-protocols-go v1.0.58 h1:W8pZ2LOlJm/mo/UOJjkLWNVHs0jQmkoa8qoI5A1WVyY=
github.com/PretendoNetwork/nex-protocols-go v1.0.58/go.mod h1:136762CMIcAsTZDrt4W7gDE4ppiBuc9zN2QFOHESjS8=
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/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
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.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs=
github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw=
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4=
github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
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.2 h1:RlWWUY/Dr4fL8qk9YG7DTZ7PDgME2V4csBXA8L/ixi4=
github.com/klauspost/compress v1.17.2/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
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.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg=
github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
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/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
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.63 h1:GbZ2oCvaUdgT5640WJOpyDhhDxvknAJU2/T3yurwcbQ=
github.com/minio/minio-go/v7 v7.0.63/go.mod h1:Q6X7Qjb7WMhvG65qKf4gUgA5XaiSox74kR1uAEjxRS4=
github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM=
github.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
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/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc=
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
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.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI=
golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo=
golang.org/x/mod v0.13.0 h1:I/DsJXRlw/8l/0c24sM9yb0T4z9liZTduXvdAWYiysY=
golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
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-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
golang.org/x/sys v0.13.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.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek=
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
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

@ -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

@ -1,93 +1,99 @@
package matchmake_extension
import (
nex "github.com/PretendoNetwork/nex-go"
common_globals "github.com/PretendoNetwork/nex-protocols-common-go/globals"
match_making_types "github.com/PretendoNetwork/nex-protocols-go/match-making/types"
matchmake_extension "github.com/PretendoNetwork/nex-protocols-go/matchmake-extension"
"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 autoMatchmake_Postpone(err error, packet nex.PacketInterface, callID uint32, anyGathering *nex.DataHolder, message string) uint32 {
if commonMatchmakeExtensionProtocol.cleanupSearchMatchmakeSessionHandler == nil {
common_globals.Logger.Warning("MatchmakeExtension::AutoMatchmake_Postpone missing CleanupSearchMatchmakeSessionHandler!")
return nex.Errors.Core.NotImplemented
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 nex.Errors.Core.InvalidArgument
return nil, nex.NewError(nex.ResultCodes.Core.InvalidArgument, "change_error")
}
server := commonMatchmakeExtensionProtocol.server
client := packet.Sender()
if len(message) > 256 {
return nil, nex.NewError(nex.ResultCodes.Core.InvalidArgument, "change_error")
}
// A client may disconnect from a session without leaving reliably,
// so let's make sure the client is removed from the session
common_globals.RemoveClientFromAllSessions(client)
connection := packet.Sender().(*nex.PRUDPConnection)
endpoint := connection.Endpoint().(*nex.PRUDPEndPoint)
var matchmakeSession *match_making_types.MatchmakeSession
anyGatheringDataType := anyGathering.TypeName()
commonProtocol.manager.Mutex.Lock()
if anyGatheringDataType == "MatchmakeSession" {
matchmakeSession = anyGathering.ObjectData().(*match_making_types.MatchmakeSession)
// * 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?!")
return nex.Errors.Core.InvalidArgument
commonProtocol.manager.Mutex.Unlock()
return nil, nex.NewError(nex.ResultCodes.Core.InvalidArgument, "change_error")
}
searchMatchmakeSession := matchmakeSession.Copy().(*match_making_types.MatchmakeSession)
commonMatchmakeExtensionProtocol.cleanupSearchMatchmakeSessionHandler(searchMatchmakeSession)
sessionIndex := common_globals.FindSessionByMatchmakeSession(client.PID(), searchMatchmakeSession)
var session *common_globals.CommonMatchmakeSession
if !common_globals.CheckValidMatchmakeSession(matchmakeSession) {
commonProtocol.manager.Mutex.Unlock()
return nil, nex.NewError(nex.ResultCodes.Core.InvalidArgument, "change_error")
}
if sessionIndex == 0 {
var errCode uint32
session, err, errCode = common_globals.CreateSessionByMatchmakeSession(matchmakeSession, searchMatchmakeSession, client.PID())
if err != nil {
common_globals.Logger.Error(err.Error())
return errCode
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
}
} else {
session = common_globals.Sessions[sessionIndex]
}
err, errCode := common_globals.AddPlayersToSession(session, []uint32{client.ConnectionID()}, client, message)
if err != nil {
common_globals.Logger.Error(err.Error())
return errCode
participants, nexError := database.JoinMatchmakeSession(commonProtocol.manager, *resultSession, connection, 1, string(message))
if nexError != nil {
commonProtocol.manager.Mutex.Unlock()
return nil, nexError
}
rmcResponseStream := nex.NewStreamOut(server)
matchmakeDataHolder := nex.NewDataHolder()
matchmakeDataHolder.SetTypeName("MatchmakeSession")
matchmakeDataHolder.SetObjectData(session.GameMatchmakeSession)
rmcResponseStream.WriteDataHolder(matchmakeDataHolder)
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.NewRMCResponse(matchmake_extension.ProtocolID, callID)
rmcResponse.SetSuccess(matchmake_extension.MethodAutoMatchmakePostpone, rmcResponseBody)
rmcResponse := nex.NewRMCSuccess(endpoint, rmcResponseBody)
rmcResponse.ProtocolID = matchmake_extension.ProtocolID
rmcResponse.MethodID = matchmake_extension.MethodAutoMatchmakePostpone
rmcResponse.CallID = callID
rmcResponseBytes := rmcResponse.Bytes()
var responsePacket nex.PacketInterface
if server.PRUDPVersion() == 0 {
responsePacket, _ = nex.NewPacketV0(client, nil)
responsePacket.SetVersion(0)
} else {
responsePacket, _ = nex.NewPacketV1(client, nil)
responsePacket.SetVersion(1)
if commonProtocol.OnAfterAutoMatchmakePostpone != nil {
go commonProtocol.OnAfterAutoMatchmakePostpone(packet, anyGathering, message)
}
responsePacket.SetSource(packet.Destination())
responsePacket.SetDestination(packet.Source())
responsePacket.SetType(nex.DataPacket)
responsePacket.SetPayload(rmcResponseBytes)
responsePacket.AddFlag(nex.FlagNeedsAck)
responsePacket.AddFlag(nex.FlagReliable)
server.Send(responsePacket)
return 0
return rmcResponse, nil
}

View file

@ -1,79 +1,105 @@
package matchmake_extension
import (
nex "github.com/PretendoNetwork/nex-go"
common_globals "github.com/PretendoNetwork/nex-protocols-common-go/globals"
match_making_types "github.com/PretendoNetwork/nex-protocols-go/match-making/types"
matchmake_extension "github.com/PretendoNetwork/nex-protocols-go/matchmake-extension"
"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 autoMatchmakeWithParam_Postpone(err error, packet nex.PacketInterface, callID uint32, autoMatchmakeParam *match_making_types.AutoMatchmakeParam) uint32 {
if err != nil {
common_globals.Logger.Error(err.Error())
return nex.Errors.Core.InvalidArgument
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")
}
server := commonMatchmakeExtensionProtocol.server
client := packet.Sender()
if err != nil {
common_globals.Logger.Error(err.Error())
return nil, nex.NewError(nex.ResultCodes.Core.InvalidArgument, "change_error")
}
// A client may disconnect from a session without leaving reliably,
// so let's make sure the client is removed from the session
common_globals.RemoveClientFromAllSessions(client)
if !common_globals.CheckValidMatchmakeSession(autoMatchmakeParam.SourceMatchmakeSession) {
return nil, nex.NewError(nex.ResultCodes.Core.InvalidArgument, "change_error")
}
var matchmakeSession *match_making_types.MatchmakeSession
matchmakeSession = autoMatchmakeParam.SourceMatchmakeSession
if len(autoMatchmakeParam.JoinMessage) > 256 {
return nil, nex.NewError(nex.ResultCodes.Core.InvalidArgument, "change_error")
}
sessions := common_globals.FindSessionsByMatchmakeSessionSearchCriterias(client.PID(), autoMatchmakeParam.LstSearchCriteria, commonMatchmakeExtensionProtocol.gameSpecificMatchmakeSessionSearchCriteriaChecksHandler)
var session *common_globals.CommonMatchmakeSession
connection := packet.Sender().(*nex.PRUDPConnection)
endpoint := connection.Endpoint().(*nex.PRUDPEndPoint)
if len(sessions) == 0 {
var errCode uint32
session, err, errCode = common_globals.CreateSessionByMatchmakeSession(matchmakeSession, nil, client.PID())
if err != nil {
common_globals.Logger.Error(err.Error())
return errCode
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 {
session = sessions[0]
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")
}
}
err, errCode := common_globals.AddPlayersToSession(session, []uint32{client.ConnectionID()}, client, "")
if err != nil {
common_globals.Logger.Error(err.Error())
return errCode
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
}
rmcResponseStream := nex.NewStreamOut(server)
matchmakeDataHolder := nex.NewDataHolder()
matchmakeDataHolder.SetTypeName("MatchmakeSession")
matchmakeDataHolder.SetObjectData(session.GameMatchmakeSession)
rmcResponseStream.WriteStructure(session.GameMatchmakeSession)
resultSession.ParticipationCount = types.NewUInt32(participants)
commonProtocol.manager.Mutex.Unlock()
rmcResponseStream := nex.NewByteStreamOut(endpoint.LibraryVersions(), endpoint.ByteStreamSettings())
resultSession.WriteTo(rmcResponseStream)
rmcResponseBody := rmcResponseStream.Bytes()
rmcResponse := nex.NewRMCResponse(matchmake_extension.ProtocolID, callID)
rmcResponse.SetSuccess(matchmake_extension.MethodAutoMatchmakeWithParamPostpone, rmcResponseBody)
rmcResponse := nex.NewRMCSuccess(endpoint, rmcResponseBody)
rmcResponse.ProtocolID = matchmake_extension.ProtocolID
rmcResponse.MethodID = matchmake_extension.MethodAutoMatchmakeWithParamPostpone
rmcResponse.CallID = callID
rmcResponseBytes := rmcResponse.Bytes()
var responsePacket nex.PacketInterface
if server.PRUDPVersion() == 0 {
responsePacket, _ = nex.NewPacketV0(client, nil)
responsePacket.SetVersion(0)
} else {
responsePacket, _ = nex.NewPacketV1(client, nil)
responsePacket.SetVersion(1)
if commonProtocol.OnAfterAutoMatchmakeWithParamPostpone != nil {
go commonProtocol.OnAfterAutoMatchmakeWithParamPostpone(packet, autoMatchmakeParam)
}
responsePacket.SetSource(packet.Destination())
responsePacket.SetDestination(packet.Source())
responsePacket.SetType(nex.DataPacket)
responsePacket.SetPayload(rmcResponseBytes)
responsePacket.AddFlag(nex.FlagNeedsAck)
responsePacket.AddFlag(nex.FlagReliable)
server.Send(responsePacket)
return 0
return rmcResponse, nil
}

View file

@ -1,87 +1,118 @@
package matchmake_extension
import (
nex "github.com/PretendoNetwork/nex-go"
common_globals "github.com/PretendoNetwork/nex-protocols-common-go/globals"
match_making_types "github.com/PretendoNetwork/nex-protocols-go/match-making/types"
matchmake_extension "github.com/PretendoNetwork/nex-protocols-go/matchmake-extension"
"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 autoMatchmakeWithSearchCriteria_Postpone(err error, packet nex.PacketInterface, callID uint32, lstSearchCriteria []*match_making_types.MatchmakeSessionSearchCriteria, anyGathering *nex.DataHolder, message string) uint32 {
if err != nil {
common_globals.Logger.Error(err.Error())
return nex.Errors.Core.InvalidArgument
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")
}
server := commonMatchmakeExtensionProtocol.server
client := packet.Sender()
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
common_globals.RemoveClientFromAllSessions(client)
database.EndMatchmakeSessionsParticipation(commonProtocol.manager, connection)
var matchmakeSession *match_making_types.MatchmakeSession
anyGatheringDataType := anyGathering.TypeName()
var matchmakeSession match_making_types.MatchmakeSession
if anyGatheringDataType == "MatchmakeSession" {
matchmakeSession = anyGathering.ObjectData().(*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?!")
return nex.Errors.Core.InvalidArgument
commonProtocol.manager.Mutex.Unlock()
return nil, nex.NewError(nex.ResultCodes.Core.InvalidArgument, "change_error")
}
sessions := common_globals.FindSessionsByMatchmakeSessionSearchCriterias(client.PID(), lstSearchCriteria, commonMatchmakeExtensionProtocol.gameSpecificMatchmakeSessionSearchCriteriaChecksHandler)
var session *common_globals.CommonMatchmakeSession
if !common_globals.CheckValidMatchmakeSession(matchmakeSession) {
commonProtocol.manager.Mutex.Unlock()
return nil, nex.NewError(nex.ResultCodes.Core.InvalidArgument, "change_error")
}
if len(sessions) == 0 {
var errCode uint32
session, err, errCode = common_globals.CreateSessionByMatchmakeSession(matchmakeSession, nil, client.PID())
if err != nil {
common_globals.Logger.Error(err.Error())
return errCode
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 {
session = sessions[0]
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")
}
}
err, errCode := common_globals.AddPlayersToSession(session, []uint32{client.ConnectionID()}, client, message)
if err != nil {
common_globals.Logger.Error(err.Error())
return errCode
var vacantParticipants uint16 = 1
if len(lstSearchCriteria) > 0 {
vacantParticipants = uint16(lstSearchCriteria[0].VacantParticipants)
}
rmcResponseStream := nex.NewStreamOut(server)
matchmakeDataHolder := nex.NewDataHolder()
matchmakeDataHolder.SetTypeName("MatchmakeSession")
matchmakeDataHolder.SetObjectData(session.GameMatchmakeSession)
rmcResponseStream.WriteDataHolder(matchmakeDataHolder)
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.NewRMCResponse(matchmake_extension.ProtocolID, callID)
rmcResponse.SetSuccess(matchmake_extension.MethodAutoMatchmakeWithSearchCriteriaPostpone, rmcResponseBody)
rmcResponse := nex.NewRMCSuccess(endpoint, rmcResponseBody)
rmcResponse.ProtocolID = matchmake_extension.ProtocolID
rmcResponse.MethodID = matchmake_extension.MethodAutoMatchmakeWithSearchCriteriaPostpone
rmcResponse.CallID = callID
rmcResponseBytes := rmcResponse.Bytes()
var responsePacket nex.PacketInterface
if server.PRUDPVersion() == 0 {
responsePacket, _ = nex.NewPacketV0(client, nil)
responsePacket.SetVersion(0)
} else {
responsePacket, _ = nex.NewPacketV1(client, nil)
responsePacket.SetVersion(1)
if commonProtocol.OnAfterAutoMatchmakeWithSearchCriteriaPostpone != nil {
go commonProtocol.OnAfterAutoMatchmakeWithSearchCriteriaPostpone(packet, lstSearchCriteria, anyGathering, strMessage)
}
responsePacket.SetSource(packet.Destination())
responsePacket.SetDestination(packet.Source())
responsePacket.SetType(nex.DataPacket)
responsePacket.SetPayload(rmcResponseBytes)
responsePacket.AddFlag(nex.FlagNeedsAck)
responsePacket.AddFlag(nex.FlagReliable)
server.Send(responsePacket)
return 0
return rmcResponse, nil
}

View file

@ -1,75 +1,69 @@
package matchmake_extension
import (
nex "github.com/PretendoNetwork/nex-go"
common_globals "github.com/PretendoNetwork/nex-protocols-common-go/globals"
match_making_types "github.com/PretendoNetwork/nex-protocols-go/match-making/types"
matchmake_extension "github.com/PretendoNetwork/nex-protocols-go/matchmake-extension"
"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 browseMatchmakeSession(err error, packet nex.PacketInterface, callID uint32, searchCriteria *match_making_types.MatchmakeSessionSearchCriteria, resultRange *nex.ResultRange) uint32 {
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 nex.Errors.Core.InvalidArgument
return nil, nex.NewError(nex.ResultCodes.Core.InvalidArgument, "change_error")
}
server := commonMatchmakeExtensionProtocol.server
client := packet.Sender()
connection := packet.Sender().(*nex.PRUDPConnection)
endpoint := connection.Endpoint().(*nex.PRUDPEndPoint)
searchCriterias := []*match_making_types.MatchmakeSessionSearchCriteria{searchCriteria}
commonProtocol.manager.Mutex.RLock()
sessions := common_globals.FindSessionsByMatchmakeSessionSearchCriterias(client.PID(), searchCriterias, commonMatchmakeExtensionProtocol.gameSpecificMatchmakeSessionSearchCriteriaChecksHandler)
searchCriterias := []match_making_types.MatchmakeSessionSearchCriteria{searchCriteria}
if len(sessions) < int(resultRange.Offset) {
return nex.Errors.Core.InvalidIndex
lstSearchCriteria := types.NewList[match_making_types.MatchmakeSessionSearchCriteria]()
lstSearchCriteria = searchCriterias
if commonProtocol.CleanupMatchmakeSessionSearchCriterias != nil {
commonProtocol.CleanupMatchmakeSessionSearchCriterias(lstSearchCriteria)
}
sessions = sessions[resultRange.Offset:]
if len(sessions) > int(resultRange.Length) {
sessions = sessions[:resultRange.Length]
sessions, nexError := database.FindMatchmakeSessionBySearchCriteria(commonProtocol.manager, connection, searchCriterias, resultRange, nil)
if nexError != nil {
commonProtocol.manager.Mutex.RUnlock()
return nil, nexError
}
lstGathering := make([]*nex.DataHolder, len(sessions))
lstGathering := types.NewList[match_making_types.GatheringHolder]()
for _, session := range sessions {
matchmakeSessionDataHolder := nex.NewDataHolder()
matchmakeSessionDataHolder.SetTypeName("MatchmakeSession")
matchmakeSessionDataHolder.SetObjectData(session.GameMatchmakeSession)
// * 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)
}
rmcResponseStream := nex.NewStreamOut(server)
commonProtocol.manager.Mutex.RUnlock()
rmcResponseStream.WriteListDataHolder(lstGathering)
rmcResponseStream := nex.NewByteStreamOut(endpoint.LibraryVersions(), endpoint.ByteStreamSettings())
lstGathering.WriteTo(rmcResponseStream)
rmcResponseBody := rmcResponseStream.Bytes()
rmcResponse := nex.NewRMCResponse(matchmake_extension.ProtocolID, callID)
rmcResponse.SetSuccess(matchmake_extension.MethodBrowseMatchmakeSession, rmcResponseBody)
rmcResponse := nex.NewRMCSuccess(endpoint, rmcResponseBody)
rmcResponse.ProtocolID = matchmake_extension.ProtocolID
rmcResponse.MethodID = matchmake_extension.MethodBrowseMatchmakeSession
rmcResponse.CallID = callID
rmcResponseBytes := rmcResponse.Bytes()
var responsePacket nex.PacketInterface
if server.PRUDPVersion() == 0 {
responsePacket, _ = nex.NewPacketV0(client, nil)
responsePacket.SetVersion(0)
} else {
responsePacket, _ = nex.NewPacketV1(client, nil)
responsePacket.SetVersion(1)
if commonProtocol.OnAfterBrowseMatchmakeSession != nil {
go commonProtocol.OnAfterBrowseMatchmakeSession(packet, searchCriteria, resultRange)
}
responsePacket.SetSource(packet.Destination())
responsePacket.SetDestination(packet.Source())
responsePacket.SetType(nex.DataPacket)
responsePacket.SetPayload(rmcResponseBytes)
responsePacket.AddFlag(nex.FlagNeedsAck)
responsePacket.AddFlag(nex.FlagReliable)
server.Send(responsePacket)
return 0
return rmcResponse, nil
}

View file

@ -1,57 +1,51 @@
package matchmake_extension
import (
nex "github.com/PretendoNetwork/nex-go"
common_globals "github.com/PretendoNetwork/nex-protocols-common-go/globals"
matchmake_extension "github.com/PretendoNetwork/nex-protocols-go/matchmake-extension"
"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 closeParticipation(err error, packet nex.PacketInterface, callID uint32, gid uint32) uint32 {
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 nex.Errors.Core.InvalidArgument
return nil, nex.NewError(nex.ResultCodes.Core.InvalidArgument, "change_error")
}
client := packet.Sender()
connection := packet.Sender().(*nex.PRUDPConnection)
endpoint := connection.Endpoint().(*nex.PRUDPEndPoint)
var session *common_globals.CommonMatchmakeSession
var ok bool
if session, ok = common_globals.Sessions[gid]; !ok {
return nex.Errors.RendezVous.SessionVoid
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.GameMatchmakeSession.Gathering.OwnerPID != client.PID() {
return nex.Errors.RendezVous.PermissionDenied
if !session.Gathering.OwnerPID.Equals(connection.PID()) {
commonProtocol.manager.Mutex.Unlock()
return nil, nex.NewError(nex.ResultCodes.RendezVous.PermissionDenied, "change_error")
}
session.GameMatchmakeSession.OpenParticipation = false
server := commonMatchmakeExtensionProtocol.server
rmcResponse := nex.NewRMCResponse(matchmake_extension.ProtocolID, callID)
rmcResponse.SetSuccess(matchmake_extension.MethodCloseParticipation, nil)
rmcResponseBytes := rmcResponse.Bytes()
var responsePacket nex.PacketInterface
if server.PRUDPVersion() == 0 {
responsePacket, _ = nex.NewPacketV0(client, nil)
responsePacket.SetVersion(0)
} else {
responsePacket, _ = nex.NewPacketV1(client, nil)
responsePacket.SetVersion(1)
nexError = database.UpdateParticipation(commonProtocol.manager, uint32(gid), false)
if nexError != nil {
commonProtocol.manager.Mutex.Unlock()
return nil, nexError
}
responsePacket.SetSource(packet.Destination())
responsePacket.SetDestination(packet.Source())
responsePacket.SetType(nex.DataPacket)
responsePacket.SetPayload(rmcResponseBytes)
commonProtocol.manager.Mutex.Unlock()
responsePacket.AddFlag(nex.FlagNeedsAck)
responsePacket.AddFlag(nex.FlagReliable)
rmcResponse := nex.NewRMCSuccess(endpoint, nil)
rmcResponse.ProtocolID = matchmake_extension.ProtocolID
rmcResponse.MethodID = matchmake_extension.MethodCloseParticipation
rmcResponse.CallID = callID
server.Send(responsePacket)
if commonProtocol.OnAfterCloseParticipation != nil {
go commonProtocol.OnAfterCloseParticipation(packet, gid)
}
return 0
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

@ -1,80 +1,85 @@
package matchmake_extension
import (
nex "github.com/PretendoNetwork/nex-go"
common_globals "github.com/PretendoNetwork/nex-protocols-common-go/globals"
match_making_types "github.com/PretendoNetwork/nex-protocols-go/match-making/types"
matchmake_extension "github.com/PretendoNetwork/nex-protocols-go/matchmake-extension"
"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 createMatchmakeSession(err error, packet nex.PacketInterface, callID uint32, anyGathering *nex.DataHolder, message string, participationCount uint16) uint32 {
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 nex.Errors.Core.InvalidArgument
return nil, nex.NewError(nex.ResultCodes.Core.InvalidArgument, "change_error")
}
client := packet.Sender()
server := commonMatchmakeExtensionProtocol.server
if len(message) > 256 {
return nil, nex.NewError(nex.ResultCodes.Core.InvalidArgument, "change_error")
}
// A client may disconnect from a session without leaving reliably,
// so let's make sure the client is removed from the session
common_globals.RemoveClientFromAllSessions(client)
connection := packet.Sender().(*nex.PRUDPConnection)
endpoint := connection.Endpoint().(*nex.PRUDPEndPoint)
server := endpoint.Server
var matchmakeSession *match_making_types.MatchmakeSession
anyGatheringDataType := anyGathering.TypeName()
commonProtocol.manager.Mutex.Lock()
if anyGatheringDataType == "MatchmakeSession" {
matchmakeSession = anyGathering.ObjectData().(*match_making_types.MatchmakeSession)
// * 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?!")
return nex.Errors.Core.InvalidArgument
commonProtocol.manager.Mutex.Unlock()
return nil, nex.NewError(nex.ResultCodes.Core.InvalidArgument, "change_error")
}
session, err, errCode := common_globals.CreateSessionByMatchmakeSession(matchmakeSession, nil, client.PID())
if err != nil {
common_globals.Logger.Error(err.Error())
return errCode
if !common_globals.CheckValidMatchmakeSession(matchmakeSession) {
commonProtocol.manager.Mutex.Unlock()
return nil, nex.NewError(nex.ResultCodes.Core.InvalidArgument, "change_error")
}
err, errCode = common_globals.AddPlayersToSession(session, []uint32{client.ConnectionID()}, client, message)
if err != nil {
common_globals.Logger.Error(err.Error())
return errCode
nexError := database.CreateMatchmakeSession(commonProtocol.manager, connection, &matchmakeSession)
if nexError != nil {
common_globals.Logger.Error(nexError.Error())
commonProtocol.manager.Mutex.Unlock()
return nil, nexError
}
rmcResponseStream := nex.NewStreamOut(server)
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
}
rmcResponseStream.WriteUInt32LE(session.GameMatchmakeSession.Gathering.ID)
matchmakeSession.ParticipationCount = types.NewUInt32(participants)
if server.MatchMakingProtocolVersion().GreaterOrEqual("3.0.0") {
rmcResponseStream.WriteBuffer(session.GameMatchmakeSession.SessionKey)
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.NewRMCResponse(matchmake_extension.ProtocolID, callID)
rmcResponse.SetSuccess(matchmake_extension.MethodCreateMatchmakeSession, rmcResponseBody)
rmcResponse := nex.NewRMCSuccess(endpoint, rmcResponseBody)
rmcResponse.ProtocolID = matchmake_extension.ProtocolID
rmcResponse.MethodID = matchmake_extension.MethodCreateMatchmakeSession
rmcResponse.CallID = callID
rmcResponseBytes := rmcResponse.Bytes()
var responsePacket nex.PacketInterface
if server.PRUDPVersion() == 0 {
responsePacket, _ = nex.NewPacketV0(client, nil)
responsePacket.SetVersion(0)
} else {
responsePacket, _ = nex.NewPacketV1(client, nil)
responsePacket.SetVersion(1)
if commonProtocol.OnAfterCreateMatchmakeSession != nil {
go commonProtocol.OnAfterCreateMatchmakeSession(packet, anyGathering, message, participationCount)
}
responsePacket.SetSource(packet.Destination())
responsePacket.SetDestination(packet.Source())
responsePacket.SetType(nex.DataPacket)
responsePacket.SetPayload(rmcResponseBytes)
responsePacket.AddFlag(nex.FlagNeedsAck)
responsePacket.AddFlag(nex.FlagReliable)
server.Send(responsePacket)
return 0
return rmcResponse, nil
}

View file

@ -1,68 +1,80 @@
package matchmake_extension
import (
nex "github.com/PretendoNetwork/nex-go"
common_globals "github.com/PretendoNetwork/nex-protocols-common-go/globals"
match_making_types "github.com/PretendoNetwork/nex-protocols-go/match-making/types"
matchmake_extension "github.com/PretendoNetwork/nex-protocols-go/matchmake-extension"
"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 createMatchmakeSessionWithParam(err error, packet nex.PacketInterface, callID uint32, createMatchmakeSessionParam *match_making_types.CreateMatchmakeSessionParam) uint32 {
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 nex.Errors.Core.InvalidArgument
return nil, nex.NewError(nex.ResultCodes.Core.InvalidArgument, "change_error")
}
client := packet.Sender()
server := commonMatchmakeExtensionProtocol.server
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
common_globals.RemoveClientFromAllSessions(client)
database.EndMatchmakeSessionsParticipation(commonProtocol.manager, connection)
joinedMatchmakeSession := createMatchmakeSessionParam.SourceMatchmakeSession.Copy().(*match_making_types.MatchmakeSession)
session, err, errCode := common_globals.CreateSessionByMatchmakeSession(joinedMatchmakeSession, nil, client.PID())
if err != nil {
common_globals.Logger.Error(err.Error())
return errCode
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
}
err, errCode = common_globals.AddPlayersToSession(session, []uint32{client.ConnectionID()}, client, createMatchmakeSessionParam.JoinMessage)
if err != nil {
common_globals.Logger.Error(err.Error())
return errCode
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
}
rmcResponseStream := nex.NewStreamOut(server)
commonProtocol.manager.Mutex.Unlock()
rmcResponseStream.WriteStructure(session.GameMatchmakeSession)
joinedMatchmakeSession.ParticipationCount = types.NewUInt32(participants)
rmcResponseStream := nex.NewByteStreamOut(endpoint.LibraryVersions(), endpoint.ByteStreamSettings())
joinedMatchmakeSession.WriteTo(rmcResponseStream)
rmcResponseBody := rmcResponseStream.Bytes()
rmcResponse := nex.NewRMCResponse(matchmake_extension.ProtocolID, callID)
rmcResponse.SetSuccess(matchmake_extension.MethodCreateMatchmakeSessionWithParam, rmcResponseBody)
rmcResponse := nex.NewRMCSuccess(endpoint, rmcResponseBody)
rmcResponse.ProtocolID = matchmake_extension.ProtocolID
rmcResponse.MethodID = matchmake_extension.MethodCreateMatchmakeSessionWithParam
rmcResponse.CallID = callID
rmcResponseBytes := rmcResponse.Bytes()
var responsePacket nex.PacketInterface
if server.PRUDPVersion() == 0 {
responsePacket, _ = nex.NewPacketV0(client, nil)
responsePacket.SetVersion(0)
} else {
responsePacket, _ = nex.NewPacketV1(client, nil)
responsePacket.SetVersion(1)
if commonProtocol.OnAfterCreateMatchmakeSessionWithParam != nil {
go commonProtocol.OnAfterCreateMatchmakeSessionWithParam(packet, createMatchmakeSessionParam)
}
responsePacket.SetSource(packet.Destination())
responsePacket.SetDestination(packet.Source())
responsePacket.SetType(nex.DataPacket)
responsePacket.SetPayload(rmcResponseBytes)
responsePacket.AddFlag(nex.FlagNeedsAck)
responsePacket.AddFlag(nex.FlagReliable)
server.Send(responsePacket)
return 0
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

@ -1,106 +1,61 @@
package matchmake_extension
import (
"fmt"
"slices"
nex "github.com/PretendoNetwork/nex-go"
common_globals "github.com/PretendoNetwork/nex-protocols-common-go/globals"
match_making_types "github.com/PretendoNetwork/nex-protocols-go/match-making/types"
matchmake_extension "github.com/PretendoNetwork/nex-protocols-go/matchmake-extension"
"golang.org/x/exp/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"
)
// * https://stackoverflow.com/a/70808522
func remove[T comparable](l []T, item T) []T {
for i, other := range l {
if other == item {
return append(l[:i], l[i+1:]...)
}
}
return l
}
func getSimplePlayingSession(err error, packet nex.PacketInterface, callID uint32, listPID []uint32, includeLoginUser bool) uint32 {
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 nex.Errors.Core.InvalidArgument
return nil, nex.NewError(nex.ResultCodes.Core.InvalidArgument, "change_error")
}
client := packet.Sender()
server := commonMatchmakeExtensionProtocol.server
connection := packet.Sender().(*nex.PRUDPConnection)
endpoint := connection.Endpoint().(*nex.PRUDPEndPoint)
if slices.Contains(listPID, client.PID()) {
listPID = remove(listPID, client.PID())
// * 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())
}
if includeLoginUser && !slices.Contains(listPID, client.PID()) {
listPID = append(listPID, client.PID())
commonProtocol.manager.Mutex.RLock()
simplePlayingSessions, nexError := database.GetSimplePlayingSession(commonProtocol.manager, listPID)
if nexError != nil {
commonProtocol.manager.Mutex.RUnlock()
return nil, nexError
}
simplePlayingSessions := make(map[string]*match_making_types.SimplePlayingSession)
commonProtocol.manager.Mutex.RUnlock()
for gatheringID, session := range common_globals.Sessions {
for _, pid := range listPID {
key := fmt.Sprintf("%d-%d", gatheringID, pid)
if simplePlayingSessions[key] == nil {
connectedPIDs := make([]uint32, 0)
for _, connectionID := range session.ConnectionIDs {
player := server.FindClientFromConnectionID(connectionID)
if player == nil {
common_globals.Logger.Warning("Player not found")
continue
}
lstSimplePlayingSession := types.NewList[match_making_types.SimplePlayingSession]()
lstSimplePlayingSession = simplePlayingSessions
connectedPIDs = append(connectedPIDs, player.PID())
}
rmcResponseStream := nex.NewByteStreamOut(endpoint.LibraryVersions(), endpoint.ByteStreamSettings())
if slices.Contains(connectedPIDs, pid) {
simplePlayingSessions[key] = match_making_types.NewSimplePlayingSession()
simplePlayingSessions[key].PrincipalID = pid
simplePlayingSessions[key].GatheringID = gatheringID
simplePlayingSessions[key].GameMode = session.GameMatchmakeSession.GameMode
simplePlayingSessions[key].Attribute0 = session.GameMatchmakeSession.Attributes[0]
}
}
}
}
lstSimplePlayingSession := make([]*match_making_types.SimplePlayingSession, 0)
for _, simplePlayingSession := range simplePlayingSessions {
lstSimplePlayingSession = append(lstSimplePlayingSession, simplePlayingSession)
}
rmcResponseStream := nex.NewStreamOut(server)
rmcResponseStream.WriteListStructure(lstSimplePlayingSession)
lstSimplePlayingSession.WriteTo(rmcResponseStream)
rmcResponseBody := rmcResponseStream.Bytes()
rmcResponse := nex.NewRMCResponse(matchmake_extension.ProtocolID, callID)
rmcResponse.SetSuccess(matchmake_extension.MethodGetSimplePlayingSession, rmcResponseBody)
rmcResponse := nex.NewRMCSuccess(endpoint, rmcResponseBody)
rmcResponse.ProtocolID = matchmake_extension.ProtocolID
rmcResponse.MethodID = matchmake_extension.MethodGetSimplePlayingSession
rmcResponse.CallID = callID
rmcResponseBytes := rmcResponse.Bytes()
var responsePacket nex.PacketInterface
if server.PRUDPVersion() == 0 {
responsePacket, _ = nex.NewPacketV0(client, nil)
responsePacket.SetVersion(0)
} else {
responsePacket, _ = nex.NewPacketV1(client, nil)
responsePacket.SetVersion(1)
if commonProtocol.OnAfterGetSimplePlayingSession != nil {
go commonProtocol.OnAfterGetSimplePlayingSession(packet, listPID, includeLoginUser)
}
responsePacket.SetSource(packet.Destination())
responsePacket.SetDestination(packet.Source())
responsePacket.SetType(nex.DataPacket)
responsePacket.SetPayload(rmcResponseBytes)
responsePacket.AddFlag(nex.FlagNeedsAck)
responsePacket.AddFlag(nex.FlagReliable)
server.Send(responsePacket)
return 0
return rmcResponse, nil
}

View file

@ -1,67 +1,78 @@
package matchmake_extension
import (
nex "github.com/PretendoNetwork/nex-go"
common_globals "github.com/PretendoNetwork/nex-protocols-common-go/globals"
matchmake_extension "github.com/PretendoNetwork/nex-protocols-go/matchmake-extension"
"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 joinMatchmakeSession(err error, packet nex.PacketInterface, callID uint32, gid uint32, strMessage string) uint32 {
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 nex.Errors.Core.InvalidArgument
return nil, nex.NewError(nex.ResultCodes.Core.InvalidArgument, "change_error")
}
client := packet.Sender()
server := commonMatchmakeExtensionProtocol.server
session, ok := common_globals.Sessions[gid]
if !ok {
return nex.Errors.RendezVous.SessionVoid
if len(strMessage) > 256 {
return nil, nex.NewError(nex.ResultCodes.Core.InvalidArgument, "change_error")
}
// TODO - More checks here
commonProtocol.manager.Mutex.Lock()
err, errCode := common_globals.AddPlayersToSession(session, []uint32{client.ConnectionID()}, client, strMessage)
if err != nil {
common_globals.Logger.Error(err.Error())
return errCode
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
}
joinedMatchmakeSession := session.GameMatchmakeSession
// 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")
}
rmcResponseStream := nex.NewStreamOut(server)
// * 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
}
if server.MatchMakingProtocolVersion().GreaterOrEqual("3.0.0") {
rmcResponseStream.WriteBuffer(joinedMatchmakeSession.SessionKey)
_, 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.NewRMCResponse(matchmake_extension.ProtocolID, callID)
rmcResponse.SetSuccess(matchmake_extension.MethodJoinMatchmakeSession, rmcResponseBody)
rmcResponse := nex.NewRMCSuccess(endpoint, rmcResponseBody)
rmcResponse.ProtocolID = matchmake_extension.ProtocolID
rmcResponse.MethodID = matchmake_extension.MethodJoinMatchmakeSession
rmcResponse.CallID = callID
rmcResponseBytes := rmcResponse.Bytes()
var responsePacket nex.PacketInterface
if server.PRUDPVersion() == 0 {
responsePacket, _ = nex.NewPacketV0(client, nil)
responsePacket.SetVersion(0)
} else {
responsePacket, _ = nex.NewPacketV1(client, nil)
responsePacket.SetVersion(1)
if commonProtocol.OnAfterJoinMatchmakeSession != nil {
go commonProtocol.OnAfterJoinMatchmakeSession(packet, gid, strMessage)
}
responsePacket.SetSource(packet.Destination())
responsePacket.SetDestination(packet.Source())
responsePacket.SetType(nex.DataPacket)
responsePacket.SetPayload(rmcResponseBytes)
responsePacket.AddFlag(nex.FlagNeedsAck)
responsePacket.AddFlag(nex.FlagReliable)
server.Send(responsePacket)
return 0
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

@ -1,65 +1,90 @@
package matchmake_extension
import (
nex "github.com/PretendoNetwork/nex-go"
common_globals "github.com/PretendoNetwork/nex-protocols-common-go/globals"
match_making_types "github.com/PretendoNetwork/nex-protocols-go/match-making/types"
matchmake_extension "github.com/PretendoNetwork/nex-protocols-go/matchmake-extension"
"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 joinMatchmakeSessionWithParam(err error, packet nex.PacketInterface, callID uint32, joinMatchmakeSessionParam *match_making_types.JoinMatchmakeSessionParam) uint32 {
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 nex.Errors.Core.InvalidArgument
return nil, nex.NewError(nex.ResultCodes.Core.InvalidArgument, "change_error")
}
client := packet.Sender()
server := commonMatchmakeExtensionProtocol.server
session, ok := common_globals.Sessions[joinMatchmakeSessionParam.GID]
if !ok {
return nex.Errors.RendezVous.SessionVoid
if len(joinMatchmakeSessionParam.JoinMessage) > 256 {
return nil, nex.NewError(nex.ResultCodes.Core.InvalidArgument, "change_error")
}
// TODO - More checks here
err, errCode := common_globals.AddPlayersToSession(session, []uint32{client.ConnectionID()}, client, joinMatchmakeSessionParam.JoinMessage)
if err != nil {
common_globals.Logger.Error(err.Error())
return errCode
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 := session.GameMatchmakeSession
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
}
rmcResponseStream := nex.NewStreamOut(server)
// 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")
}
rmcResponseStream.WriteStructure(joinedMatchmakeSession)
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.NewRMCResponse(matchmake_extension.ProtocolID, callID)
rmcResponse.SetSuccess(matchmake_extension.MethodJoinMatchmakeSessionWithParam, rmcResponseBody)
rmcResponse := nex.NewRMCSuccess(endpoint, rmcResponseBody)
rmcResponse.ProtocolID = matchmake_extension.ProtocolID
rmcResponse.MethodID = matchmake_extension.MethodJoinMatchmakeSessionWithParam
rmcResponse.CallID = callID
rmcResponseBytes := rmcResponse.Bytes()
var responsePacket nex.PacketInterface
if server.PRUDPVersion() == 0 {
responsePacket, _ = nex.NewPacketV0(client, nil)
responsePacket.SetVersion(0)
} else {
responsePacket, _ = nex.NewPacketV1(client, nil)
responsePacket.SetVersion(1)
if commonProtocol.OnAfterJoinMatchmakeSessionWithParam != nil {
go commonProtocol.OnAfterJoinMatchmakeSessionWithParam(packet, joinMatchmakeSessionParam)
}
responsePacket.SetSource(packet.Destination())
responsePacket.SetDestination(packet.Source())
responsePacket.SetType(nex.DataPacket)
responsePacket.SetPayload(rmcResponseBytes)
responsePacket.AddFlag(nex.FlagNeedsAck)
responsePacket.AddFlag(nex.FlagReliable)
server.Send(responsePacket)
return 0
return rmcResponse, nil
}

View file

@ -1,59 +1,58 @@
package matchmake_extension
import (
nex "github.com/PretendoNetwork/nex-go"
common_globals "github.com/PretendoNetwork/nex-protocols-common-go/globals"
matchmake_extension "github.com/PretendoNetwork/nex-protocols-go/matchmake-extension"
"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 modifyCurrentGameAttribute(err error, packet nex.PacketInterface, callID uint32, gid uint32, attribIndex uint32, newValue uint32) uint32 {
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 nex.Errors.Core.InvalidArgument
return nil, nex.NewError(nex.ResultCodes.Core.InvalidArgument, "change_error")
}
client := packet.Sender()
server := commonMatchmakeExtensionProtocol.server
connection := packet.Sender().(*nex.PRUDPConnection)
endpoint := connection.Endpoint().(*nex.PRUDPEndPoint)
session, ok := common_globals.Sessions[gid]
if !ok {
return nex.Errors.RendezVous.SessionVoid
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.GameMatchmakeSession.Gathering.OwnerPID != client.PID() {
return nex.Errors.RendezVous.PermissionDenied
if !session.Gathering.OwnerPID.Equals(connection.PID()) {
commonProtocol.manager.Mutex.Unlock()
return nil, nex.NewError(nex.ResultCodes.RendezVous.PermissionDenied, "change_error")
}
if int(attribIndex) > len(session.GameMatchmakeSession.Attributes) {
return nex.Errors.Core.InvalidIndex
index := int(attribIndex)
if index >= len(session.Attributes) {
commonProtocol.manager.Mutex.Unlock()
return nil, nex.NewError(nex.ResultCodes.Core.InvalidIndex, "change_error")
}
session.GameMatchmakeSession.Attributes[attribIndex] = newValue
rmcResponse := nex.NewRMCResponse(matchmake_extension.ProtocolID, callID)
rmcResponse.SetSuccess(matchmake_extension.MethodModifyCurrentGameAttribute, nil)
rmcResponseBytes := rmcResponse.Bytes()
var responsePacket nex.PacketInterface
if server.PRUDPVersion() == 0 {
responsePacket, _ = nex.NewPacketV0(client, nil)
responsePacket.SetVersion(0)
} else {
responsePacket, _ = nex.NewPacketV1(client, nil)
responsePacket.SetVersion(1)
nexError = database.UpdateGameAttribute(commonProtocol.manager, uint32(gid), uint32(attribIndex), uint32(newValue))
if nexError != nil {
commonProtocol.manager.Mutex.Unlock()
return nil, nexError
}
responsePacket.SetSource(packet.Destination())
responsePacket.SetDestination(packet.Source())
responsePacket.SetType(nex.DataPacket)
responsePacket.SetPayload(rmcResponseBytes)
commonProtocol.manager.Mutex.Unlock()
responsePacket.AddFlag(nex.FlagNeedsAck)
responsePacket.AddFlag(nex.FlagReliable)
rmcResponse := nex.NewRMCSuccess(endpoint, nil)
rmcResponse.ProtocolID = matchmake_extension.ProtocolID
rmcResponse.MethodID = matchmake_extension.MethodModifyCurrentGameAttribute
rmcResponse.CallID = callID
server.Send(responsePacket)
if commonProtocol.OnAfterModifyCurrentGameAttribute != nil {
go commonProtocol.OnAfterModifyCurrentGameAttribute(packet, gid, attribIndex, newValue)
}
return 0
return rmcResponse, nil
}

View file

@ -1,57 +1,52 @@
package matchmake_extension
import (
nex "github.com/PretendoNetwork/nex-go"
common_globals "github.com/PretendoNetwork/nex-protocols-common-go/globals"
matchmake_extension "github.com/PretendoNetwork/nex-protocols-go/matchmake-extension"
"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 openParticipation(err error, packet nex.PacketInterface, callID uint32, gid uint32) uint32 {
func (commonProtocol *CommonProtocol) openParticipation(err error, packet nex.PacketInterface, callID uint32, gid types.UInt32) (*nex.RMCMessage, *nex.Error) {
if err != nil {
common_globals.Logger.Error(err.Error())
return nex.Errors.Core.InvalidArgument
return nil, nex.NewError(nex.ResultCodes.Core.InvalidArgument, "change_error")
}
client := packet.Sender()
connection := packet.Sender().(*nex.PRUDPConnection)
endpoint := connection.Endpoint().(*nex.PRUDPEndPoint)
var session *common_globals.CommonMatchmakeSession
var ok bool
if session, ok = common_globals.Sessions[gid]; !ok {
return nex.Errors.RendezVous.SessionVoid
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.GameMatchmakeSession.Gathering.OwnerPID != client.PID() {
return nex.Errors.RendezVous.PermissionDenied
if !session.Gathering.OwnerPID.Equals(connection.PID()) {
commonProtocol.manager.Mutex.Unlock()
return nil, nex.NewError(nex.ResultCodes.RendezVous.PermissionDenied, "change_error")
}
session.GameMatchmakeSession.OpenParticipation = true
server := commonMatchmakeExtensionProtocol.server
rmcResponse := nex.NewRMCResponse(matchmake_extension.ProtocolID, callID)
rmcResponse.SetSuccess(matchmake_extension.MethodOpenParticipation, nil)
rmcResponseBytes := rmcResponse.Bytes()
var responsePacket nex.PacketInterface
if server.PRUDPVersion() == 0 {
responsePacket, _ = nex.NewPacketV0(client, nil)
responsePacket.SetVersion(0)
} else {
responsePacket, _ = nex.NewPacketV1(client, nil)
responsePacket.SetVersion(1)
nexError = database.UpdateParticipation(commonProtocol.manager, uint32(gid), true)
if nexError != nil {
commonProtocol.manager.Mutex.Unlock()
return nil, nexError
}
responsePacket.SetSource(packet.Destination())
responsePacket.SetDestination(packet.Source())
responsePacket.SetType(nex.DataPacket)
responsePacket.SetPayload(rmcResponseBytes)
commonProtocol.manager.Mutex.Unlock()
responsePacket.AddFlag(nex.FlagNeedsAck)
responsePacket.AddFlag(nex.FlagReliable)
rmcResponse := nex.NewRMCSuccess(endpoint, nil)
rmcResponse.ProtocolID = matchmake_extension.ProtocolID
rmcResponse.MethodID = matchmake_extension.MethodOpenParticipation
rmcResponse.CallID = callID
server.Send(responsePacket)
if commonProtocol.OnAfterOpenParticipation != nil {
go commonProtocol.OnAfterOpenParticipation(packet, gid)
}
return 0
return rmcResponse, nil
}

View file

@ -1,90 +1,203 @@
package matchmake_extension
import (
"strings"
"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"
matchmake_extension "github.com/PretendoNetwork/nex-protocols-go/v2/matchmake-extension"
nex "github.com/PretendoNetwork/nex-go"
match_making_types "github.com/PretendoNetwork/nex-protocols-go/match-making/types"
matchmake_extension "github.com/PretendoNetwork/nex-protocols-go/matchmake-extension"
matchmake_extension_mario_kart_8 "github.com/PretendoNetwork/nex-protocols-go/matchmake-extension/mario-kart-8"
common_globals "github.com/PretendoNetwork/nex-protocols-common-go/globals"
common_globals "github.com/PretendoNetwork/nex-protocols-common-go/v2/globals"
"github.com/PretendoNetwork/nex-protocols-common-go/v2/matchmake-extension/database"
)
var commonMatchmakeExtensionProtocol *CommonMatchmakeExtensionProtocol
type CommonMatchmakeExtensionProtocol struct {
server *nex.Server
DefaultProtocol *matchmake_extension.Protocol
MarioKart8Protocol *matchmake_extension_mario_kart_8.Protocol
cleanupSearchMatchmakeSessionHandler func(matchmakeSession *match_making_types.MatchmakeSession)
gameSpecificMatchmakeSessionSearchCriteriaChecksHandler func(searchCriteria *match_making_types.MatchmakeSessionSearchCriteria, matchmakeSession *match_making_types.MatchmakeSession) bool
type CommonProtocol struct {
endpoint nex.EndpointInterface
protocol matchmake_extension.Interface
manager *common_globals.MatchmakingManager
PersistentGatheringCreationMax int
CanJoinMatchmakeSession func(manager *common_globals.MatchmakingManager, pid types.PID, matchmakeSession match_making_types.MatchmakeSession) *nex.Error
CleanupSearchMatchmakeSession func(matchmakeSession *match_making_types.MatchmakeSession)
CleanupMatchmakeSessionSearchCriterias func(searchCriterias types.List[match_making_types.MatchmakeSessionSearchCriteria])
OnAfterOpenParticipation func(packet nex.PacketInterface, gid types.UInt32)
OnAfterCloseParticipation func(packet nex.PacketInterface, gid types.UInt32)
OnAfterCreateMatchmakeSession func(packet nex.PacketInterface, anyGathering match_making_types.GatheringHolder, message types.String, participationCount types.UInt16)
OnAfterGetSimplePlayingSession func(packet nex.PacketInterface, listPID types.List[types.PID], includeLoginUser types.Bool)
OnAfterAutoMatchmakePostpone func(packet nex.PacketInterface, anyGathering match_making_types.GatheringHolder, message types.String)
OnAfterAutoMatchmakeWithParamPostpone func(packet nex.PacketInterface, autoMatchmakeParam match_making_types.AutoMatchmakeParam)
OnAfterAutoMatchmakeWithSearchCriteriaPostpone func(packet nex.PacketInterface, lstSearchCriteria types.List[match_making_types.MatchmakeSessionSearchCriteria], anyGathering match_making_types.GatheringHolder, strMessage types.String)
OnAfterCreateCommunity func(packet nex.PacketInterface, community match_making_types.PersistentGathering, strMessage types.String)
OnAfterFindCommunityByGatheringID func(packet nex.PacketInterface, lstGID types.List[types.UInt32])
OnAfterFindOfficialCommunity func(packet nex.PacketInterface, isAvailableOnly types.Bool, resultRange types.ResultRange)
OnAfterFindCommunityByParticipant func(packet nex.PacketInterface, pid types.PID, resultRange types.ResultRange)
OnAfterUpdateProgressScore func(packet nex.PacketInterface, gid types.UInt32, progressScore types.UInt8)
OnAfterCreateMatchmakeSessionWithParam func(packet nex.PacketInterface, createMatchmakeSessionParam match_making_types.CreateMatchmakeSessionParam)
OnAfterUpdateApplicationBuffer func(packet nex.PacketInterface, gid types.UInt32, applicationBuffer types.Buffer)
OnAfterJoinMatchmakeSession func(packet nex.PacketInterface, gid types.UInt32, strMessage types.String)
OnAfterJoinMatchmakeSessionWithParam func(packet nex.PacketInterface, joinMatchmakeSessionParam match_making_types.JoinMatchmakeSessionParam)
OnAfterModifyCurrentGameAttribute func(packet nex.PacketInterface, gid types.UInt32, attribIndex types.UInt32, newValue types.UInt32)
OnAfterBrowseMatchmakeSession func(packet nex.PacketInterface, searchCriteria match_making_types.MatchmakeSessionSearchCriteria, resultRange types.ResultRange)
OnAfterJoinMatchmakeSessionEx func(packet nex.PacketInterface, gid types.UInt32, strMessage types.String, dontCareMyBlockList types.Bool, participationCount types.UInt16)
OnAfterGetSimpleCommunity func(packet nex.PacketInterface, gatheringIDList types.List[types.UInt32])
OnAfterUpdateNotificationData func(packet nex.PacketInterface, uiType types.UInt32, uiParam1 types.UInt32, uiParam2 types.UInt32, strParam types.String)
OnAfterGetFriendNotificationData func(packet nex.PacketInterface, uiType types.Int32)
OnAfterGetlstFriendNotificationData func(packet nex.PacketInterface, lstTypes types.List[types.UInt32])
}
// CleanupSearchMatchmakeSession sets the CleanupSearchMatchmakeSession handler function
func (commonMatchmakeExtensionProtocol *CommonMatchmakeExtensionProtocol) CleanupSearchMatchmakeSession(handler func(matchmakeSession *match_making_types.MatchmakeSession)) {
commonMatchmakeExtensionProtocol.cleanupSearchMatchmakeSessionHandler = handler
}
// SetDatabase defines the matchmaking manager to be used by the common protocol
func (commonProtocol *CommonProtocol) SetManager(manager *common_globals.MatchmakingManager) {
var err error
// GameSpecificMatchmakeSessionSearchCriteriaChecks sets the GameSpecificMatchmakeSessionSearchCriteriaChecks handler function
func (commonMatchmakeExtensionProtocol *CommonMatchmakeExtensionProtocol) GameSpecificMatchmakeSessionSearchCriteriaChecks(handler func(searchCriteria *match_making_types.MatchmakeSessionSearchCriteria, matchmakeSession *match_making_types.MatchmakeSession) bool) {
commonMatchmakeExtensionProtocol.gameSpecificMatchmakeSessionSearchCriteriaChecksHandler = handler
}
commonProtocol.manager = manager
// GetUserFriendPIDs sets the GetUserFriendPIDs handler function
func (commonMatchmakeExtensionProtocol *CommonMatchmakeExtensionProtocol) GetUserFriendPIDs(handler func(pid uint32) []uint32) {
common_globals.GetUserFriendPIDsHandler = handler
}
manager.GetDetailedGatheringByID = database.GetDetailedGatheringByID
func initDefault(c *CommonMatchmakeExtensionProtocol) {
// TODO - Organize by method ID
c.DefaultProtocol = matchmake_extension.NewProtocol(c.server)
c.DefaultProtocol.OpenParticipation(openParticipation)
c.DefaultProtocol.CloseParticipation(closeParticipation)
c.DefaultProtocol.CreateMatchmakeSession(createMatchmakeSession)
c.DefaultProtocol.GetSimplePlayingSession(getSimplePlayingSession)
c.DefaultProtocol.AutoMatchmakePostpone(autoMatchmake_Postpone)
c.DefaultProtocol.AutoMatchmakeWithParamPostpone(autoMatchmakeWithParam_Postpone)
c.DefaultProtocol.AutoMatchmakeWithSearchCriteriaPostpone(autoMatchmakeWithSearchCriteria_Postpone)
c.DefaultProtocol.UpdateProgressScore(updateProgressScore)
c.DefaultProtocol.CreateMatchmakeSessionWithParam(createMatchmakeSessionWithParam)
c.DefaultProtocol.UpdateApplicationBuffer(updateApplicationBuffer)
c.DefaultProtocol.JoinMatchmakeSession(joinMatchmakeSession)
c.DefaultProtocol.JoinMatchmakeSessionWithParam(joinMatchmakeSessionWithParam)
c.DefaultProtocol.ModifyCurrentGameAttribute(modifyCurrentGameAttribute)
c.DefaultProtocol.BrowseMatchmakeSession(browseMatchmakeSession)
}
func initMarioKart8(c *CommonMatchmakeExtensionProtocol) {
// TODO - Organize by method ID
c.MarioKart8Protocol = matchmake_extension_mario_kart_8.NewProtocol(c.server)
c.MarioKart8Protocol.OpenParticipation(openParticipation)
c.MarioKart8Protocol.CloseParticipation(closeParticipation)
c.MarioKart8Protocol.CreateMatchmakeSession(createMatchmakeSession)
c.MarioKart8Protocol.GetSimplePlayingSession(getSimplePlayingSession)
c.MarioKart8Protocol.AutoMatchmakePostpone(autoMatchmake_Postpone)
c.MarioKart8Protocol.AutoMatchmakeWithSearchCriteriaPostpone(autoMatchmakeWithSearchCriteria_Postpone)
c.MarioKart8Protocol.UpdateProgressScore(updateProgressScore)
}
// NewCommonMatchmakeExtensionProtocol returns a new CommonMatchmakeExtensionProtocol
func NewCommonMatchmakeExtensionProtocol(server *nex.Server) *CommonMatchmakeExtensionProtocol {
commonMatchmakeExtensionProtocol = &CommonMatchmakeExtensionProtocol{server: server}
patch := server.MatchMakingProtocolVersion().GameSpecificPatch
if strings.EqualFold(patch, "AMKJ") {
common_globals.Logger.Info("Using Mario Kart 8 MatchmakeExtension protocol")
initMarioKart8(commonMatchmakeExtensionProtocol)
} else {
if patch != "" {
common_globals.Logger.Infof("Matchmaking version patch %q not recognized", patch)
}
common_globals.Logger.Info("Using default MatchmakeExtension protocol")
initDefault(commonMatchmakeExtensionProtocol)
_, err = manager.Database.Exec(`CREATE TABLE IF NOT EXISTS matchmaking.matchmake_sessions (
id bigserial PRIMARY KEY,
game_mode bigint,
attribs bigint[],
open_participation boolean,
matchmake_system_type bigint,
application_buffer bytea,
flags bigint,
state bigint,
progress_score smallint,
session_key bytea,
option_zero bigint,
matchmake_param bytea,
user_password text,
refer_gid bigint,
user_password_enabled boolean,
system_password_enabled boolean,
codeword text,
system_password text NOT NULL DEFAULT ''
)`)
if err != nil {
common_globals.Logger.Error(err.Error())
return
}
return commonMatchmakeExtensionProtocol
_, err = manager.Database.Exec(`CREATE TABLE IF NOT EXISTS matchmaking.persistent_gatherings (
id bigserial PRIMARY KEY,
community_type bigint,
password text,
attribs bigint[],
application_buffer bytea,
participation_start_date timestamp,
participation_end_date timestamp
)`)
if err != nil {
common_globals.Logger.Error(err.Error())
return
}
_, err = manager.Database.Exec(`CREATE TABLE IF NOT EXISTS matchmaking.community_participations (
id bigserial PRIMARY KEY,
user_pid numeric(10),
gathering_id bigint,
participation_count bigint,
UNIQUE (user_pid, gathering_id)
)`)
if err != nil {
common_globals.Logger.Error(err.Error())
return
}
_, err = manager.Database.Exec(`CREATE TABLE IF NOT EXISTS matchmaking.notifications (
id bigserial PRIMARY KEY,
source_pid numeric(10),
type bigint,
param_1 bigint,
param_2 bigint,
param_str text,
active boolean NOT NULL DEFAULT true,
UNIQUE (source_pid, type)
)`)
if err != nil {
common_globals.Logger.Error(err.Error())
return
}
_, err = manager.Database.Exec(`CREATE TABLE IF NOT EXISTS tracking.participate_community (
id bigserial PRIMARY KEY,
date timestamp,
source_pid numeric(10),
community_gid bigint,
gathering_id bigint,
participation_count bigint
)`)
if err != nil {
common_globals.Logger.Error(err.Error())
return
}
_, err = manager.Database.Exec(`CREATE TABLE IF NOT EXISTS tracking.notification_data (
id bigserial PRIMARY KEY,
date timestamp,
source_pid numeric(10),
type bigint,
param_1 bigint,
param_2 bigint,
param_str text
)`)
if err != nil {
common_globals.Logger.Error(err.Error())
return
}
// * In case the server is restarted, unregister any previous matchmake sessions
_, err = manager.Database.Exec(`UPDATE matchmaking.gatherings SET registered=false WHERE type='MatchmakeSession'`)
if err != nil {
common_globals.Logger.Error(err.Error())
return
}
// * Mark all notifications as inactive
_, err = manager.Database.Exec(`UPDATE matchmaking.notifications SET active=false`)
if err != nil {
common_globals.Logger.Error(err.Error())
return
}
}
// NewCommonProtocol returns a new CommonProtocol
func NewCommonProtocol(protocol matchmake_extension.Interface) *CommonProtocol {
endpoint := protocol.Endpoint().(*nex.PRUDPEndPoint)
commonProtocol := &CommonProtocol{
endpoint: endpoint,
protocol: protocol,
PersistentGatheringCreationMax: 4, // * Default of 4 active persistent gatherings per user
}
protocol.SetHandlerOpenParticipation(commonProtocol.openParticipation)
protocol.SetHandlerCloseParticipation(commonProtocol.closeParticipation)
protocol.SetHandlerCreateMatchmakeSession(commonProtocol.createMatchmakeSession)
protocol.SetHandlerGetSimplePlayingSession(commonProtocol.getSimplePlayingSession)
protocol.SetHandlerAutoMatchmakePostpone(commonProtocol.autoMatchmakePostpone)
protocol.SetHandlerAutoMatchmakeWithParamPostpone(commonProtocol.autoMatchmakeWithParamPostpone)
protocol.SetHandlerAutoMatchmakeWithSearchCriteriaPostpone(commonProtocol.autoMatchmakeWithSearchCriteriaPostpone)
protocol.SetHandlerCreateCommunity(commonProtocol.createCommunity)
protocol.SetHandlerFindCommunityByGatheringID(commonProtocol.findCommunityByGatheringID)
protocol.SetHandlerFindOfficialCommunity(commonProtocol.findOfficialCommunity)
protocol.SetHandlerFindCommunityByParticipant(commonProtocol.findCommunityByParticipant)
protocol.SetHandlerUpdateProgressScore(commonProtocol.updateProgressScore)
protocol.SetHandlerCreateMatchmakeSessionWithParam(commonProtocol.createMatchmakeSessionWithParam)
protocol.SetHandlerUpdateApplicationBuffer(commonProtocol.updateApplicationBuffer)
protocol.SetHandlerJoinMatchmakeSession(commonProtocol.joinMatchmakeSession)
protocol.SetHandlerJoinMatchmakeSessionWithParam(commonProtocol.joinMatchmakeSessionWithParam)
protocol.SetHandlerModifyCurrentGameAttribute(commonProtocol.modifyCurrentGameAttribute)
protocol.SetHandlerBrowseMatchmakeSession(commonProtocol.browseMatchmakeSession)
protocol.SetHandlerJoinMatchmakeSessionEx(commonProtocol.joinMatchmakeSessionEx)
protocol.SetHandlerGetSimpleCommunity(commonProtocol.getSimpleCommunity)
protocol.SetHandlerUpdateNotificationData(commonProtocol.updateNotificationData)
protocol.SetHandlerGetFriendNotificationData(commonProtocol.getFriendNotificationData)
protocol.SetHandlerGetlstFriendNotificationData(commonProtocol.getlstFriendNotificationData)
endpoint.OnConnectionEnded(func(connection *nex.PRUDPConnection) {
commonProtocol.manager.Mutex.Lock()
database.InactivateNotificationDatas(commonProtocol.manager, connection.PID())
commonProtocol.manager.Mutex.Unlock()
})
return commonProtocol
}

View file

@ -0,0 +1,42 @@
package tracking
import (
"database/sql"
"time"
"github.com/PretendoNetwork/nex-go/v2"
notifications_types "github.com/PretendoNetwork/nex-protocols-go/v2/notifications/types"
)
// LogNotificationData logs the update of the notification data of a user with UpdateNotificationData
func LogNotificationData(db *sql.DB, notificationData notifications_types.NotificationEvent) *nex.Error {
eventTime := time.Now().UTC()
_, err := db.Exec(`INSERT INTO tracking.notification_data (
date,
source_pid,
type,
param_1,
param_2,
param_str
) VALUES (
$1,
$2,
$3,
$4,
$5,
$6
)`,
eventTime,
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,33 @@
package tracking
import (
"database/sql"
"time"
"github.com/PretendoNetwork/nex-go/v2"
"github.com/PretendoNetwork/nex-go/v2/types"
)
// LogParticipateCommunity logs a persistent gathering participation event on the given database
func LogParticipateCommunity(db *sql.DB, sourcePID types.PID, communityGID uint32, gatheringID uint32, participationCount uint32) *nex.Error {
eventTime := time.Now().UTC()
_, err := db.Exec(`INSERT INTO tracking.participate_community (
date,
source_pid,
community_gid,
gathering_id,
participation_count
) VALUES (
$1,
$2,
$3,
$4,
$5
)`, eventTime, uint64(sourcePID), communityGID, gatheringID, participationCount)
if err != nil {
return nex.NewError(nex.ResultCodes.Core.Unknown, err.Error())
}
return nil
}

View file

@ -1,51 +1,51 @@
package matchmake_extension
import (
nex "github.com/PretendoNetwork/nex-go"
common_globals "github.com/PretendoNetwork/nex-protocols-common-go/globals"
matchmake_extension "github.com/PretendoNetwork/nex-protocols-go/matchmake-extension"
"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 updateApplicationBuffer(err error, packet nex.PacketInterface, callID uint32, gid uint32, applicationBuffer []byte) uint32 {
func (commonProtocol *CommonProtocol) updateApplicationBuffer(err error, packet nex.PacketInterface, callID uint32, gid types.UInt32, applicationBuffer types.Buffer) (*nex.RMCMessage, *nex.Error) {
if err != nil {
common_globals.Logger.Error(err.Error())
return nex.Errors.Core.InvalidArgument
return nil, nex.NewError(nex.ResultCodes.Core.InvalidArgument, "change_error")
}
client := packet.Sender()
server := commonMatchmakeExtensionProtocol.server
connection := packet.Sender().(*nex.PRUDPConnection)
endpoint := connection.Endpoint().(*nex.PRUDPEndPoint)
session, ok := common_globals.Sessions[gid]
if !ok {
return nex.Errors.RendezVous.SessionVoid
commonProtocol.manager.Mutex.Lock()
session, _, nexError := database.GetMatchmakeSessionByID(commonProtocol.manager, endpoint, uint32(gid))
if nexError != nil {
commonProtocol.manager.Mutex.Unlock()
return nil, nexError
}
session.GameMatchmakeSession.ApplicationData = applicationBuffer
rmcResponse := nex.NewRMCResponse(matchmake_extension.ProtocolID, callID)
rmcResponse.SetSuccess(matchmake_extension.MethodUpdateApplicationBuffer, nil)
rmcResponseBytes := rmcResponse.Bytes()
var responsePacket nex.PacketInterface
if server.PRUDPVersion() == 0 {
responsePacket, _ = nex.NewPacketV0(client, nil)
responsePacket.SetVersion(0)
} else {
responsePacket, _ = nex.NewPacketV1(client, nil)
responsePacket.SetVersion(1)
if !session.Gathering.OwnerPID.Equals(connection.PID()) {
commonProtocol.manager.Mutex.Unlock()
return nil, nex.NewError(nex.ResultCodes.RendezVous.PermissionDenied, "change_error")
}
responsePacket.SetSource(packet.Destination())
responsePacket.SetDestination(packet.Source())
responsePacket.SetType(nex.DataPacket)
responsePacket.SetPayload(rmcResponseBytes)
nexError = database.UpdateApplicationBuffer(commonProtocol.manager, uint32(gid), applicationBuffer)
if nexError != nil {
commonProtocol.manager.Mutex.Unlock()
return nil, nexError
}
responsePacket.AddFlag(nex.FlagNeedsAck)
responsePacket.AddFlag(nex.FlagReliable)
commonProtocol.manager.Mutex.Unlock()
server.Send(responsePacket)
rmcResponse := nex.NewRMCSuccess(endpoint, nil)
rmcResponse.ProtocolID = matchmake_extension.ProtocolID
rmcResponse.MethodID = matchmake_extension.MethodUpdateApplicationBuffer
rmcResponse.CallID = callID
return 0
if commonProtocol.OnAfterUpdateApplicationBuffer != nil {
go commonProtocol.OnAfterUpdateApplicationBuffer(packet, gid, applicationBuffer)
}
return rmcResponse, nil
}

View file

@ -0,0 +1,87 @@
package matchmake_extension
import (
"unicode/utf8"
"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-common-go/v2/matchmake-extension/tracking"
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) updateNotificationData(err error, packet nex.PacketInterface, callID uint32, uiType types.UInt32, uiParam1 types.UInt32, uiParam2 types.UInt32, strParam types.String) (*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 send 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")
}
// * All strings must have a length lower than 256.
// * Kid Icarus: Uprising sends strings with UTF-8 bytes longer than 256, so I assume this should count the runes instead
if utf8.RuneCountInString(string(strParam)) > 256 {
return nil, nex.NewError(nex.ResultCodes.Core.InvalidArgument, "change_error")
}
connection := packet.Sender().(*nex.PRUDPConnection)
endpoint := connection.Endpoint().(*nex.PRUDPEndPoint)
notificationData := notifications_types.NewNotificationEvent()
notificationData.PIDSource = connection.PID()
notificationData.Type = types.NewUInt32(notifications.BuildNotificationType(uint32(uiType), 0))
notificationData.Param1 = uiParam1
notificationData.Param2 = uiParam2
notificationData.StrParam = strParam
commonProtocol.manager.Mutex.Lock()
nexError := database.UpdateNotificationData(commonProtocol.manager, notificationData)
if nexError != nil {
commonProtocol.manager.Mutex.Unlock()
return nil, nexError
}
nexError = tracking.LogNotificationData(commonProtocol.manager.Database, notificationData)
if nexError != nil {
commonProtocol.manager.Mutex.Unlock()
return nil, nexError
}
commonProtocol.manager.Mutex.Unlock()
// * If the friends are connected, try to send the notifications directly aswell. This is observed on Mario Tennis Open
var friendList []uint32
if commonProtocol.manager.GetUserFriendPIDs != nil {
friendList = commonProtocol.manager.GetUserFriendPIDs(uint32(connection.PID()))
}
if len(friendList) != 0 {
var targets []uint64
for _, pid := range friendList {
// * Only send the notification to friends who are connected
if endpoint.FindConnectionByPID(uint64(pid)) != nil {
targets = append(targets, uint64(pid))
}
}
common_globals.SendNotificationEvent(endpoint, notificationData, targets)
}
rmcResponse := nex.NewRMCSuccess(endpoint, nil)
rmcResponse.ProtocolID = matchmake_extension.ProtocolID
rmcResponse.MethodID = matchmake_extension.MethodUpdateNotificationData
rmcResponse.CallID = callID
if commonProtocol.OnAfterUpdateNotificationData != nil {
go commonProtocol.OnAfterUpdateNotificationData(packet, uiType, uiParam1, uiParam2, strParam)
}
return rmcResponse, nil
}

View file

@ -1,60 +1,55 @@
package matchmake_extension
import (
nex "github.com/PretendoNetwork/nex-go"
common_globals "github.com/PretendoNetwork/nex-protocols-common-go/globals"
matchmake_extension "github.com/PretendoNetwork/nex-protocols-go/matchmake-extension"
"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 updateProgressScore(err error, packet nex.PacketInterface, callID uint32, gid uint32, progressScore uint8) uint32 {
func (commonProtocol *CommonProtocol) updateProgressScore(err error, packet nex.PacketInterface, callID uint32, gid types.UInt32, progressScore types.UInt8) (*nex.RMCMessage, *nex.Error) {
if err != nil {
common_globals.Logger.Error(err.Error())
return nex.Errors.Core.InvalidArgument
return nil, nex.NewError(nex.ResultCodes.Core.InvalidArgument, "change_error")
}
client := packet.Sender()
session := common_globals.Sessions[gid]
if session == nil {
return nex.Errors.RendezVous.SessionVoid
}
connection := packet.Sender().(*nex.PRUDPConnection)
endpoint := connection.Endpoint().(*nex.PRUDPEndPoint)
if progressScore > 100 {
return nex.Errors.Core.InvalidArgument
return nil, nex.NewError(nex.ResultCodes.Core.InvalidArgument, "change_error")
}
if session.GameMatchmakeSession.Gathering.OwnerPID != client.PID() {
return nex.Errors.RendezVous.PermissionDenied
commonProtocol.manager.Mutex.Lock()
session, _, nexError := database.GetMatchmakeSessionByID(commonProtocol.manager, endpoint, uint32(gid))
if nexError != nil {
commonProtocol.manager.Mutex.Unlock()
return nil, nexError
}
session.GameMatchmakeSession.ProgressScore += progressScore
server := commonMatchmakeExtensionProtocol.server
rmcResponse := nex.NewRMCResponse(matchmake_extension.ProtocolID, callID)
rmcResponse.SetSuccess(matchmake_extension.MethodUpdateProgressScore, nil)
rmcResponseBytes := rmcResponse.Bytes()
var responsePacket nex.PacketInterface
if server.PRUDPVersion() == 0 {
responsePacket, _ = nex.NewPacketV0(client, nil)
responsePacket.SetVersion(0)
} else {
responsePacket, _ = nex.NewPacketV1(client, nil)
responsePacket.SetVersion(1)
if !session.Gathering.OwnerPID.Equals(connection.PID()) {
commonProtocol.manager.Mutex.Unlock()
return nil, nex.NewError(nex.ResultCodes.RendezVous.PermissionDenied, "change_error")
}
responsePacket.SetSource(packet.Destination())
responsePacket.SetDestination(packet.Source())
responsePacket.SetType(nex.DataPacket)
responsePacket.SetPayload(rmcResponseBytes)
nexError = database.UpdateProgressScore(commonProtocol.manager, uint32(gid), uint8(progressScore))
if nexError != nil {
commonProtocol.manager.Mutex.Unlock()
return nil, nexError
}
responsePacket.AddFlag(nex.FlagNeedsAck)
responsePacket.AddFlag(nex.FlagReliable)
commonProtocol.manager.Mutex.Unlock()
server.Send(responsePacket)
rmcResponse := nex.NewRMCSuccess(endpoint, nil)
rmcResponse.ProtocolID = matchmake_extension.ProtocolID
rmcResponse.MethodID = matchmake_extension.MethodUpdateProgressScore
rmcResponse.CallID = callID
return 0
if commonProtocol.OnAfterUpdateProgressScore != nil {
go commonProtocol.OnAfterUpdateProgressScore(packet, gid, progressScore)
}
return rmcResponse, nil
}

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