mirror of
https://github.com/PretendoNetwork/friends.git
synced 2026-09-09 11:55:44 -05:00
Merge pull request #42 from PretendoNetwork/feat/grpc-friends-data
feat(grpc): implement fetch user data for Wii U & 3DS
This commit is contained in:
@@ -98,4 +98,5 @@ All configuration options are handled via environment variables
|
||||
| `PN_FRIENDS_CONFIG_ACCOUNT_GRPC_PORT` | Port for your account server gRPC service | Yes |
|
||||
| `PN_FRIENDS_CONFIG_ACCOUNT_GRPC_API_KEY` | API key for your account server gRPC service | No (Assumed to be an open gRPC API) |
|
||||
| `PN_FRIENDS_CONFIG_HEALTH_CHECK_PORT` | Port for the basic UDP health check server | No |
|
||||
| `PN_FRIENDS_CONFIG_ENABLE_BELLA` | Enables a debug user named "Bella" which is always assigned as your friend | No | No
|
||||
| `PN_FRIENDS_CONFIG_ENABLE_BELLA` | Enables a debug user named "Bella" which is always assigned as your friend | No |
|
||||
| `PN_FRIENDS_CONFIG_MII_DECRYPT_KEY` | AES key used to decrypt 3DS Mii data (as a hex string) | Yes |
|
||||
|
||||
44
database/3ds/get_mii.go
Normal file
44
database/3ds/get_mii.go
Normal file
@@ -0,0 +1,44 @@
|
||||
package database_3ds
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
|
||||
"github.com/PretendoNetwork/friends/database"
|
||||
"github.com/PretendoNetwork/nex-go/v2/types"
|
||||
friends_3ds_types "github.com/PretendoNetwork/nex-protocols-go/v2/friends-3ds/types"
|
||||
)
|
||||
|
||||
// GetMii returns the Mii of a specified user
|
||||
func GetMii(pid types.PID) (friends_3ds_types.FriendMii, error) {
|
||||
friendMii := friends_3ds_types.NewFriendMii()
|
||||
|
||||
rows, err := database.Manager.QueryRow(`
|
||||
SELECT mii_name, mii_profanity, mii_character_set, mii_data, mii_changed FROM "3ds".user_data WHERE pid=$1`, pid)
|
||||
if err != nil {
|
||||
return friendMii, err
|
||||
}
|
||||
|
||||
var miiName string
|
||||
var miiProfanity bool
|
||||
var miiCharacterSet uint8
|
||||
var miiData []byte
|
||||
var changedTime uint64
|
||||
|
||||
err = rows.Scan(&pid, &miiName, &miiProfanity, &miiCharacterSet, &miiData, &changedTime)
|
||||
if err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
return friendMii, database.ErrPIDNotFound
|
||||
} else {
|
||||
return friendMii, err
|
||||
}
|
||||
}
|
||||
|
||||
friendMii.PID = types.NewPID(uint64(pid))
|
||||
friendMii.Mii.Name = types.NewString(miiName)
|
||||
friendMii.Mii.ProfanityFlag = types.NewBool(miiProfanity)
|
||||
friendMii.Mii.CharacterSet = types.NewUInt8(miiCharacterSet)
|
||||
friendMii.Mii.MiiData = types.NewBuffer(miiData)
|
||||
friendMii.ModifiedAt = types.NewDateTime(changedTime)
|
||||
|
||||
return friendMii, nil
|
||||
}
|
||||
72
database/3ds/get_user_data.go
Normal file
72
database/3ds/get_user_data.go
Normal file
@@ -0,0 +1,72 @@
|
||||
package database_3ds
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
|
||||
"github.com/PretendoNetwork/friends/database"
|
||||
"github.com/PretendoNetwork/nex-go/v2/types"
|
||||
friends_3ds_types "github.com/PretendoNetwork/nex-protocols-go/v2/friends-3ds/types"
|
||||
)
|
||||
|
||||
// GetUserData returns a data for a specific user
|
||||
func GetUserData(pid types.PID) (friends_3ds_types.FriendPersistentInfo, error) {
|
||||
friendPersistentInfo := friends_3ds_types.NewFriendPersistentInfo()
|
||||
|
||||
row, err := database.Manager.QueryRow(`
|
||||
SELECT pid, region, area,
|
||||
language, country, favorite_title,
|
||||
favorite_title_version, comment,
|
||||
comment_changed, last_online, mii_changed
|
||||
FROM "3ds".user_data WHERE pid=$1
|
||||
`, pid)
|
||||
if err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
return friendPersistentInfo, database.ErrPIDNotFound
|
||||
} else {
|
||||
return friendPersistentInfo, err
|
||||
}
|
||||
}
|
||||
|
||||
var region uint8
|
||||
var area uint8
|
||||
var language uint8
|
||||
var country uint8
|
||||
var titleID uint64
|
||||
var titleVersion uint16
|
||||
var message string
|
||||
var lastOnlineTime uint64
|
||||
var msgUpdateTime uint64
|
||||
var miiModifiedAtTime uint64
|
||||
|
||||
err = row.Scan(
|
||||
&pid,
|
||||
®ion,
|
||||
&area,
|
||||
&language,
|
||||
&country,
|
||||
&titleID,
|
||||
&titleVersion,
|
||||
&message,
|
||||
&msgUpdateTime,
|
||||
&lastOnlineTime,
|
||||
&miiModifiedAtTime,
|
||||
)
|
||||
if err != nil {
|
||||
return friendPersistentInfo, err
|
||||
}
|
||||
|
||||
friendPersistentInfo.PID = types.NewPID(uint64(pid))
|
||||
friendPersistentInfo.Region = types.NewUInt8(region)
|
||||
friendPersistentInfo.Country = types.NewUInt8(country)
|
||||
friendPersistentInfo.Area = types.NewUInt8(area)
|
||||
friendPersistentInfo.Language = types.NewUInt8(language)
|
||||
friendPersistentInfo.Platform = types.NewUInt8(2) // * Always 3DS
|
||||
friendPersistentInfo.GameKey.TitleID = types.NewUInt64(titleID)
|
||||
friendPersistentInfo.GameKey.TitleVersion = types.NewUInt16(titleVersion)
|
||||
friendPersistentInfo.Message = types.NewString(message)
|
||||
friendPersistentInfo.MessageUpdatedAt = types.NewDateTime(msgUpdateTime)
|
||||
friendPersistentInfo.MiiModifiedAt = types.NewDateTime(miiModifiedAtTime)
|
||||
friendPersistentInfo.LastOnline = types.NewDateTime(lastOnlineTime)
|
||||
|
||||
return friendPersistentInfo, nil
|
||||
}
|
||||
86
database/wiiu/get_user_data.go
Normal file
86
database/wiiu/get_user_data.go
Normal file
@@ -0,0 +1,86 @@
|
||||
package database_wiiu
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
|
||||
"github.com/PretendoNetwork/friends/database"
|
||||
"github.com/PretendoNetwork/friends/globals"
|
||||
"github.com/PretendoNetwork/nex-go/v2/types"
|
||||
friends_wiiu_types "github.com/PretendoNetwork/nex-protocols-go/v2/friends-wiiu/types"
|
||||
)
|
||||
|
||||
// GetUserData returns a data for a specific user
|
||||
func GetUserData(pid types.PID) (friends_wiiu_types.FriendInfo, error) {
|
||||
friendInfo := friends_wiiu_types.NewFriendInfo()
|
||||
|
||||
row, err := database.Manager.QueryRow(`
|
||||
SELECT
|
||||
u.comment, u.comment_changed,
|
||||
u.last_online,
|
||||
bi.username, bi.unknown,
|
||||
ai.unknown1, ai.unknown2,
|
||||
mii.name, mii.unknown1, mii.unknown2, mii.data, mii.unknown_datetime
|
||||
FROM wiiu.user_data AS u
|
||||
INNER JOIN wiiu.principal_basic_info AS bi ON bi.pid = $1
|
||||
INNER JOIN wiiu.network_account_info AS ai ON ai.pid = $1
|
||||
INNER JOIN wiiu.mii AS mii ON mii.pid = $1
|
||||
WHERE u.pid=$1
|
||||
LIMIT 1
|
||||
`, pid)
|
||||
|
||||
if err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
return friendInfo, database.ErrPIDNotFound
|
||||
} else {
|
||||
return friendInfo, err
|
||||
}
|
||||
}
|
||||
var date uint64
|
||||
var lastOnlineTime uint64
|
||||
var commentContents string
|
||||
var commentChanged uint64 = 0
|
||||
var nnid string
|
||||
var unknown uint8
|
||||
var unknown1 uint8
|
||||
var unknown2 uint8
|
||||
var miiName string
|
||||
var miiUnknown1 uint8
|
||||
var miiUnknown2 uint8
|
||||
var miiData []byte
|
||||
var miiDatetime uint64
|
||||
|
||||
err = row.Scan(&commentContents, &commentChanged, &lastOnlineTime, &nnid, &unknown, &unknown1, &unknown2, &miiName, &miiUnknown1, &miiUnknown2, &miiData, &miiDatetime)
|
||||
if err != nil {
|
||||
return friendInfo, err
|
||||
}
|
||||
|
||||
friendInfo.NNAInfo.Unknown1 = types.NewUInt8(unknown1)
|
||||
friendInfo.NNAInfo.Unknown2 = types.NewUInt8(unknown2)
|
||||
friendInfo.NNAInfo.PrincipalBasicInfo.PID = types.NewPID(uint64(pid))
|
||||
friendInfo.NNAInfo.PrincipalBasicInfo.NNID = types.NewString(nnid)
|
||||
friendInfo.NNAInfo.PrincipalBasicInfo.Unknown = types.NewUInt8(unknown)
|
||||
friendInfo.NNAInfo.PrincipalBasicInfo.Mii.Name = types.NewString(miiName)
|
||||
friendInfo.NNAInfo.PrincipalBasicInfo.Mii.Unknown1 = types.NewUInt8(miiUnknown1)
|
||||
friendInfo.NNAInfo.PrincipalBasicInfo.Mii.Unknown2 = types.NewUInt8(miiUnknown2)
|
||||
friendInfo.NNAInfo.PrincipalBasicInfo.Mii.MiiData = types.NewBuffer(miiData)
|
||||
friendInfo.NNAInfo.PrincipalBasicInfo.Mii.Datetime = types.NewDateTime(miiDatetime)
|
||||
|
||||
lastOnline := types.NewDateTime(0).Now()
|
||||
connectedUser, ok := globals.ConnectedUsers.Get(uint32(pid))
|
||||
if ok && connectedUser != nil {
|
||||
// * Online
|
||||
friendInfo.Presence = connectedUser.PresenceV2.Copy().(friends_wiiu_types.NintendoPresenceV2)
|
||||
} else {
|
||||
// * Offline
|
||||
lastOnline = types.NewDateTime(lastOnlineTime) // TODO - Change this
|
||||
}
|
||||
|
||||
friendInfo.Status.Unknown = types.NewUInt8(0)
|
||||
friendInfo.Status.Contents = types.NewString(commentContents)
|
||||
friendInfo.Status.LastChanged = types.NewDateTime(commentChanged)
|
||||
friendInfo.BecameFriend = types.NewDateTime(date)
|
||||
friendInfo.LastOnline = lastOnline
|
||||
friendInfo.Unknown = types.NewUInt64(0)
|
||||
|
||||
return friendInfo, nil
|
||||
}
|
||||
@@ -14,6 +14,7 @@ type config struct {
|
||||
AccountGRPCAPIKey string `envconf:"optional"`
|
||||
HealthCheckPort uint16 `envconf:"optional"`
|
||||
EnableBella bool `envconf:"optional"`
|
||||
MiiDecryptKey string
|
||||
}
|
||||
|
||||
var Config *config = &config{}
|
||||
|
||||
4
go.mod
4
go.mod
@@ -5,7 +5,7 @@ go 1.24.0
|
||||
toolchain go1.24.3
|
||||
|
||||
require (
|
||||
github.com/PretendoNetwork/grpc/go v0.0.0-20260114221322-0631a1e0c840
|
||||
github.com/PretendoNetwork/grpc/go v0.0.0-20260501210425-981c793afb28
|
||||
github.com/PretendoNetwork/nex-go/v2 v2.3.0
|
||||
github.com/PretendoNetwork/nex-protocols-common-go/v2 v2.4.0
|
||||
github.com/PretendoNetwork/nex-protocols-go/v2 v2.2.2
|
||||
@@ -15,6 +15,7 @@ require (
|
||||
github.com/joho/godotenv v1.5.1
|
||||
github.com/lib/pq v1.10.9
|
||||
google.golang.org/grpc v1.78.0
|
||||
google.golang.org/protobuf v1.36.11
|
||||
)
|
||||
|
||||
require (
|
||||
@@ -43,5 +44,4 @@ require (
|
||||
golang.org/x/term v0.39.0 // indirect
|
||||
golang.org/x/text v0.33.0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260114163908-3f89685c29c3 // indirect
|
||||
google.golang.org/protobuf v1.36.11 // indirect
|
||||
)
|
||||
|
||||
4
go.sum
4
go.sum
@@ -1,5 +1,5 @@
|
||||
github.com/PretendoNetwork/grpc/go v0.0.0-20260114221322-0631a1e0c840 h1:IkflRrU2XT/8voysYxZTxcQYPyMBCt7yBHWy8U6Q/tU=
|
||||
github.com/PretendoNetwork/grpc/go v0.0.0-20260114221322-0631a1e0c840/go.mod h1:L6We4KkcQeiQVPrF7iu8Zax0B1Bm0v4nssR1JOAiRFQ=
|
||||
github.com/PretendoNetwork/grpc/go v0.0.0-20260501210425-981c793afb28 h1:BHRf3HF4Wyavo1+GAzaem+dqsox/V2asr91W3YY8GbI=
|
||||
github.com/PretendoNetwork/grpc/go v0.0.0-20260501210425-981c793afb28/go.mod h1:L6We4KkcQeiQVPrF7iu8Zax0B1Bm0v4nssR1JOAiRFQ=
|
||||
github.com/PretendoNetwork/nex-go/v2 v2.3.0 h1:CQNm/DzYhXvyzD/5l+Dxfp0/AbObuCfyfhLAeY6BejI=
|
||||
github.com/PretendoNetwork/nex-go/v2 v2.3.0/go.mod h1:2xKxiTtNxGliQ80xeicc6w3D53hmunOndoB3XJxUn/8=
|
||||
github.com/PretendoNetwork/nex-protocols-common-go/v2 v2.4.0 h1:EhXj1EDbNgdg40BPx/7n1HHsAy/DayGIWthu81UNyvI=
|
||||
|
||||
97
grpc/get_user_data_3ds.go
Normal file
97
grpc/get_user_data_3ds.go
Normal file
@@ -0,0 +1,97 @@
|
||||
package grpc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/PretendoNetwork/friends/database"
|
||||
database_3ds "github.com/PretendoNetwork/friends/database/3ds"
|
||||
"github.com/PretendoNetwork/friends/globals"
|
||||
"github.com/PretendoNetwork/friends/utility"
|
||||
pb "github.com/PretendoNetwork/grpc/go/friends/v2"
|
||||
"github.com/PretendoNetwork/nex-go/v2/types"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
)
|
||||
|
||||
func (s *gRPCFriendsV2Server) GetUserData3DS(ctx context.Context, in *pb.GetUserData3DSRequest) (*pb.GetUserData3DSResponse, error) {
|
||||
user, err := database_3ds.GetUserData(types.PID(in.GetPid()))
|
||||
if err != nil {
|
||||
if err == database.ErrPIDNotFound {
|
||||
return nil, status.Errorf(codes.NotFound, "PID was not found")
|
||||
} else {
|
||||
globals.Logger.Critical(err.Error())
|
||||
return nil, status.Errorf(codes.Internal, "Internal error")
|
||||
}
|
||||
}
|
||||
|
||||
miiData, err := database_3ds.GetMii(types.PID(in.GetPid()))
|
||||
if err != nil {
|
||||
if err == database.ErrPIDNotFound {
|
||||
return nil, status.Errorf(codes.NotFound, "PID was not found")
|
||||
} else {
|
||||
globals.Logger.Critical(err.Error())
|
||||
return nil, status.Errorf(codes.Internal, "Internal error")
|
||||
}
|
||||
}
|
||||
|
||||
gameKey := &pb.GameKey{
|
||||
TitleId: uint64(user.GameKey.TitleID),
|
||||
TitleVersion: uint32(user.GameKey.TitleVersion),
|
||||
}
|
||||
|
||||
mii := &pb.Mii{
|
||||
Name: string(miiData.Mii.Name),
|
||||
ProfanityFlag: bool(miiData.Mii.ProfanityFlag),
|
||||
CharacterSet: uint32(miiData.Mii.CharacterSet),
|
||||
MiiDataEncrypted: miiData.Mii.MiiData,
|
||||
}
|
||||
|
||||
mii_data, err := utility.DecryptMiiData(miiData.Mii.MiiData)
|
||||
if err == nil {
|
||||
mii.MiiData = mii_data
|
||||
}
|
||||
friendMii := &pb.FriendMii{
|
||||
Pid: uint32(miiData.PID),
|
||||
Mii: mii,
|
||||
ModifiedAt: timestamppb.New(time.Unix(int64(miiData.ModifiedAt.Second()), 0)),
|
||||
}
|
||||
|
||||
presence := &pb.NintendoPresence{}
|
||||
connectedUser, ok := globals.ConnectedUsers.Get(uint32(user.PID))
|
||||
if ok && connectedUser != nil {
|
||||
presence.ChangedFlags = uint32(connectedUser.Presence.ChangedFlags)
|
||||
presence.GameKey = &pb.GameKey{
|
||||
TitleId: uint64(connectedUser.Presence.GameKey.TitleID),
|
||||
TitleVersion: uint32(connectedUser.Presence.GameKey.TitleVersion),
|
||||
}
|
||||
presence.Message = string(connectedUser.Presence.Message)
|
||||
presence.JoinAvailableFlag = uint32(connectedUser.Presence.JoinAvailableFlag)
|
||||
presence.MatchmakeType = uint32(connectedUser.Presence.MatchmakeType)
|
||||
presence.JoinGameId = uint32(connectedUser.Presence.JoinGameID)
|
||||
presence.JoinGameMode = uint32(connectedUser.Presence.JoinGameMode)
|
||||
presence.OwnerPid = uint32(connectedUser.Presence.OwnerPID)
|
||||
presence.JoinGroupId = uint32(connectedUser.Presence.JoinGroupID)
|
||||
presence.ApplicationArg = connectedUser.Presence.ApplicationArg
|
||||
}
|
||||
|
||||
info := &pb.FriendInfo3DS{
|
||||
Pid: uint32(user.PID),
|
||||
Region: uint32(user.Region),
|
||||
Country: uint32(user.Country),
|
||||
Area: uint32(user.Area),
|
||||
Language: uint32(user.Language),
|
||||
Platform: uint32(user.Platform),
|
||||
Presence: presence,
|
||||
GameKey: gameKey,
|
||||
Message: string(user.Message),
|
||||
MessageUpdatedAt: timestamppb.New(user.MessageUpdatedAt.Standard()),
|
||||
MiiModifiedAt: timestamppb.New(user.MiiModifiedAt.Standard()),
|
||||
LastOnline: timestamppb.New(user.LastOnline.Standard()),
|
||||
Mii: friendMii,
|
||||
}
|
||||
return &pb.GetUserData3DSResponse{
|
||||
User: info,
|
||||
}, nil
|
||||
}
|
||||
75
grpc/get_user_data_wiiu.go
Normal file
75
grpc/get_user_data_wiiu.go
Normal file
@@ -0,0 +1,75 @@
|
||||
package grpc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/PretendoNetwork/friends/database"
|
||||
database_wiiu "github.com/PretendoNetwork/friends/database/wiiu"
|
||||
"github.com/PretendoNetwork/friends/globals"
|
||||
pb "github.com/PretendoNetwork/grpc/go/friends/v2"
|
||||
"github.com/PretendoNetwork/nex-go/v2/types"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
)
|
||||
|
||||
func (s *gRPCFriendsV2Server) GetUserDataWiiU(ctx context.Context, in *pb.GetUserDataWiiURequest) (*pb.GetUserDataWiiUResponse, error) {
|
||||
user, err := database_wiiu.GetUserData(types.PID(in.GetPid()))
|
||||
if err != nil {
|
||||
if err == database.ErrPIDNotFound {
|
||||
return nil, status.Errorf(codes.NotFound, "PID was not found")
|
||||
} else {
|
||||
globals.Logger.Critical(err.Error())
|
||||
return nil, status.Errorf(codes.Internal, "Internal error")
|
||||
}
|
||||
}
|
||||
|
||||
comment := &pb.Comment{
|
||||
Contents: string(user.Status.Contents),
|
||||
LastChanged: timestamppb.New(time.Unix(int64(user.Status.LastChanged.Second()), 0)),
|
||||
}
|
||||
|
||||
mii := &pb.MiiV2{
|
||||
Name: string(user.NNAInfo.PrincipalBasicInfo.Mii.Name),
|
||||
MiiData: user.NNAInfo.PrincipalBasicInfo.Mii.MiiData,
|
||||
Datetime: timestamppb.New(time.Unix(int64(user.NNAInfo.PrincipalBasicInfo.Mii.Datetime.Second()), 0)),
|
||||
}
|
||||
|
||||
principal := &pb.PrincipalBasicInfo{
|
||||
Pid: uint32(user.NNAInfo.PrincipalBasicInfo.PID),
|
||||
Nnid: string(user.NNAInfo.PrincipalBasicInfo.NNID),
|
||||
Mii: mii,
|
||||
}
|
||||
|
||||
nnaInfo := &pb.NNAInfo{
|
||||
PrincipalBasicInfo: principal,
|
||||
}
|
||||
|
||||
gameKey := &pb.GameKey{
|
||||
TitleId: uint64(user.Presence.GameKey.TitleID),
|
||||
TitleVersion: uint32(user.Presence.GameKey.TitleVersion),
|
||||
}
|
||||
|
||||
presence := &pb.NintendoPresenceV2{
|
||||
ChangedFlags: uint32(user.Presence.ChangedFlags),
|
||||
Online: bool(user.Presence.Online),
|
||||
GameKey: gameKey,
|
||||
Message: string(user.Presence.Message),
|
||||
GameServerId: uint32(user.Presence.GameServerID),
|
||||
Pid: uint32(user.Presence.PID),
|
||||
GatheringId: uint32(user.Presence.GatheringID),
|
||||
ApplicationData: user.Presence.ApplicationData,
|
||||
}
|
||||
|
||||
info := &pb.FriendInfoWiiU{
|
||||
NnaInfo: nnaInfo,
|
||||
Presence: presence,
|
||||
Status: comment,
|
||||
BecameFriend: timestamppb.New(user.BecameFriend.Standard()),
|
||||
LastOnline: timestamppb.New(user.LastOnline.Standard()),
|
||||
}
|
||||
return &pb.GetUserDataWiiUResponse{
|
||||
User: info,
|
||||
}, nil
|
||||
}
|
||||
174
grpc/get_user_friends_data_3ds.go
Normal file
174
grpc/get_user_friends_data_3ds.go
Normal file
@@ -0,0 +1,174 @@
|
||||
package grpc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"time"
|
||||
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
|
||||
"github.com/PretendoNetwork/friends/database"
|
||||
database_3ds "github.com/PretendoNetwork/friends/database/3ds"
|
||||
"github.com/PretendoNetwork/friends/globals"
|
||||
"github.com/PretendoNetwork/friends/utility"
|
||||
pb "github.com/PretendoNetwork/grpc/go/friends/v2"
|
||||
"github.com/PretendoNetwork/nex-go/v2/types"
|
||||
friends_3ds_types "github.com/PretendoNetwork/nex-protocols-go/v2/friends-3ds/types"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
)
|
||||
|
||||
func (s *gRPCFriendsV2Server) GetUserFriendsData3DS(ctx context.Context, in *pb.GetUserFriendsData3DSRequest) (*pb.GetUserFriendsData3DSResponse, error) {
|
||||
var friends []*pb.FriendInfo3DS
|
||||
friendList, err := database_3ds.GetUserFriends(in.Pid)
|
||||
if err != nil && err != database.ErrEmptyList {
|
||||
globals.Logger.Critical(err.Error())
|
||||
return &pb.GetUserFriendsData3DSResponse{
|
||||
Friends: friends,
|
||||
}, status.Errorf(codes.Internal, "internal server error")
|
||||
}
|
||||
|
||||
friendPIDs := make([]uint32, len(friendList))
|
||||
|
||||
for _, friend := range friendList {
|
||||
friendPIDs = append(friendPIDs, uint32(friend.PID))
|
||||
}
|
||||
|
||||
friendInfoList, err := database_3ds.GetFriendPersistentInfos(uint32(in.Pid), friendPIDs)
|
||||
if err != nil && err != sql.ErrNoRows {
|
||||
globals.Logger.Critical(err.Error())
|
||||
return &pb.GetUserFriendsData3DSResponse{
|
||||
Friends: friends,
|
||||
}, status.Errorf(codes.Internal, "internal server error")
|
||||
}
|
||||
|
||||
miiList, err := database_3ds.GetFriendMiis(friendPIDs)
|
||||
if err != nil && err != sql.ErrNoRows {
|
||||
globals.Logger.Critical(err.Error())
|
||||
return &pb.GetUserFriendsData3DSResponse{
|
||||
Friends: friends,
|
||||
}, status.Errorf(codes.Internal, "internal server error")
|
||||
}
|
||||
|
||||
if globals.Config.EnableBella {
|
||||
bella := friends_3ds_types.NewFriendPersistentInfo()
|
||||
|
||||
bella.PID = types.NewPID(1743126339)
|
||||
bella.Region = types.NewUInt8(0)
|
||||
bella.Country = types.NewUInt8(0)
|
||||
bella.Area = types.NewUInt8(0)
|
||||
bella.Language = types.NewUInt8(0)
|
||||
bella.Platform = types.NewUInt8(0)
|
||||
bella.GameKey.TitleID = 0x0005000010176900
|
||||
bella.GameKey.TitleVersion = types.NewUInt16(0)
|
||||
bella.Message = "Howdy Hey!"
|
||||
bella.MessageUpdatedAt = types.NewDateTime(0)
|
||||
bella.MiiModifiedAt = types.NewDateTime(0)
|
||||
bella.LastOnline = types.NewDateTime(0)
|
||||
|
||||
mii := friends_3ds_types.NewMii()
|
||||
mii.Name = types.NewString("Bandwidth")
|
||||
mii.ProfanityFlag = types.NewBool(false)
|
||||
mii.CharacterSet = types.NewUInt8(0)
|
||||
mii.MiiData = types.NewBuffer([]byte{
|
||||
0x03, 0x00, 0x00, 0x40, 0xE9, 0x55, 0xA2, 0x09,
|
||||
0xE7, 0xC7, 0x41, 0x82, 0xD9, 0x7D, 0x0B, 0x2D,
|
||||
0x03, 0xB3, 0xB8, 0x8D, 0x27, 0xD9, 0x00, 0x00,
|
||||
0x01, 0x40, 0x62, 0x00, 0x65, 0x00, 0x6C, 0x00,
|
||||
0x6C, 0x00, 0x61, 0x00, 0x00, 0x00, 0x45, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x40,
|
||||
0x12, 0x00, 0x81, 0x01, 0x04, 0x68, 0x43, 0x18,
|
||||
0x20, 0x34, 0x46, 0x14, 0x81, 0x12, 0x17, 0x68,
|
||||
0x0D, 0x00, 0x00, 0x29, 0x03, 0x52, 0x48, 0x50,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFE, 0x86,
|
||||
})
|
||||
|
||||
friendMii := friends_3ds_types.NewFriendMii()
|
||||
friendMii.PID = types.NewPID(uint64(bella.PID))
|
||||
friendMii.Mii = mii
|
||||
friendMii.ModifiedAt = types.NewDateTime(0)
|
||||
|
||||
friendInfoList = append(friendInfoList, bella)
|
||||
miiList = append(miiList, friendMii)
|
||||
}
|
||||
|
||||
for _, friend := range friendInfoList {
|
||||
gameKey := &pb.GameKey{
|
||||
TitleId: uint64(friend.GameKey.TitleID),
|
||||
TitleVersion: uint32(friend.GameKey.TitleVersion),
|
||||
}
|
||||
|
||||
miiIndex := -1
|
||||
for index, mii := range miiList {
|
||||
if mii.PID == friend.PID {
|
||||
miiIndex = index
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if miiIndex == -1 {
|
||||
continue
|
||||
}
|
||||
|
||||
miiData := miiList[miiIndex]
|
||||
mii := &pb.Mii{
|
||||
Name: string(miiData.Mii.Name),
|
||||
ProfanityFlag: bool(miiData.Mii.ProfanityFlag),
|
||||
CharacterSet: uint32(miiData.Mii.CharacterSet),
|
||||
MiiDataEncrypted: miiData.Mii.MiiData,
|
||||
}
|
||||
|
||||
mii_data, err := utility.DecryptMiiData(miiData.Mii.MiiData)
|
||||
if err == nil {
|
||||
mii.MiiData = mii_data
|
||||
}
|
||||
|
||||
friendMii := &pb.FriendMii{
|
||||
Pid: uint32(miiData.PID),
|
||||
Mii: mii,
|
||||
ModifiedAt: timestamppb.New(time.Unix(int64(miiData.ModifiedAt.Second()), 0)),
|
||||
}
|
||||
|
||||
presence := &pb.NintendoPresence{}
|
||||
connectedUser, ok := globals.ConnectedUsers.Get(uint32(friend.PID))
|
||||
|
||||
if ok && connectedUser != nil {
|
||||
presence.ChangedFlags = uint32(connectedUser.Presence.ChangedFlags)
|
||||
presence.GameKey = &pb.GameKey{
|
||||
TitleId: uint64(connectedUser.Presence.GameKey.TitleID),
|
||||
TitleVersion: uint32(connectedUser.Presence.GameKey.TitleVersion),
|
||||
}
|
||||
presence.Message = string(connectedUser.Presence.Message)
|
||||
presence.JoinAvailableFlag = uint32(connectedUser.Presence.JoinAvailableFlag)
|
||||
presence.MatchmakeType = uint32(connectedUser.Presence.MatchmakeType)
|
||||
presence.JoinGameId = uint32(connectedUser.Presence.JoinGameID)
|
||||
presence.JoinGameMode = uint32(connectedUser.Presence.JoinGameMode)
|
||||
presence.OwnerPid = uint32(connectedUser.Presence.OwnerPID)
|
||||
presence.JoinGroupId = uint32(connectedUser.Presence.JoinGroupID)
|
||||
presence.ApplicationArg = connectedUser.Presence.ApplicationArg
|
||||
}
|
||||
|
||||
info := &pb.FriendInfo3DS{
|
||||
Pid: uint32(friend.PID),
|
||||
Region: uint32(friend.Region),
|
||||
Country: uint32(friend.Country),
|
||||
Area: uint32(friend.Area),
|
||||
Language: uint32(friend.Language),
|
||||
Platform: uint32(friend.Platform),
|
||||
Presence: presence,
|
||||
GameKey: gameKey,
|
||||
Message: string(friend.Message),
|
||||
MessageUpdatedAt: timestamppb.New(friend.MessageUpdatedAt.Standard()),
|
||||
MiiModifiedAt: timestamppb.New(friend.MiiModifiedAt.Standard()),
|
||||
LastOnline: timestamppb.New(friend.LastOnline.Standard()),
|
||||
Mii: friendMii,
|
||||
}
|
||||
friends = append(friends, info)
|
||||
}
|
||||
|
||||
return &pb.GetUserFriendsData3DSResponse{
|
||||
Friends: friends,
|
||||
}, nil
|
||||
}
|
||||
145
grpc/get_user_friends_data_wiiu.go
Normal file
145
grpc/get_user_friends_data_wiiu.go
Normal file
@@ -0,0 +1,145 @@
|
||||
package grpc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
|
||||
"github.com/PretendoNetwork/friends/database"
|
||||
database_wiiu "github.com/PretendoNetwork/friends/database/wiiu"
|
||||
"github.com/PretendoNetwork/friends/globals"
|
||||
pb "github.com/PretendoNetwork/grpc/go/friends/v2"
|
||||
"github.com/PretendoNetwork/nex-go/v2/types"
|
||||
friends_wiiu_types "github.com/PretendoNetwork/nex-protocols-go/v2/friends-wiiu/types"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
)
|
||||
|
||||
func (s *gRPCFriendsV2Server) GetUserFriendsDataWiiU(ctx context.Context, in *pb.GetUserFriendsDataWiiURequest) (*pb.GetUserFriendsDataWiiUResponse, error) {
|
||||
var friends []*pb.FriendInfoWiiU
|
||||
friendList, err := database_wiiu.GetUserFriendList(in.Pid)
|
||||
if err != nil && err != database.ErrEmptyList {
|
||||
globals.Logger.Critical(err.Error())
|
||||
return &pb.GetUserFriendsDataWiiUResponse{
|
||||
Friends: friends,
|
||||
}, status.Errorf(codes.Internal, "internal server error")
|
||||
}
|
||||
|
||||
if globals.Config.EnableBella {
|
||||
bella := friends_wiiu_types.NewFriendInfo()
|
||||
|
||||
bella.NNAInfo = friends_wiiu_types.NewNNAInfo()
|
||||
bella.Presence = friends_wiiu_types.NewNintendoPresenceV2()
|
||||
bella.Status = friends_wiiu_types.NewComment()
|
||||
bella.BecameFriend = types.NewDateTime(0)
|
||||
bella.LastOnline = types.NewDateTime(0)
|
||||
bella.Unknown = types.NewUInt64(0)
|
||||
|
||||
bella.NNAInfo.PrincipalBasicInfo = friends_wiiu_types.NewPrincipalBasicInfo()
|
||||
bella.NNAInfo.Unknown1 = types.NewUInt8(0)
|
||||
bella.NNAInfo.Unknown2 = types.NewUInt8(0)
|
||||
|
||||
bella.NNAInfo.PrincipalBasicInfo.PID = types.NewPID(1743126339)
|
||||
bella.NNAInfo.PrincipalBasicInfo.NNID = types.NewString("PN_Testing")
|
||||
bella.NNAInfo.PrincipalBasicInfo.Mii = friends_wiiu_types.NewMiiV2()
|
||||
bella.NNAInfo.PrincipalBasicInfo.Unknown = types.NewUInt8(0)
|
||||
|
||||
bella.NNAInfo.PrincipalBasicInfo.Mii.Name = types.NewString("Bandwidth")
|
||||
bella.NNAInfo.PrincipalBasicInfo.Mii.Unknown1 = types.NewUInt8(0)
|
||||
bella.NNAInfo.PrincipalBasicInfo.Mii.Unknown2 = types.NewUInt8(0)
|
||||
bella.NNAInfo.PrincipalBasicInfo.Mii.MiiData = types.NewBuffer([]byte{
|
||||
0x03, 0x00, 0x00, 0x40, 0xE9, 0x55, 0xA2, 0x09,
|
||||
0xE7, 0xC7, 0x41, 0x82, 0xD9, 0x7D, 0x0B, 0x2D,
|
||||
0x03, 0xB3, 0xB8, 0x8D, 0x27, 0xD9, 0x00, 0x00,
|
||||
0x01, 0x40, 0x62, 0x00, 0x65, 0x00, 0x6C, 0x00,
|
||||
0x6C, 0x00, 0x61, 0x00, 0x00, 0x00, 0x45, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x40,
|
||||
0x12, 0x00, 0x81, 0x01, 0x04, 0x68, 0x43, 0x18,
|
||||
0x20, 0x34, 0x46, 0x14, 0x81, 0x12, 0x17, 0x68,
|
||||
0x0D, 0x00, 0x00, 0x29, 0x03, 0x52, 0x48, 0x50,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFE, 0x86,
|
||||
})
|
||||
bella.NNAInfo.PrincipalBasicInfo.Mii.Datetime = types.NewDateTime(0)
|
||||
|
||||
bella.Presence.ChangedFlags = types.NewUInt32(0x1EE)
|
||||
bella.Presence.Online = types.NewBool(true)
|
||||
bella.Presence.GameKey = friends_wiiu_types.NewGameKey()
|
||||
bella.Presence.Unknown1 = types.NewUInt8(0)
|
||||
bella.Presence.Message = types.NewString("Testing")
|
||||
bella.Presence.Unknown2 = types.NewUInt32(0)
|
||||
bella.Presence.Unknown3 = types.NewUInt8(0)
|
||||
bella.Presence.GameServerID = types.NewUInt32(0)
|
||||
bella.Presence.Unknown4 = types.NewUInt32(0)
|
||||
bella.Presence.PID = types.NewPID(1743126339)
|
||||
bella.Presence.GatheringID = types.NewUInt32(0)
|
||||
bella.Presence.ApplicationData = types.NewBuffer([]byte{0x0})
|
||||
bella.Presence.Unknown5 = types.NewUInt8(0)
|
||||
bella.Presence.Unknown6 = types.NewUInt8(0)
|
||||
bella.Presence.Unknown7 = types.NewUInt8(0)
|
||||
|
||||
bella.Presence.GameKey.TitleID = 0x0005000010176900
|
||||
bella.Presence.GameKey.TitleVersion = types.NewUInt16(0)
|
||||
|
||||
bella.Status.Unknown = types.NewUInt8(0)
|
||||
bella.Status.Contents = types.NewString("Greetings programs!")
|
||||
bella.Status.LastChanged = types.NewDateTime(0)
|
||||
|
||||
friendList = append(friendList, bella)
|
||||
}
|
||||
|
||||
for _, friend := range friendList {
|
||||
comment := &pb.Comment{
|
||||
Contents: string(friend.Status.Contents),
|
||||
LastChanged: timestamppb.New(time.Unix(int64(friend.Status.LastChanged.Second()), 0)),
|
||||
}
|
||||
|
||||
mii := &pb.MiiV2{
|
||||
Name: string(friend.NNAInfo.PrincipalBasicInfo.Mii.Name),
|
||||
MiiData: friend.NNAInfo.PrincipalBasicInfo.Mii.MiiData,
|
||||
Datetime: timestamppb.New(time.Unix(int64(friend.NNAInfo.PrincipalBasicInfo.Mii.Datetime.Second()), 0)),
|
||||
}
|
||||
|
||||
principal := &pb.PrincipalBasicInfo{
|
||||
Pid: uint32(friend.NNAInfo.PrincipalBasicInfo.PID),
|
||||
Nnid: string(friend.NNAInfo.PrincipalBasicInfo.NNID),
|
||||
Mii: mii,
|
||||
}
|
||||
|
||||
nnaInfo := &pb.NNAInfo{
|
||||
PrincipalBasicInfo: principal,
|
||||
}
|
||||
|
||||
gameKey := &pb.GameKey{
|
||||
TitleId: uint64(friend.Presence.GameKey.TitleID),
|
||||
TitleVersion: uint32(friend.Presence.GameKey.TitleVersion),
|
||||
}
|
||||
|
||||
presence := &pb.NintendoPresenceV2{
|
||||
ChangedFlags: uint32(friend.Presence.ChangedFlags),
|
||||
Online: bool(friend.Presence.Online),
|
||||
GameKey: gameKey,
|
||||
Message: string(friend.Presence.Message),
|
||||
GameServerId: uint32(friend.Presence.GameServerID),
|
||||
Pid: uint32(friend.Presence.PID),
|
||||
GatheringId: uint32(friend.Presence.GatheringID),
|
||||
ApplicationData: friend.Presence.ApplicationData,
|
||||
}
|
||||
|
||||
info := &pb.FriendInfoWiiU{
|
||||
NnaInfo: nnaInfo,
|
||||
Presence: presence,
|
||||
Status: comment,
|
||||
BecameFriend: timestamppb.New(friend.BecameFriend.Standard()),
|
||||
LastOnline: timestamppb.New(friend.LastOnline.Standard()),
|
||||
}
|
||||
|
||||
friends = append(friends, info)
|
||||
}
|
||||
|
||||
return &pb.GetUserFriendsDataWiiUResponse{
|
||||
Friends: friends,
|
||||
}, nil
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
|
||||
"github.com/PretendoNetwork/friends/globals"
|
||||
pb "github.com/PretendoNetwork/grpc/go/friends"
|
||||
pbv2 "github.com/PretendoNetwork/grpc/go/friends/v2"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
@@ -14,6 +15,10 @@ type gRPCFriendsServer struct {
|
||||
pb.UnimplementedFriendsServer
|
||||
}
|
||||
|
||||
type gRPCFriendsV2Server struct {
|
||||
pbv2.UnimplementedFriendsServiceServer
|
||||
}
|
||||
|
||||
func StartGRPCServer() {
|
||||
listener, err := net.Listen("tcp", fmt.Sprintf(":%d", globals.Config.GRPCServerPort))
|
||||
if err != nil {
|
||||
@@ -25,6 +30,7 @@ func StartGRPCServer() {
|
||||
)
|
||||
|
||||
pb.RegisterFriendsServer(server, &gRPCFriendsServer{})
|
||||
pbv2.RegisterFriendsServiceServer(server, &gRPCFriendsV2Server{})
|
||||
|
||||
log.Printf("server listening at %v", listener.Addr())
|
||||
|
||||
|
||||
17
init.go
17
init.go
@@ -1,6 +1,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
@@ -71,5 +72,21 @@ func init() {
|
||||
"X-API-Key", globals.Config.AccountGRPCAPIKey,
|
||||
)
|
||||
|
||||
if strings.TrimSpace(globals.Config.MiiDecryptKey) == "" {
|
||||
globals.Logger.Warning("PN_FRIENDS_CONFIG_MII_DECRYPT_KEY environment variable not set. 3DS Mii data cannot be decrypted")
|
||||
}
|
||||
|
||||
miiKeyBytes, err := hex.DecodeString(globals.Config.MiiDecryptKey)
|
||||
if err != nil {
|
||||
globals.Logger.Criticalf("Failed to decode PN_FRIENDS_CONFIG_MII_DECRYPT_KEY %v", err)
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
miiMD5Hash := md5.Sum(miiKeyBytes)
|
||||
if hex.EncodeToString(miiMD5Hash[:]) != "aeb707b225ec0fcd8a503e26e3dcd596" {
|
||||
globals.Logger.Criticalf("PN_FRIENDS_CONFIG_MII_DECRYPT_KEY is incorrect! md5: %s", hex.EncodeToString(miiMD5Hash[:]))
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
database.ConnectPostgres()
|
||||
}
|
||||
|
||||
57
utility/decrypt_mii.go
Normal file
57
utility/decrypt_mii.go
Normal file
@@ -0,0 +1,57 @@
|
||||
package utility
|
||||
|
||||
import (
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
|
||||
"github.com/PretendoNetwork/friends/globals"
|
||||
"github.com/PretendoNetwork/nex-go/v2/types"
|
||||
)
|
||||
|
||||
func ccmDecrypt(key, nonce, ciphertext []byte) ([]byte, error) {
|
||||
block, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
L := 15 - len(nonce)
|
||||
|
||||
counter := make([]byte, aes.BlockSize)
|
||||
counter[0] = byte(L - 1)
|
||||
copy(counter[1:1+len(nonce)], nonce)
|
||||
stream := cipher.NewCTR(block, counter)
|
||||
|
||||
padded := make([]byte, aes.BlockSize+len(ciphertext))
|
||||
copy(padded[aes.BlockSize:], ciphertext)
|
||||
|
||||
out := make([]byte, len(padded))
|
||||
stream.XORKeyStream(out, padded)
|
||||
|
||||
return out[aes.BlockSize:], nil
|
||||
}
|
||||
|
||||
func DecryptMiiData(miiData types.Buffer) ([]byte, error) {
|
||||
if len(miiData) < 0x70 {
|
||||
return nil, fmt.Errorf("Mii data length is incorrect: %d", len(miiData))
|
||||
}
|
||||
nonce := miiData[:8]
|
||||
ciphertext := miiData[8 : 8+0x58]
|
||||
|
||||
key, err := hex.DecodeString(globals.Config.MiiDecryptKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
content, err := ccmDecrypt(key, append(append([]byte{}, nonce...), 0, 0, 0, 0), ciphertext)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result := make([]byte, 0, len(content)+len(nonce))
|
||||
result = append(result, content[:12]...)
|
||||
result = append(result, nonce...)
|
||||
result = append(result, content[12:]...)
|
||||
|
||||
return result, nil
|
||||
}
|
||||
Reference in New Issue
Block a user