diff --git a/ranking/get_cached_top_x_ranking.go b/ranking/get_cached_top_x_ranking.go new file mode 100644 index 0000000..5201a2d --- /dev/null +++ b/ranking/get_cached_top_x_ranking.go @@ -0,0 +1,81 @@ +package ranking + +import ( + "github.com/PretendoNetwork/nex-go" + ranking "github.com/PretendoNetwork/nex-protocols-go/ranking" + ranking_types "github.com/PretendoNetwork/nex-protocols-go/ranking/types" + + "time" +) + +func getCachedTopXRanking(err error, client *nex.Client, callID uint32, category uint32, orderParam *ranking_types.RankingOrderParam) uint32 { + if commonRankingProtocol.getRankingsAndCountByCategoryAndRankingOrderParamHandler == nil { + logger.Warning("Ranking::GetCachedTopXRanking missing GetRankingsAndCountByCategoryAndRankingOrderParamHandler!") + return nex.Errors.Core.NotImplemented + } + + server := client.Server() + + if err != nil { + logger.Error(err.Error()) + return nex.Errors.Ranking.InvalidArgument + } + + rankDataList, totalCount, err := commonRankingProtocol.getRankingsAndCountByCategoryAndRankingOrderParamHandler(category, orderParam) + if err != nil { + logger.Critical(err.Error()) + return nex.Errors.Ranking.Unknown + } + + if totalCount == 0 || len(rankDataList) == 0 { + return nex.Errors.Ranking.NotFound + } + + rankingResult := ranking_types.NewRankingResult() + + rankingResult.RankDataList = rankDataList + rankingResult.TotalCount = totalCount + rankingResult.SinceTime = nex.NewDateTime(0x1f40420000) // * 2000-01-01T00:00:00.000Z, this is what the real server sends back + + pResult := ranking_types.NewRankingCachedResult() + serverTime := nex.NewDateTime(0) + pResult.CreatedTime = nex.NewDateTime(serverTime.UTC()) + //The real server sends the "CreatedTime" + 5 minutes. + //It doesn't change, even on subsequent requests, until after the ExpiredTime has passed (seemingly what the "cached" means). + //Whether we need to replicate this idk, but in case, here's a note. + pResult.ExpiredTime = nex.NewDateTime(serverTime.FromTimestamp(time.Now().UTC().Add(time.Minute * time.Duration(5)))) + pResult.MaxLength = 10 //This is the length Ultimate NES Remix uses. TODO: Does this matter? and are other games different? + + rmcResponseStream := nex.NewStreamOut(server) + + rmcResponseStream.WriteStructure(pResult) + + rmcResponseBody := rmcResponseStream.Bytes() + + rmcResponse := nex.NewRMCResponse(ranking.ProtocolID, callID) + rmcResponse.SetSuccess(ranking.MethodGetCachedTopXRanking, rmcResponseBody) + + 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) + } + + responsePacket.SetSource(0xA1) + responsePacket.SetDestination(0xAF) + responsePacket.SetType(nex.DataPacket) + responsePacket.SetPayload(rmcResponseBytes) + + responsePacket.AddFlag(nex.FlagNeedsAck) + responsePacket.AddFlag(nex.FlagReliable) + + server.Send(responsePacket) + + return 0 +} diff --git a/ranking/get_cached_top_x_rankings.go b/ranking/get_cached_top_x_rankings.go new file mode 100644 index 0000000..b34ee58 --- /dev/null +++ b/ranking/get_cached_top_x_rankings.go @@ -0,0 +1,85 @@ +package ranking + +import ( + "github.com/PretendoNetwork/nex-go" + ranking "github.com/PretendoNetwork/nex-protocols-go/ranking" + ranking_types "github.com/PretendoNetwork/nex-protocols-go/ranking/types" + + "time" +) + +func getCachedTopXRankings(err error, client *nex.Client, callID uint32, categories []uint32, orderParams []*ranking_types.RankingOrderParam) uint32 { + if commonRankingProtocol.getRankingsAndCountByCategoryAndRankingOrderParamHandler == nil { + logger.Warning("Ranking::GetCachedTopXRankings missing GetRankingsAndCountByCategoryAndRankingOrderParamHandler!") + return nex.Errors.Core.NotImplemented + } + + server := client.Server() + + if err != nil { + logger.Error(err.Error()) + return nex.Errors.Ranking.InvalidArgument + } + + var pResult []*ranking_types.RankingCachedResult + for i := 0; i < len(categories); i++ { + rankDataList, totalCount, err := commonRankingProtocol.getRankingsAndCountByCategoryAndRankingOrderParamHandler(categories[i], orderParams[i]) + if err != nil { + logger.Critical(err.Error()) + return nex.Errors.Ranking.Unknown + } + + if totalCount == 0 || len(rankDataList) == 0 { + return nex.Errors.Ranking.NotFound + } + + rankingResult := ranking_types.NewRankingResult() + + rankingResult.RankDataList = rankDataList + rankingResult.TotalCount = totalCount + rankingResult.SinceTime = nex.NewDateTime(0x1f40420000) // * 2000-01-01T00:00:00.000Z, this is what the real server sends back + + result := ranking_types.NewRankingCachedResult() + serverTime := nex.NewDateTime(0) + result.CreatedTime = nex.NewDateTime(serverTime.UTC()) + //The real server sends the "CreatedTime" + 5 minutes. + //It doesn't change, even on subsequent requests, until after the ExpiredTime has passed (seemingly what the "cached" means). + //Whether we need to replicate this idk, but in case, here's a note. + result.ExpiredTime = nex.NewDateTime(serverTime.FromTimestamp(time.Now().UTC().Add(time.Minute * time.Duration(5)))) + result.MaxLength = 10 //This is the length Ultimate NES Remix uses. TODO: Does this matter? and are other games different? + + result.SetParentType(rankingResult) + pResult = append(pResult, result) + } + + rmcResponseStream := nex.NewStreamOut(server) + rmcResponseStream.WriteListStructure(pResult) + rmcResponseBody := rmcResponseStream.Bytes() + + rmcResponse := nex.NewRMCResponse(ranking.ProtocolID, callID) + rmcResponse.SetSuccess(ranking.MethodGetCachedTopXRankings, rmcResponseBody) + + 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) + } + + responsePacket.SetSource(0xA1) + responsePacket.SetDestination(0xAF) + responsePacket.SetType(nex.DataPacket) + responsePacket.SetPayload(rmcResponseBytes) + + responsePacket.AddFlag(nex.FlagNeedsAck) + responsePacket.AddFlag(nex.FlagReliable) + + server.Send(responsePacket) + + return 0 +} diff --git a/ranking/get_common_data.go b/ranking/get_common_data.go new file mode 100644 index 0000000..a3e4c84 --- /dev/null +++ b/ranking/get_common_data.go @@ -0,0 +1,56 @@ +package ranking + +import ( + "github.com/PretendoNetwork/nex-go" + ranking "github.com/PretendoNetwork/nex-protocols-go/ranking" +) + +func getCommonData(err error, client *nex.Client, callID uint32, uniqueID uint64) uint32 { + if commonRankingProtocol.getCommonDataHandler == nil { + logger.Warning("Ranking::GetCommonData missing GetCommonDataHandler!") + return nex.Errors.Core.NotImplemented + } + + server := client.Server() + + if err != nil { + logger.Error(err.Error()) + return nex.Errors.Ranking.InvalidArgument + } + + commonData, err := commonRankingProtocol.getCommonDataHandler(uniqueID) + if err != nil { + return nex.Errors.Ranking.NotFound + } + + rmcResponseStream := nex.NewStreamOut(server) + rmcResponseStream.WriteBuffer(commonData) + rmcResponseBody := rmcResponseStream.Bytes() + + rmcResponse := nex.NewRMCResponse(ranking.ProtocolID, callID) + rmcResponse.SetSuccess(ranking.MethodGetCommonData, rmcResponseBody) + + 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) + } + + responsePacket.SetSource(0xA1) + responsePacket.SetDestination(0xAF) + responsePacket.SetType(nex.DataPacket) + responsePacket.SetPayload(rmcResponseBytes) + + responsePacket.AddFlag(nex.FlagNeedsAck) + responsePacket.AddFlag(nex.FlagReliable) + + server.Send(responsePacket) + + return 0 +} diff --git a/ranking/get_ranking.go b/ranking/get_ranking.go new file mode 100644 index 0000000..b45139a --- /dev/null +++ b/ranking/get_ranking.go @@ -0,0 +1,70 @@ +package ranking + +import ( + "github.com/PretendoNetwork/nex-go" + ranking "github.com/PretendoNetwork/nex-protocols-go/ranking" + ranking_types "github.com/PretendoNetwork/nex-protocols-go/ranking/types" +) + +func getRanking(err error, client *nex.Client, callID uint32, rankingMode uint8, category uint32, orderParam *ranking_types.RankingOrderParam, uniqueID uint64, principalID uint32) uint32 { + if commonRankingProtocol.getRankingsAndCountByCategoryAndRankingOrderParamHandler == nil { + logger.Warning("Ranking::GetRanking missing GetRankingsAndCountByCategoryAndRankingOrderParamHandler!") + return nex.Errors.Core.NotImplemented + } + + server := client.Server() + + if err != nil { + logger.Error(err.Error()) + return nex.Errors.Ranking.InvalidArgument + } + + rankDataList, totalCount, err := commonRankingProtocol.getRankingsAndCountByCategoryAndRankingOrderParamHandler(category, orderParam) + if err != nil { + logger.Critical(err.Error()) + return nex.Errors.Ranking.Unknown + } + + if totalCount == 0 || len(rankDataList) == 0 { + return nex.Errors.Ranking.NotFound + } + + pResult := ranking_types.NewRankingResult() + + pResult.RankDataList = rankDataList + pResult.TotalCount = totalCount + pResult.SinceTime = nex.NewDateTime(0x1f40420000) // * 2000-01-01T00:00:00.000Z, this is what the real server sends back + + rmcResponseStream := nex.NewStreamOut(server) + + rmcResponseStream.WriteStructure(pResult) + + rmcResponseBody := rmcResponseStream.Bytes() + + rmcResponse := nex.NewRMCResponse(ranking.ProtocolID, callID) + rmcResponse.SetSuccess(ranking.MethodGetRanking, rmcResponseBody) + + 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) + } + + responsePacket.SetSource(0xA1) + responsePacket.SetDestination(0xAF) + responsePacket.SetType(nex.DataPacket) + responsePacket.SetPayload(rmcResponseBytes) + + responsePacket.AddFlag(nex.FlagNeedsAck) + responsePacket.AddFlag(nex.FlagReliable) + + server.Send(responsePacket) + + return 0 +} diff --git a/ranking/protocol.go b/ranking/protocol.go new file mode 100644 index 0000000..5614db8 --- /dev/null +++ b/ranking/protocol.go @@ -0,0 +1,88 @@ +package ranking + +import ( + "strings" + + "github.com/PretendoNetwork/nex-go" + ranking "github.com/PretendoNetwork/nex-protocols-go/ranking" + ranking_mario_kart_8 "github.com/PretendoNetwork/nex-protocols-go/ranking/mario-kart-8" + ranking_types "github.com/PretendoNetwork/nex-protocols-go/ranking/types" + "github.com/PretendoNetwork/plogger-go" +) + +var commonRankingProtocol *CommonRankingProtocol +var logger = plogger.NewLogger() + +type CommonRankingProtocol struct { + server *nex.Server + DefaultProtocol *ranking.Protocol + MarioKart8Protocol *ranking_mario_kart_8.Protocol + + getCommonDataHandler func(unique_id uint64) ([]byte, error) + uploadCommonDataHandler func(pid uint32, uniqueID uint64, commonData []byte) error + insertRankingByPIDAndRankingScoreDataHandler func(pid uint32, rankingScoreData *ranking_types.RankingScoreData, uniqueID uint64) error + getRankingsAndCountByCategoryAndRankingOrderParamHandler func(category uint32, rankingOrderParam *ranking_types.RankingOrderParam) ([]*ranking_types.RankingRankData, uint32, error) +} + +// GetCommonData sets the GetCommonData handler function +func (commonRankingProtocol *CommonRankingProtocol) GetCommonData(handler func(unique_id uint64) ([]byte, error)) { + commonRankingProtocol.getCommonDataHandler = handler +} + +// UploadCommonData sets the UploadCommonData handler function +func (commonRankingProtocol *CommonRankingProtocol) UploadCommonData(handler func(pid uint32, uniqueID uint64, commonData []byte) error) { + commonRankingProtocol.uploadCommonDataHandler = handler +} + +// InsertRankingByPIDAndRankingScoreData sets the InsertRankingByPIDAndRankingScoreData handler function +func (commonRankingProtocol *CommonRankingProtocol) InsertRankingByPIDAndRankingScoreData(handler func(pid uint32, rankingScoreData *ranking_types.RankingScoreData, uniqueID uint64) error) { + commonRankingProtocol.insertRankingByPIDAndRankingScoreDataHandler = handler +} + +// GetRankingsAndCountByCategoryAndRankingOrderParam sets the GetRankingsAndCountByCategoryAndRankingOrderParam handler function +func (commonRankingProtocol *CommonRankingProtocol) GetRankingsAndCountByCategoryAndRankingOrderParam(handler func(category uint32, rankingOrderParam *ranking_types.RankingOrderParam) ([]*ranking_types.RankingRankData, uint32, error)) { + commonRankingProtocol.getRankingsAndCountByCategoryAndRankingOrderParamHandler = handler +} + +func initDefault(c *CommonRankingProtocol) { + // TODO - Organize by method ID + c.DefaultProtocol = ranking.NewProtocol(c.server) + c.DefaultProtocol.GetCachedTopXRanking(getCachedTopXRanking) + c.DefaultProtocol.GetCachedTopXRankings(getCachedTopXRankings) + c.DefaultProtocol.GetCommonData(getCommonData) + c.DefaultProtocol.GetRanking(getRanking) + c.DefaultProtocol.UploadCommonData(uploadCommonData) + c.DefaultProtocol.UploadScore(uploadScore) +} + +func initMarioKart8(c *CommonRankingProtocol) { + // TODO - Organize by method ID + c.MarioKart8Protocol = ranking_mario_kart_8.NewProtocol(c.server) + c.MarioKart8Protocol.GetCachedTopXRanking(getCachedTopXRanking) + c.MarioKart8Protocol.GetCachedTopXRankings(getCachedTopXRankings) + c.MarioKart8Protocol.GetCommonData(getCommonData) + c.MarioKart8Protocol.GetRanking(getRanking) + c.MarioKart8Protocol.UploadCommonData(uploadCommonData) + c.MarioKart8Protocol.UploadScore(uploadScore) +} + +// NewCommonRankingProtocol returns a new CommonRankingProtocol +func NewCommonRankingProtocol(server *nex.Server) *CommonRankingProtocol { + commonRankingProtocol = &CommonRankingProtocol{server: server} + + patch := server.MatchMakingProtocolVersion().GameSpecificPatch + + if strings.EqualFold(patch, "AMKJ") { + logger.Info("Using Mario Kart 8 Ranking protocol") + initMarioKart8(commonRankingProtocol) + } else { + if patch != "" { + logger.Infof("Ranking version patch %q not recognized", patch) + } + + logger.Info("Using default Ranking protocol") + initDefault(commonRankingProtocol) + } + + return commonRankingProtocol +} diff --git a/ranking/upload_common_data.go b/ranking/upload_common_data.go new file mode 100644 index 0000000..f3c90dd --- /dev/null +++ b/ranking/upload_common_data.go @@ -0,0 +1,53 @@ +package ranking + +import ( + "github.com/PretendoNetwork/nex-go" + ranking "github.com/PretendoNetwork/nex-protocols-go/ranking" +) + +func uploadCommonData(err error, client *nex.Client, callID uint32, commonData []byte, uniqueID uint64) uint32 { + if commonRankingProtocol.uploadCommonDataHandler == nil { + logger.Warning("Ranking::UploadCommonData missing UploadCommonDataHandler!") + return nex.Errors.Core.NotImplemented + } + + server := client.Server() + + if err != nil { + logger.Error(err.Error()) + return nex.Errors.Ranking.InvalidArgument + } + + err = commonRankingProtocol.uploadCommonDataHandler(client.PID(), uniqueID, commonData) + if err != nil { + logger.Critical(err.Error()) + return nex.Errors.Ranking.Unknown + } + + rmcResponse := nex.NewRMCResponse(ranking.ProtocolID, callID) + rmcResponse.SetSuccess(ranking.MethodUploadCommonData, 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) + } + + responsePacket.SetSource(0xA1) + responsePacket.SetDestination(0xAF) + responsePacket.SetType(nex.DataPacket) + responsePacket.SetPayload(rmcResponseBytes) + + responsePacket.AddFlag(nex.FlagNeedsAck) + responsePacket.AddFlag(nex.FlagReliable) + + server.Send(responsePacket) + + return 0 +} diff --git a/ranking/upload_score.go b/ranking/upload_score.go new file mode 100644 index 0000000..460e67c --- /dev/null +++ b/ranking/upload_score.go @@ -0,0 +1,54 @@ +package ranking + +import ( + "github.com/PretendoNetwork/nex-go" + ranking "github.com/PretendoNetwork/nex-protocols-go/ranking" + ranking_types "github.com/PretendoNetwork/nex-protocols-go/ranking/types" +) + +func uploadScore(err error, client *nex.Client, callID uint32, scoreData *ranking_types.RankingScoreData, uniqueID uint64) uint32 { + if commonRankingProtocol.insertRankingByPIDAndRankingScoreDataHandler == nil { + logger.Warning("Ranking::UploadScore missing InsertRankingByPIDAndRankingScoreDataHandler!") + return nex.Errors.Core.NotImplemented + } + + server := client.Server() + + if err != nil { + logger.Error(err.Error()) + return nex.Errors.Ranking.InvalidArgument + } + + err = commonRankingProtocol.insertRankingByPIDAndRankingScoreDataHandler(client.PID(), scoreData, uniqueID) + if err != nil { + logger.Critical(err.Error()) + return nex.Errors.Ranking.Unknown + } + + rmcResponse := nex.NewRMCResponse(ranking.ProtocolID, callID) + rmcResponse.SetSuccess(ranking.MethodUploadScore, 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) + } + + responsePacket.SetSource(0xA1) + responsePacket.SetDestination(0xAF) + responsePacket.SetType(nex.DataPacket) + responsePacket.SetPayload(rmcResponseBytes) + + responsePacket.AddFlag(nex.FlagNeedsAck) + responsePacket.AddFlag(nex.FlagReliable) + + server.Send(responsePacket) + + return 0 +}