-
Notifications
You must be signed in to change notification settings - Fork 16
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #42 from PretendoNetwork/rewrite
- Loading branch information
Showing
110 changed files
with
10,442 additions
and
6,505 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,48 +1,98 @@ | ||
# NEX Go | ||
## Barebones PRUDP/NEX server library written in Go | ||
|
||
[![GoDoc](https://godoc.org/github.com/PretendoNetwork/nex-go?status.svg)](https://godoc.org/github.com/PretendoNetwork/nex-go) | ||
|
||
### Other NEX libraries | ||
[nex-protocols-go](https://github.com/PretendoNetwork/nex-protocols-go) - NEX protocol definitions | ||
### Overview | ||
NEX is the networking library used by all 1st party, and many 3rd party, games on the Nintendo Wii U, 3DS, and Switch which have online features. The NEX library has many different parts, ranging from low level packet transport to higher level service implementations | ||
|
||
[nex-protocols-common-go](https://github.com/PretendoNetwork/nex-protocols-common-go) - NEX protocols used by many games with premade handlers and a high level API | ||
This library implements the lowest level parts of NEX, the transport protocols. For other parts of the NEX stack, see the below libraries. For detailed information on NEX as a whole, see our wiki docs https://nintendo-wiki.pretendo.network/docs/nex | ||
|
||
### Install | ||
|
||
`go get github.com/PretendoNetwork/nex-go` | ||
``` | ||
go get github.com/PretendoNetwork/nex-go | ||
``` | ||
|
||
### Other NEX libraries | ||
- [nex-protocols-go](https://github.com/PretendoNetwork/nex-protocols-go) - NEX protocol definitions | ||
- [nex-protocols-common-go](https://github.com/PretendoNetwork/nex-protocols-common-go) - Implementations of common NEX protocols which can be reused on many servers | ||
|
||
### Quazal Rendez-Vous | ||
Nintendo did not make NEX from scratch. NEX is largely based on an existing library called Rendez-Vous (QRV), made by Canadian software company Quazal. Quazal licensed Rendez-Vous out to many other companies, and was eventually bought out by Ubisoft. Because of this, QRV is seen in many many other games on all major platforms, especially Ubisoft | ||
|
||
Nintendo modified Rendez-Vous somewhat heavily, simplifying the library/transport protocol quite a bit, and adding several custom services | ||
|
||
### Usage note | ||
While the main goal of this library is to support games which use the NEX variant of Rendez-Vous made by Nintendo, we also aim to be compatible with games using the original Rendez-Vous library. Due to the extensible nature of Rendez-Vous, many games may feature customizations much like NEX and have non-standard features/behavior. We do our best to support these cases, but there may be times where supporting all variations becomes untenable. In those cases, a fork of these libraries should be made instead if they require heavy modifications | ||
|
||
This module provides a barebones PRUDP server for use with titles using the Nintendo NEX library. It does not provide any support for titles using the original Rendez-Vous library developed by Quazal. This library only provides the low level packet data, as such it is recommended to use [NEX Protocols Go](https://github.com/PretendoNetwork/nex-protocols-go) to develop servers. | ||
### Supported features | ||
- [x] Quazal compatibility mode/settings | ||
- [x] [HPP servers](https://nintendo-wiki.pretendo.network/docs/hpp) (NEX over HTTP) | ||
- [x] [PRUDP servers](https://nintendo-wiki.pretendo.network/docs/prudp) | ||
- [x] UDP transport | ||
- [x] WebSocket transport (Experimental, largely untested) | ||
- [x] PRUDPv0 packets | ||
- [x] PRUDPv1 packets | ||
- [x] PRUDPLite packets | ||
- [x] Fragmented packet payloads | ||
- [x] Packet retransmission | ||
- [x] Reliable packets | ||
- [x] Unreliable packets | ||
- [x] [Virtual ports](https://nintendo-wiki.pretendo.network/docs/prudp#virtual-ports) | ||
- [x] Packet compression | ||
- [x] [RMC](https://nintendo-wiki.pretendo.network/docs/rmc) | ||
- [x] Request messages | ||
- [x] Response messages | ||
- [x] "Packed" encoded messages | ||
- [x] "Packed" (extended) encoded messages | ||
- [x] "Verbose" encoded messages | ||
- [x] [Kerberos authentication](https://nintendo-wiki.pretendo.network/docs/nex/kerberos) | ||
|
||
### Usage | ||
### Example | ||
|
||
```go | ||
package main | ||
|
||
import ( | ||
"fmt" | ||
|
||
nex "github.com/PretendoNetwork/nex-go" | ||
"github.com/PretendoNetwork/nex-go" | ||
"github.com/PretendoNetwork/nex-go/types" | ||
) | ||
|
||
func main() { | ||
nexServer := nex.NewServer() | ||
nexServer.SetPrudpVersion(0) | ||
nexServer.SetSignatureVersion(1) | ||
nexServer.SetKerberosKeySize(16) | ||
nexServer.SetAccessKey("ridfebb9") | ||
|
||
nexServer.On("Data", func(packet *nex.PacketV0) { | ||
request := packet.RMCRequest() | ||
|
||
fmt.Println("==Friends - Auth==") | ||
fmt.Printf("Protocol ID: %#v\n", request.ProtocolID()) | ||
fmt.Printf("Method ID: %#v\n", request.MethodID()) | ||
fmt.Println("==================") | ||
// Skeleton of a WiiU/3DS Friends server running on PRUDPv0 with a single endpoint | ||
|
||
authServer := nex.NewPRUDPServer() // The main PRUDP server | ||
endpoint := nex.NewPRUDPEndPoint(1) // A PRUDP endpoint for PRUDP connections to connect to. Bound to StreamID 1 | ||
endpoint.ServerAccount = nex.NewAccount(types.NewPID(1), "Quazal Authentication", "password")) | ||
endpoint.AccountDetailsByPID = accountDetailsByPID | ||
endpoint.AccountDetailsByUsername = accountDetailsByUsername | ||
|
||
// Setup event handlers for the endpoint | ||
endpoint.OnData(func(packet nex.PacketInterface) { | ||
if packet, ok := packet.(nex.PRUDPPacketInterface); ok { | ||
request := packet.RMCMessage() | ||
|
||
fmt.Println("[AUTH]", request.ProtocolID, request.MethodID) | ||
|
||
if request.ProtocolID == 0xA { // TicketGrantingProtocol | ||
if request.MethodID == 0x1 { // TicketGrantingProtocol::Login | ||
handleLogin(packet) | ||
} | ||
|
||
if request.MethodID == 0x3 { // TicketGrantingProtocol::RequestTicket | ||
handleRequestTicket(packet) | ||
} | ||
} | ||
} | ||
}) | ||
|
||
nexServer.Listen(":60000") | ||
// Bind the endpoint to the server and configure it's settings | ||
authServer.BindPRUDPEndPoint(endpoint) | ||
authServer.SetFragmentSize(962) | ||
authServer.LibraryVersions.SetDefault(nex.NewLibraryVersion(1, 1, 0)) | ||
authServer.SessionKeyLength = 16 | ||
authServer.AccessKey = "ridfebb9" | ||
authServer.Listen(60000) | ||
} | ||
``` | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
package nex | ||
|
||
import "github.com/PretendoNetwork/nex-go/types" | ||
|
||
// Account represents a game server account. | ||
// | ||
// Game server accounts are separate from other accounts, like Uplay, Nintendo Accounts and NNIDs. | ||
// These exist only on the game server. Account passwords are used as part of the servers Kerberos | ||
// authentication. There are also a collection of non-user, special, accounts. These include a | ||
// guest account, an account which represents the authentication server, and one which represents | ||
// the secure server. See https://nintendo-wiki.pretendo.network/docs/nex/kerberos for more information. | ||
type Account struct { | ||
PID *types.PID // * The PID of the account. PIDs are unique IDs per account. NEX PIDs start at 1800000000 and decrement with each new account. | ||
Username string // * The username for the account. For NEX user accounts this is the same as the accounts PID. | ||
Password string // * The password for the account. For NEX accounts this is always 16 characters long using seemingly any ASCII character | ||
} | ||
|
||
// NewAccount returns a new instance of Account. | ||
// This does not register an account, only creates a new | ||
// struct instance. | ||
func NewAccount(pid *types.PID, username, password string) *Account { | ||
return &Account{ | ||
PID: pid, | ||
Username: username, | ||
Password: password, | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,177 @@ | ||
package nex | ||
|
||
import ( | ||
"errors" | ||
|
||
crunch "github.com/superwhiskers/crunch/v3" | ||
) | ||
|
||
// ByteStreamIn is an input stream abstraction of github.com/superwhiskers/crunch/v3 with nex type support | ||
type ByteStreamIn struct { | ||
*crunch.Buffer | ||
LibraryVersions *LibraryVersions | ||
Settings *ByteStreamSettings | ||
} | ||
|
||
// StringLengthSize returns the expected size of String length fields | ||
func (bsi *ByteStreamIn) StringLengthSize() int { | ||
size := 2 | ||
|
||
if bsi.Settings != nil { | ||
size = bsi.Settings.StringLengthSize | ||
} | ||
|
||
return size | ||
} | ||
|
||
// PIDSize returns the size of PID types | ||
func (bsi *ByteStreamIn) PIDSize() int { | ||
size := 4 | ||
|
||
if bsi.Settings != nil { | ||
size = bsi.Settings.PIDSize | ||
} | ||
|
||
return size | ||
} | ||
|
||
// UseStructureHeader determines if Structure headers should be used | ||
func (bsi *ByteStreamIn) UseStructureHeader() bool { | ||
useStructureHeader := false | ||
|
||
if bsi.Settings != nil { | ||
useStructureHeader = bsi.Settings.UseStructureHeader | ||
} | ||
|
||
return useStructureHeader | ||
} | ||
|
||
// Remaining returns the amount of data left to be read in the buffer | ||
func (bsi *ByteStreamIn) Remaining() uint64 { | ||
return uint64(len(bsi.Bytes()[bsi.ByteOffset():])) | ||
} | ||
|
||
// ReadRemaining reads all the data left to be read in the buffer | ||
func (bsi *ByteStreamIn) ReadRemaining() []byte { | ||
// * Can safely ignore this error, since bsi.Remaining() will never be less than itself | ||
remaining, _ := bsi.Read(uint64(bsi.Remaining())) | ||
|
||
return remaining | ||
} | ||
|
||
// Read reads the specified number of bytes. Returns an error if OOB | ||
func (bsi *ByteStreamIn) Read(length uint64) ([]byte, error) { | ||
if bsi.Remaining() < length { | ||
return []byte{}, errors.New("Read is OOB") | ||
} | ||
|
||
return bsi.ReadBytesNext(int64(length)), nil | ||
} | ||
|
||
// ReadPrimitiveUInt8 reads a uint8 | ||
func (bsi *ByteStreamIn) ReadPrimitiveUInt8() (uint8, error) { | ||
if bsi.Remaining() < 1 { | ||
return 0, errors.New("Not enough data to read uint8") | ||
} | ||
|
||
return uint8(bsi.ReadByteNext()), nil | ||
} | ||
|
||
// ReadPrimitiveUInt16LE reads a Little-Endian encoded uint16 | ||
func (bsi *ByteStreamIn) ReadPrimitiveUInt16LE() (uint16, error) { | ||
if bsi.Remaining() < 2 { | ||
return 0, errors.New("Not enough data to read uint16") | ||
} | ||
|
||
return bsi.ReadU16LENext(1)[0], nil | ||
} | ||
|
||
// ReadPrimitiveUInt32LE reads a Little-Endian encoded uint32 | ||
func (bsi *ByteStreamIn) ReadPrimitiveUInt32LE() (uint32, error) { | ||
if bsi.Remaining() < 4 { | ||
return 0, errors.New("Not enough data to read uint32") | ||
} | ||
|
||
return bsi.ReadU32LENext(1)[0], nil | ||
} | ||
|
||
// ReadPrimitiveUInt64LE reads a Little-Endian encoded uint64 | ||
func (bsi *ByteStreamIn) ReadPrimitiveUInt64LE() (uint64, error) { | ||
if bsi.Remaining() < 8 { | ||
return 0, errors.New("Not enough data to read uint64") | ||
} | ||
|
||
return bsi.ReadU64LENext(1)[0], nil | ||
} | ||
|
||
// ReadPrimitiveInt8 reads a uint8 | ||
func (bsi *ByteStreamIn) ReadPrimitiveInt8() (int8, error) { | ||
if bsi.Remaining() < 1 { | ||
return 0, errors.New("Not enough data to read int8") | ||
} | ||
|
||
return int8(bsi.ReadByteNext()), nil | ||
} | ||
|
||
// ReadPrimitiveInt16LE reads a Little-Endian encoded int16 | ||
func (bsi *ByteStreamIn) ReadPrimitiveInt16LE() (int16, error) { | ||
if bsi.Remaining() < 2 { | ||
return 0, errors.New("Not enough data to read int16") | ||
} | ||
|
||
return int16(bsi.ReadU16LENext(1)[0]), nil | ||
} | ||
|
||
// ReadPrimitiveInt32LE reads a Little-Endian encoded int32 | ||
func (bsi *ByteStreamIn) ReadPrimitiveInt32LE() (int32, error) { | ||
if bsi.Remaining() < 4 { | ||
return 0, errors.New("Not enough data to read int32") | ||
} | ||
|
||
return int32(bsi.ReadU32LENext(1)[0]), nil | ||
} | ||
|
||
// ReadPrimitiveInt64LE reads a Little-Endian encoded int64 | ||
func (bsi *ByteStreamIn) ReadPrimitiveInt64LE() (int64, error) { | ||
if bsi.Remaining() < 8 { | ||
return 0, errors.New("Not enough data to read int64") | ||
} | ||
|
||
return int64(bsi.ReadU64LENext(1)[0]), nil | ||
} | ||
|
||
// ReadPrimitiveFloat32LE reads a Little-Endian encoded float32 | ||
func (bsi *ByteStreamIn) ReadPrimitiveFloat32LE() (float32, error) { | ||
if bsi.Remaining() < 4 { | ||
return 0, errors.New("Not enough data to read float32") | ||
} | ||
|
||
return bsi.ReadF32LENext(1)[0], nil | ||
} | ||
|
||
// ReadPrimitiveFloat64LE reads a Little-Endian encoded float64 | ||
func (bsi *ByteStreamIn) ReadPrimitiveFloat64LE() (float64, error) { | ||
if bsi.Remaining() < 8 { | ||
return 0, errors.New("Not enough data to read float64") | ||
} | ||
|
||
return bsi.ReadF64LENext(1)[0], nil | ||
} | ||
|
||
// ReadPrimitiveBool reads a bool | ||
func (bsi *ByteStreamIn) ReadPrimitiveBool() (bool, error) { | ||
if bsi.Remaining() < 1 { | ||
return false, errors.New("Not enough data to read bool") | ||
} | ||
|
||
return bsi.ReadByteNext() == 1, nil | ||
} | ||
|
||
// NewByteStreamIn returns a new NEX input byte stream | ||
func NewByteStreamIn(data []byte, libraryVersions *LibraryVersions, settings *ByteStreamSettings) *ByteStreamIn { | ||
return &ByteStreamIn{ | ||
Buffer: crunch.NewBuffer(data), | ||
LibraryVersions: libraryVersions, | ||
Settings: settings, | ||
} | ||
} |
Oops, something went wrong.