Improve Database Queries by 1000x; Add support for newer seasons; Lockers nearly implemented.

This commit is contained in:
eccentric 2023-11-20 21:20:26 +00:00
parent e2707473aa
commit 5508ad4348
17 changed files with 7029 additions and 108 deletions

View File

@ -10,7 +10,7 @@ import (
)
var (
oatuhTokenGrantTypes = map[string]func(c *fiber.Ctx, body *OAuthTokenBody) error{
oauthTokenGrantTypes = map[string]func(c *fiber.Ctx, body *OAuthTokenBody) error{
"client_credentials": PostOAuthTokenClientCredentials,
"password": PostOAuthTokenPassword,
}
@ -29,7 +29,7 @@ func PostOAuthToken(c *fiber.Ctx) error {
return c.Status(fiber.StatusBadRequest).JSON(aid.ErrorBadRequest("Invalid Request Body"))
}
if action, ok := oatuhTokenGrantTypes[body.GrantType]; ok {
if action, ok := oauthTokenGrantTypes[body.GrantType]; ok {
return action(c, &body)
}

View File

@ -1,6 +1,8 @@
package handlers
import (
"strconv"
"github.com/ectrc/snow/aid"
"github.com/gofiber/fiber/v2"
)
@ -26,7 +28,7 @@ func GetFortniteReceipts(c *fiber.Ctx) error {
}
func GetMatchmakingSession(c *fiber.Ctx) error {
return c.Status(fiber.StatusOK).JSON([]string{})
return c.Status(fiber.StatusOK).Send([]byte{})
}
func GetFortniteVersion(c *fiber.Ctx) error {
@ -35,8 +37,24 @@ func GetFortniteVersion(c *fiber.Ctx) error {
})
}
func GetWaitingRoomStatus(c *fiber.Ctx) error {
return c.SendStatus(fiber.StatusNoContent)
}
func GetRegion(c *fiber.Ctx) error {
return c.Status(fiber.StatusOK).JSON(aid.JSON{
"continent": aid.JSON{
"code": "EU",
},
"country": aid.JSON{
"iso_code": "GB",
},
"subdivisions": []aid.JSON{},
})
}
func GetContentPages(c *fiber.Ctx) error {
// seasonString := strconv.Itoa(aid.Config.Fortnite.Season)
seasonString := strconv.Itoa(aid.Config.Fortnite.Season)
return c.Status(fiber.StatusOK).JSON(aid.JSON{
"battlepassaboutmessages": aid.JSON{
@ -77,14 +95,14 @@ func GetContentPages(c *fiber.Ctx) error {
},
"dynamicbackgrounds": aid.JSON{
"backgrounds": aid.JSON{"backgrounds": []aid.JSON{
// {
// "key": "lobby",
// "stage": "season"+seasonString,
// },
// {
// "key": "vault",
// "stage": "season"+seasonString,
// },
{
"key": "lobby",
"stage": "season" + seasonString,
},
{
"key": "vault",
"stage": "season" + seasonString,
},
}},
"lastModified": "0000-00-00T00:00:00.000Z",
},

View File

@ -29,28 +29,35 @@ func PostProfileAction(c *fiber.Ctx) error {
}
profile := person.GetProfileFromType(c.Query("profileId"))
defer profile.ClearProfileChanges()
before := profile.Snapshot()
if action, ok := profileActions[c.Params("action")]; ok {
if action, ok := profileActions[c.Params("action")]; ok && profile != nil {
defer profile.ClearProfileChanges()
before := profile.Snapshot()
if err := action(c, person, profile); err != nil {
return c.Status(400).JSON(aid.ErrorBadRequest(err.Error()))
}
profile.Diff(before)
}
profile.Diff(before)
revision, _ := strconv.Atoi(c.Query("rvn"))
if revision == -1 {
if revision == -1 && profile == nil {
revision = 1
}
if revision == -1 && profile != nil {
revision = profile.Revision
}
revision++
changes := []interface{}{}
if profile != nil {
changes = profile.Changes
}
return c.Status(200).JSON(aid.JSON{
"profileId": profile.Type,
"profileId": c.Query("profileId"),
"profileRevision": revision,
"profileCommandRevision": revision,
"profileChangesBaseRevision": revision - 1,
"profileChanges": profile.Changes,
"profileChanges": changes,
"multiUpdate": []aid.JSON{},
"notifications": []aid.JSON{},
"responseVersion": 1,

View File

@ -1,12 +1,119 @@
package handlers
import (
"crypto/sha1"
"encoding/hex"
"github.com/ectrc/snow/aid"
"github.com/gofiber/fiber/v2"
)
var (
TEMP_STORAGE = map[string][]byte{
"DefaultEngine.ini": []byte(`
[OnlineSubsystemMcp.Xmpp Prod]
bUseSSL=false
ServerAddr="ws://127.0.0.1:80"
ServerPort="80"
[OnlineSubsystemMcp.Xmpp]
bUseSSL=false
ServerAddr="ws://127.0.0.1:80"
ServerPort="80"
[/Script/Qos.QosRegionManager]
NumTestsPerRegion=5
PingTimeout=3.0
[XMPP]
bEnableWebsockets=true
[OnlineSubsystemMcp]
bUsePartySystemV2=false
[OnlineSubsystemMcp.OnlinePartySystemMcpAdapter]
bUsePartySystemV2=false
[ConsoleVariables]
n.VerifyPeer=0
FortMatchmakingV2.ContentBeaconFailureCancelsMatchmaking=0
Fort.ShutdownWhenContentBeaconFails=0
FortMatchmakingV2.EnableContentBeacon=0
`),
"DefaultGame.ini": []byte(`
[/Script/FortniteGame.FortOnlineAccount]
bEnableEulaCheck=false
[/Script/FortniteGame.FortChatManager]
bShouldRequestGeneralChatRooms=false
bShouldJoinGlobalChat=flase
bShouldJoinFounderChat=false
bIsAthenaGlobalChatEnabled=false
[/Script/FortniteGame.FortGameInstance]
!FrontEndPlaylistData=ClearArray
+FrontEndPlaylistData=(PlaylistName=Playlist_DefaultSolo, PlaylistAccess=(bEnabled=True, bIsDefaultPlaylist=True, bVisibleWhenDisabled=True, bDisplayAsNew=False, CategoryIndex=0, bDisplayAsLimitedTime=False, DisplayPriority=0))
+FrontEndPlaylistData=(PlaylistName=Playlist_DefaultDuo, PlaylistAccess=(bEnabled=False, bIsDefaultPlaylist=True, bVisibleWhenDisabled=True, bDisplayAsNew=False, CategoryIndex=0, bDisplayAsLimitedTime=False, DisplayPriority=1))
+FrontEndPlaylistData=(PlaylistName=Playlist_DefaultSquad, PlaylistAccess=(bEnabled=False, bIsDefaultPlaylist=True, bVisibleWhenDisabled=True, bDisplayAsNew=False, CategoryIndex=0, bDisplayAsLimitedTime=False, DisplayPriority=2))
+FrontEndPlaylistData=(PlaylistName=Playlist_Fill_Squads, PlaylistAccess=(bEnabled=False, bIsDefaultPlaylist=False, bVisibleWhenDisabled=True, bDisplayAsNew=False, CategoryIndex=1, bDisplayAsLimitedTime=False, DisplayPriority=0))
+FrontEndPlaylistData=(PlaylistName=Playlist_Blitz_Solo, PlaylistAccess=(bEnabled=True, bIsDefaultPlaylist=False, bVisibleWhenDisabled=True, bDisplayAsNew=True, CategoryIndex=1, bDisplayAsLimitedTime=True, DisplayPriority=1))
`),
"DefaultRuntimeOptions.ini": []byte(`
[/Script/FortniteGame.FortRuntimeOptions]
bEnableGlobalChat=false
bDisableGifting=false
bDisableGiftingPC=false
bDisableGiftingPS4=false
bDisableGiftingXB=false`),
}
)
func GetCloudStorageFiles(c *fiber.Ctx) error {
return c.Status(fiber.StatusOK).JSON([]aid.JSON{})
engineHash := sha1.Sum(TEMP_STORAGE["DefaultEngine.ini"])
engineHash256 := sha1.Sum(TEMP_STORAGE["DefaultEngine.ini"])
gameHash := sha1.Sum(TEMP_STORAGE["DefaultGame.ini"])
gameHash256 := sha1.Sum(TEMP_STORAGE["DefaultGame.ini"])
runtimeHash := sha1.Sum(TEMP_STORAGE["DefaultRuntimeOptions.ini"])
runtimeHash256 := sha1.Sum(TEMP_STORAGE["DefaultRuntimeOptions.ini"])
return c.Status(fiber.StatusOK).JSON([]aid.JSON{
{
"uniqueFilename": "DefaultEngine.ini",
"filename": "DefaultEngine.ini",
"hash": hex.EncodeToString(engineHash[:]),
"hash256": hex.EncodeToString(engineHash256[:]),
"length": len(TEMP_STORAGE["DefaultEngine.ini"]),
"contentType": "application/octet-stream",
"uploaded": "2021-01-01T00:00:00.000Z",
"storageType": "S3",
"doNotCache": false,
"storageIds": []string{"primary"},
},
{
"uniqueFilename": "DefaultGame.ini",
"filename": "DefaultGame.ini",
"hash": hex.EncodeToString(gameHash[:]),
"hash256": hex.EncodeToString(gameHash256[:]),
"length": len(TEMP_STORAGE["DefaultGame.ini"]),
"contentType": "application/octet-stream",
"uploaded": "2021-01-01T00:00:00.000Z",
"storageType": "S3",
"doNotCache": false,
"storageIds": []string{"primary"},
},
{
"uniqueFilename": "DefaultRuntimeOptions.ini",
"filename": "DefaultRuntimeOptions.ini",
"hash": hex.EncodeToString(runtimeHash[:]),
"hash256": hex.EncodeToString(runtimeHash256[:]),
"length": len(TEMP_STORAGE["DefaultRuntimeOptions.ini"]),
"contentType": "application/octet-stream",
"uploaded": "2021-01-01T00:00:00.000Z",
"storageType": "S3",
"doNotCache": false,
"storageIds": []string{"primary"},
},
})
}
func GetCloudStorageConfig(c *fiber.Ctx) error {
@ -28,7 +135,12 @@ func GetCloudStorageFile(c *fiber.Ctx) error {
return c.Status(fiber.StatusBadRequest).JSON(aid.ErrorBadRequest)
}
return c.Status(fiber.StatusOK).JSON(aid.JSON{})
file, ok := TEMP_STORAGE[fileName]
if !ok {
return c.Status(fiber.StatusNotFound).JSON(aid.ErrorNotFound)
}
return c.Status(fiber.StatusOK).Send(file)
}
func GetUserStorageFiles(c *fiber.Ctx) error {

View File

@ -45,6 +45,14 @@ func main() {
r.Use(aid.FiberCors())
r.Get("/content/api/pages/fortnite-game", handlers.GetContentPages)
r.Get("/waitingroom/api/waitingroom", handlers.GetWaitingRoomStatus)
r.Get("/region", handlers.GetRegion)
r.Put("/profile/play_region", handlers.AnyNoContent)
r.Get("/snow/cache", func(c *fiber.Ctx) error {
cache := person.AllFromCache()
return c.JSON(cache)
})
account := r.Group("/account/api")
account.Get("/public/account", handlers.GetPublicAccounts)

View File

@ -1,9 +1,11 @@
package person
import (
"encoding/json"
"strconv"
"github.com/ectrc/snow/aid"
"github.com/ectrc/snow/storage"
)
var (
@ -25,7 +27,7 @@ func NewFortnitePerson(displayName string, key string) *Person {
person.DisplayName = displayName
person.AccessKey = key
person.Profile0Profile.Items.AddItem(NewItem("Currency:MtxPurchased", 0)) // for season 2 and bellow
person.Profile0Profile.Items.AddItem(NewItem("Currency:MtxPurchased", 0)).Save() // for season 2 and bellow
for _, item := range defaultAthenaItems {
person.AthenaProfile.Items.AddItem(NewItem(item, 1))
@ -34,77 +36,91 @@ func NewFortnitePerson(displayName string, key string) *Person {
for _, item := range defaultCommonCoreItems {
if item == "HomebaseBannerIcon:StandardBanner" {
for i := 1; i < 32; i++ {
person.CommonCoreProfile.Items.AddItem(NewItem(item+strconv.Itoa(i), 1))
person.CommonCoreProfile.Items.AddItem(NewItem(item+strconv.Itoa(i), 1)).Save()
}
continue
}
if item == "HomebaseBannerColor:DefaultColor" {
for i := 1; i < 22; i++ {
person.CommonCoreProfile.Items.AddItem(NewItem(item+strconv.Itoa(i), 1))
person.CommonCoreProfile.Items.AddItem(NewItem(item+strconv.Itoa(i), 1)).Save()
}
continue
}
if item == "Currency:MtxPurchased" {
person.CommonCoreProfile.Items.AddItem(NewItem(item, 0))
person.CommonCoreProfile.Items.AddItem(NewItem(item, 0)).Save()
continue
}
person.CommonCoreProfile.Items.AddItem(NewItem(item, 1))
person.CommonCoreProfile.Items.AddItem(NewItem(item, 1)).Save()
}
person.AthenaProfile.Attributes.AddAttribute(NewAttribute("mfa_reward_claimed", true))
person.AthenaProfile.Attributes.AddAttribute(NewAttribute("rested_xp_overflow", 0))
person.AthenaProfile.Attributes.AddAttribute(NewAttribute("lifetime_wins", 0))
person.AthenaProfile.Attributes.AddAttribute(NewAttribute("party_assist_quest", ""))
person.AthenaProfile.Attributes.AddAttribute(NewAttribute("quest_manager", aid.JSON{}))
person.AthenaProfile.Attributes.AddAttribute(NewAttribute("inventory_limit_bonus", 0))
person.AthenaProfile.Attributes.AddAttribute(NewAttribute("daily_rewards", []aid.JSON{}))
person.AthenaProfile.Attributes.AddAttribute(NewAttribute("competitive_identity", aid.JSON{}))
person.AthenaProfile.Attributes.AddAttribute(NewAttribute("season_update", 0))
person.AthenaProfile.Attributes.AddAttribute(NewAttribute("season_num", aid.Config.Fortnite.Season))
person.AthenaProfile.Attributes.AddAttribute(NewAttribute("permissions", []aid.JSON{}))
person.AthenaProfile.Attributes.AddAttribute(NewAttribute("mfa_reward_claimed", true)).Save()
person.AthenaProfile.Attributes.AddAttribute(NewAttribute("rested_xp_overflow", 0)).Save()
person.AthenaProfile.Attributes.AddAttribute(NewAttribute("lifetime_wins", 0)).Save()
person.AthenaProfile.Attributes.AddAttribute(NewAttribute("party_assist_quest", "")).Save()
person.AthenaProfile.Attributes.AddAttribute(NewAttribute("quest_manager", aid.JSON{})).Save()
person.AthenaProfile.Attributes.AddAttribute(NewAttribute("inventory_limit_bonus", 0)).Save()
person.AthenaProfile.Attributes.AddAttribute(NewAttribute("daily_rewards", []aid.JSON{})).Save()
person.AthenaProfile.Attributes.AddAttribute(NewAttribute("competitive_identity", aid.JSON{})).Save()
person.AthenaProfile.Attributes.AddAttribute(NewAttribute("season_update", 0)).Save()
person.AthenaProfile.Attributes.AddAttribute(NewAttribute("season_num", aid.Config.Fortnite.Season)).Save()
person.AthenaProfile.Attributes.AddAttribute(NewAttribute("permissions", []aid.JSON{})).Save()
person.AthenaProfile.Attributes.AddAttribute(NewAttribute("last_applied_loadout", "")).Save()
person.AthenaProfile.Attributes.AddAttribute(NewAttribute("active_loadout_index", 0)).Save()
person.AthenaProfile.Attributes.AddAttribute(NewAttribute("loadouts", []aid.JSON{}))
person.AthenaProfile.Attributes.AddAttribute(NewAttribute("last_applied_loadout", ""))
person.AthenaProfile.Attributes.AddAttribute(NewAttribute("active_loadout_index", 0))
person.AthenaProfile.Attributes.AddAttribute(NewAttribute("accountLevel", 1)).Save()
person.AthenaProfile.Attributes.AddAttribute(NewAttribute("level", 1)).Save()
person.AthenaProfile.Attributes.AddAttribute(NewAttribute("xp", 0)).Save()
person.AthenaProfile.Attributes.AddAttribute(NewAttribute("xp_overflow", 0)).Save()
person.AthenaProfile.Attributes.AddAttribute(NewAttribute("rested_xp", 0)).Save()
person.AthenaProfile.Attributes.AddAttribute(NewAttribute("rested_xp_mult", 0)).Save()
person.AthenaProfile.Attributes.AddAttribute(NewAttribute("rested_xp_exchange", 0)).Save()
person.AthenaProfile.Attributes.AddAttribute(NewAttribute("accountLevel", 1))
person.AthenaProfile.Attributes.AddAttribute(NewAttribute("level", 1))
person.AthenaProfile.Attributes.AddAttribute(NewAttribute("xp", 0))
person.AthenaProfile.Attributes.AddAttribute(NewAttribute("xp_overflow", 0))
person.AthenaProfile.Attributes.AddAttribute(NewAttribute("rested_xp", 0))
person.AthenaProfile.Attributes.AddAttribute(NewAttribute("rested_xp_mult", 0))
person.AthenaProfile.Attributes.AddAttribute(NewAttribute("rested_xp_exchange", 0))
person.AthenaProfile.Attributes.AddAttribute(NewAttribute("book_purchased", false)).Save()
person.AthenaProfile.Attributes.AddAttribute(NewAttribute("book_level", 1)).Save()
person.AthenaProfile.Attributes.AddAttribute(NewAttribute("book_xp", 0)).Save()
person.AthenaProfile.Attributes.AddAttribute(NewAttribute("book_purchased", false))
person.AthenaProfile.Attributes.AddAttribute(NewAttribute("book_level", 1))
person.AthenaProfile.Attributes.AddAttribute(NewAttribute("book_xp", 0))
person.AthenaProfile.Attributes.AddAttribute(NewAttribute("favorite_character", person.AthenaProfile.Items.GetItemByTemplateID("AthenaCharacter:CID_001_Athena_Commando_F_Default").ID)).Save()
person.AthenaProfile.Attributes.AddAttribute(NewAttribute("favorite_backpack", "")).Save()
person.AthenaProfile.Attributes.AddAttribute(NewAttribute("favorite_pickaxe", person.AthenaProfile.Items.GetItemByTemplateID("AthenaPickaxe:DefaultPickaxe").ID)).Save()
person.AthenaProfile.Attributes.AddAttribute(NewAttribute("favorite_glider", person.AthenaProfile.Items.GetItemByTemplateID("AthenaGlider:DefaultGlider").ID)).Save()
person.AthenaProfile.Attributes.AddAttribute(NewAttribute("favorite_skydivecontrail", "")).Save()
person.AthenaProfile.Attributes.AddAttribute(NewAttribute("favorite_dance", make([]string, 6))).Save()
person.AthenaProfile.Attributes.AddAttribute(NewAttribute("favorite_itemwraps", make([]string, 7))).Save()
person.AthenaProfile.Attributes.AddAttribute(NewAttribute("favorite_loadingscreen", "")).Save()
person.AthenaProfile.Attributes.AddAttribute(NewAttribute("favorite_musicpack", "")).Save()
person.AthenaProfile.Attributes.AddAttribute(NewAttribute("banner_icon", "")).Save()
person.AthenaProfile.Attributes.AddAttribute(NewAttribute("banner_color", "")).Save()
person.AthenaProfile.Attributes.AddAttribute(NewAttribute("favorite_character", person.AthenaProfile.Items.GetItemByTemplateID("AthenaCharacter:CID_001_Athena_Commando_F_Default").ID))
person.AthenaProfile.Attributes.AddAttribute(NewAttribute("favorite_backpack", ""))
person.AthenaProfile.Attributes.AddAttribute(NewAttribute("favorite_pickaxe", person.AthenaProfile.Items.GetItemByTemplateID("AthenaPickaxe:DefaultPickaxe").ID))
person.AthenaProfile.Attributes.AddAttribute(NewAttribute("favorite_glider", person.AthenaProfile.Items.GetItemByTemplateID("AthenaGlider:DefaultGlider").ID))
person.AthenaProfile.Attributes.AddAttribute(NewAttribute("favorite_skydivecontrail", ""))
person.AthenaProfile.Attributes.AddAttribute(NewAttribute("favorite_dance", make([]string, 6)))
person.AthenaProfile.Attributes.AddAttribute(NewAttribute("favorite_itemwraps", make([]string, 7)))
person.AthenaProfile.Attributes.AddAttribute(NewAttribute("favorite_loadingscreen", ""))
person.AthenaProfile.Attributes.AddAttribute(NewAttribute("favorite_musicpack", ""))
person.AthenaProfile.Attributes.AddAttribute(NewAttribute("banner_icon", ""))
person.AthenaProfile.Attributes.AddAttribute(NewAttribute("banner_color", ""))
person.CommonCoreProfile.Attributes.AddAttribute(NewAttribute("mfa_enabled", true))
person.CommonCoreProfile.Attributes.AddAttribute(NewAttribute("mtx_affiliate", ""))
person.CommonCoreProfile.Attributes.AddAttribute(NewAttribute("mfa_enabled", true)).Save()
person.CommonCoreProfile.Attributes.AddAttribute(NewAttribute("mtx_affiliate", "")).Save()
person.CommonCoreProfile.Attributes.AddAttribute(NewAttribute("mtx_purchase_history", aid.JSON{
"refundsUsed": 0,
"refundCredits": 3,
"purchases": []any{},
}))
person.CommonCoreProfile.Attributes.AddAttribute(NewAttribute("current_mtx_platform", "EpicPC"))
person.CommonCoreProfile.Attributes.AddAttribute(NewAttribute("allowed_to_receive_gifts", true))
person.CommonCoreProfile.Attributes.AddAttribute(NewAttribute("allowed_to_send_gifts", true))
person.CommonCoreProfile.Attributes.AddAttribute(NewAttribute("gift_history", aid.JSON{}))
})).Save()
person.CommonCoreProfile.Attributes.AddAttribute(NewAttribute("current_mtx_platform", "EpicPC")).Save()
person.CommonCoreProfile.Attributes.AddAttribute(NewAttribute("allowed_to_receive_gifts", true)).Save()
person.CommonCoreProfile.Attributes.AddAttribute(NewAttribute("allowed_to_send_gifts", true)).Save()
person.CommonCoreProfile.Attributes.AddAttribute(NewAttribute("gift_history", aid.JSON{})).Save()
loadout := NewLoadout("sandbox_loadout", person.AthenaProfile)
person.AthenaProfile.Loadouts.AddLoadout(loadout).Save()
person.AthenaProfile.Attributes.AddAttribute(NewAttribute("loadouts", []string{
loadout.ID,
})).Save()
person.AthenaProfile.Attributes.AddAttribute(NewAttribute("last_applied_loadout", loadout.ID)).Save()
person.AthenaProfile.Attributes.AddAttribute(NewAttribute("active_loadout_index", 0)).Save()
allItemsBytes := storage.Asset("cosmetics.json")
var allItems []string
json.Unmarshal(*allItemsBytes, &allItems)
for _, item := range allItems {
person.AthenaProfile.Items.AddItem(NewItem(item, 1)).Save()
}
person.Save()

View File

@ -70,21 +70,12 @@ func FromDatabaseLoot(item *storage.DB_Loot) *Item {
}
func (i *Item) GenerateFortniteItemEntry() aid.JSON {
variants := []aid.JSON{}
attributes := aid.JSON{
"variants": variants,
"variants": i.GenerateFortniteItemVariantChannels(),
"favorite": i.Favorite,
"item_seen": i.HasSeen,
}
for _, variant := range i.Variants {
variants = append(variants, aid.JSON{
"channel": variant.Channel,
"owned": variant.Owned,
"active": variant.Active,
})
}
if i.TemplateID == "Currency:MtxPurchased" {
attributes = aid.JSON{
"platform": "Shared",
@ -98,6 +89,20 @@ func (i *Item) GenerateFortniteItemEntry() aid.JSON {
}
}
func (i *Item) GenerateFortniteItemVariantChannels() []aid.JSON {
variants := []aid.JSON{}
for _, variant := range i.Variants {
variants = append(variants, aid.JSON{
"channel": variant.Channel,
"owned": variant.Owned,
"active": variant.Active,
})
}
return variants
}
func (i *Item) GetAttribute(attribute string) interface{} {
switch attribute {
case "Favorite":

244
person/loadout.go Normal file
View File

@ -0,0 +1,244 @@
package person
import (
"github.com/ectrc/snow/aid"
"github.com/ectrc/snow/storage"
"github.com/google/uuid"
)
type Loadout struct {
ID string
ProfileID string
TemplateID string
LockerName string
BannerID string
BannerColorID string
CharacterID string
PickaxeID string
BackpackID string
GliderID string
DanceID []string
ItemWrapID []string
ContrailID string
LoadingScreenID string
MusicPackID string
}
func NewLoadout(name string, athena *Profile) *Loadout {
character := athena.Attributes.GetAttributeByKey("favorite_character")
pickaxe := athena.Attributes.GetAttributeByKey("favorite_pickaxe")
backpack := athena.Attributes.GetAttributeByKey("favorite_backpack")
glider := athena.Attributes.GetAttributeByKey("favorite_glider")
contrail := athena.Attributes.GetAttributeByKey("favorite_skydivecontrail")
screen := athena.Attributes.GetAttributeByKey("favorite_loadingscreen")
music := athena.Attributes.GetAttributeByKey("favorite_musicpack")
icon := athena.Attributes.GetAttributeByKey("banner_icon")
color := athena.Attributes.GetAttributeByKey("banner_color")
dances := athena.Attributes.GetAttributeByKey("favorite_dance")
wraps := athena.Attributes.GetAttributeByKey("favorite_itemwraps")
dancesReal := aid.JSONParse(dances.ValueJSON).([]any)
wrapsReal := aid.JSONParse(wraps.ValueJSON).([]any)
dancesValue := make([]string, len(dancesReal))
wrapsValue := make([]string, len(wrapsReal))
for i, v := range dancesReal {
value, ok := v.(string)
if !ok {
continue
}
dancesValue[i] = value
}
for i, v := range wrapsReal {
value, ok := v.(string)
if !ok {
continue
}
wrapsValue[i] = value
}
return &Loadout{
ID: uuid.New().String(),
ProfileID: athena.ID,
TemplateID: "CosmeticLocker:CosmeticLocker_Athena",
LockerName: name,
CharacterID: aid.JSONParse(character.ValueJSON).(string),
PickaxeID: aid.JSONParse(pickaxe.ValueJSON).(string),
BackpackID: aid.JSONParse(backpack.ValueJSON).(string),
GliderID: aid.JSONParse(glider.ValueJSON).(string),
ContrailID: aid.JSONParse(contrail.ValueJSON).(string),
LoadingScreenID: aid.JSONParse(screen.ValueJSON).(string),
MusicPackID: aid.JSONParse(music.ValueJSON).(string),
BannerID: aid.JSONParse(icon.ValueJSON).(string),
BannerColorID: aid.JSONParse(color.ValueJSON).(string),
DanceID: dancesValue,
ItemWrapID: wrapsValue,
}
}
func FromDatabaseLoadout(loadout *storage.DB_Loadout) *Loadout {
return &Loadout{
ID: loadout.ID,
ProfileID: loadout.ProfileID,
TemplateID: loadout.TemplateID,
LockerName: loadout.LockerName,
BannerID: loadout.BannerID,
BannerColorID: loadout.BannerColorID,
CharacterID: loadout.CharacterID,
PickaxeID: loadout.PickaxeID,
BackpackID: loadout.BackpackID,
GliderID: loadout.GliderID,
DanceID: loadout.DanceID,
ItemWrapID: loadout.ItemWrapID,
ContrailID: loadout.ContrailID,
LoadingScreenID: loadout.LoadingScreenID,
MusicPackID: loadout.MusicPackID,
}
}
func (l *Loadout) GenerateFortniteLoadoutEntry() aid.JSON {
json := aid.JSON{
"templateId": l.TemplateID,
"attributes": aid.JSON{
"locker_name": l.LockerName,
"banner_icon_template": l.BannerID,
"banner_color_template": l.BannerColorID,
"locker_slots_data": l.GenerateFortniteLockerSlotsData(),
"item_seen": true,
},
"quantity": 1,
}
return json
}
func (l *Loadout) GetAttribute(attribute string) interface{} {
switch attribute {
case "locker_name":
return l.LockerName
case "banner_icon_template":
return l.BannerID
case "banner_color_template":
return l.BannerColorID
case "locker_slots_data":
return l.GenerateFortniteLockerSlotsData()
}
return nil
}
func (l *Loadout) GenerateFortniteLockerSlotsData() aid.JSON {
return aid.JSON{
"slots": aid.JSON{
"Character": l.GetItemSlotData(l.CharacterID),
"Backpack": l.GetItemSlotData(l.BackpackID),
"Pickaxe": l.GetItemSlotData(l.PickaxeID),
"Glider": l.GetItemSlotData(l.GliderID),
"ItemWrap": l.GetItemsSlotData(l.ItemWrapID),
"Dance": l.GetItemsSlotData(l.DanceID),
"SkyDiveContrail": l.GetItemSlotData(l.ContrailID),
"LoadingScreen": l.GetItemSlotData(l.LoadingScreenID),
"MusicPack": l.GetItemSlotData(l.MusicPackID),
},
}
}
func (l *Loadout) GetItemSlotData(itemId string) aid.JSON {
json := aid.JSON{
"items": []string{},
"activeVariants": []aid.JSON{},
}
person := Find(l.ProfileID)
if person == nil {
return json
}
item := person.AthenaProfile.Items.GetItem(itemId)
if item == nil {
return json
}
items := json["items"].([]string)
items = append(items, item.TemplateID)
activeVariants := json["activeVariants"].([]aid.JSON)
activeVariants = append(activeVariants, aid.JSON{
"variants": item.GenerateFortniteItemVariantChannels(),
})
json["items"] = items
json["activeVariants"] = activeVariants
return json
}
func (l *Loadout) GetItemsSlotData(itemIds []string) aid.JSON {
json := aid.JSON{
"items": []string{},
"activeVariants": []aid.JSON{},
}
person := Find(l.ProfileID)
if person == nil {
return json
}
for _, itemId := range itemIds {
item := person.AthenaProfile.Items.GetItem(itemId)
if item == nil {
item = &Item{
ProfileID: l.ProfileID,
Variants: []*VariantChannel{},
}
}
items := json["items"].([]string)
items = append(items, item.TemplateID)
activeVariants := json["activeVariants"].([]aid.JSON)
activeVariants = append(activeVariants, aid.JSON{
"variants": item.GenerateFortniteItemVariantChannels(),
})
json["items"] = items
json["activeVariants"] = activeVariants
}
return aid.JSON{
"items": itemIds,
"activeVariants": []aid.JSON{},
}
}
func (l *Loadout) Delete() {
storage.Repo.DeleteLoadout(l.ID)
}
func (l *Loadout) ToDatabase(profileId string) *storage.DB_Loadout {
return &storage.DB_Loadout{
ID: l.ID,
ProfileID: profileId,
TemplateID: l.TemplateID,
LockerName: l.LockerName,
BannerID: l.BannerID,
BannerColorID: l.BannerColorID,
CharacterID: l.CharacterID,
PickaxeID: l.PickaxeID,
BackpackID: l.BackpackID,
GliderID: l.GliderID,
DanceID: l.DanceID,
ItemWrapID: l.ItemWrapID,
ContrailID: l.ContrailID,
LoadingScreenID: l.LoadingScreenID,
MusicPackID: l.MusicPackID,
}
}
func (q *Loadout) Save() {
storage.Repo.SaveLoadout(q.ToDatabase(q.ProfileID))
}

View File

@ -1,8 +1,6 @@
package person
import (
"time"
"github.com/ectrc/snow/storage"
"github.com/google/uuid"
)
@ -108,10 +106,7 @@ func findHelper(databasePerson *storage.DB_Person) *Person {
Profile0Profile: profile0,
}
cache.Store(person.ID, &CacheEntry{
Entry: person,
LastAccessed: time.Now(),
})
cache.SavePerson(person)
return person
}
@ -182,6 +177,7 @@ func (p *Person) ToDatabase() *storage.DB_Person {
Items: []storage.DB_Item{},
Gifts: []storage.DB_Gift{},
Quests: []storage.DB_Quest{},
Loadouts: []storage.DB_Loadout{},
Attributes: []storage.DB_PAttribute{},
Revision: profile.Revision,
}
@ -206,6 +202,11 @@ func (p *Person) ToDatabase() *storage.DB_Person {
return true
})
profile.Loadouts.RangeLoadouts(func(id string, loadout *Loadout) bool {
dbProfile.Loadouts = append(dbProfile.Loadouts, *loadout.ToDatabase(p.ID))
return true
})
dbPerson.Profiles = append(dbPerson.Profiles, dbProfile)
}

View File

@ -14,11 +14,12 @@ type Profile struct {
PersonID string
Items *ItemMutex
Gifts *GiftMutex
Quests *QuestMutex
Quests *QuestMutex
Attributes *AttributeMutex
Type string
Revision int
Changes []interface{}
Loadouts *LoadoutMutex
Type string
Revision int
Changes []interface{}
}
func NewProfile(profile string) *Profile {
@ -28,8 +29,9 @@ func NewProfile(profile string) *Profile {
PersonID: "",
Items: NewItemMutex(&storage.DB_Profile{ID: id, Type: profile}),
Gifts: NewGiftMutex(&storage.DB_Profile{ID: id, Type: profile}),
Quests: NewQuestMutex(&storage.DB_Profile{ID: id, Type: profile}),
Quests: NewQuestMutex(&storage.DB_Profile{ID: id, Type: profile}),
Attributes: NewAttributeMutex(&storage.DB_Profile{ID: id, Type: profile}),
Loadouts: NewLoadoutMutex(&storage.DB_Profile{ID: id, Type: profile}),
Type: profile,
Revision: 0,
Changes: []interface{}{},
@ -41,6 +43,7 @@ func FromDatabaseProfile(profile *storage.DB_Profile) *Profile {
gifts := NewGiftMutex(profile)
quests := NewQuestMutex(profile)
attributes := NewAttributeMutex(profile)
loadouts := NewLoadoutMutex(profile)
for _, item := range profile.Items {
items.AddItem(FromDatabaseItem(&item))
@ -54,6 +57,10 @@ func FromDatabaseProfile(profile *storage.DB_Profile) *Profile {
quests.AddQuest(FromDatabaseQuest(&quest))
}
for _, loadout := range profile.Loadouts {
loadouts.AddLoadout(FromDatabaseLoadout(&loadout))
}
for _, attribute := range profile.Attributes {
parsed := FromDatabaseAttribute(&attribute)
if parsed == nil {
@ -71,6 +78,7 @@ func FromDatabaseProfile(profile *storage.DB_Profile) *Profile {
Gifts: gifts,
Quests: quests,
Attributes: attributes,
Loadouts: loadouts,
Type: profile.Type,
Revision: profile.Revision,
Changes: []interface{}{},
@ -101,6 +109,11 @@ func (p *Profile) GenerateFortniteProfileEntry() aid.JSON {
return true
})
p.Loadouts.RangeLoadouts(func(id string, loadout *Loadout) bool {
items[id] = loadout.GenerateFortniteLoadoutEntry()
return true
})
return aid.JSON{
"profileId": p.Type,
"accountId": p.PersonID,
@ -124,6 +137,7 @@ func (p *Profile) Snapshot() *ProfileSnapshot {
gifts := map[string]GiftSnapshot{}
quests := map[string]Quest{}
attributes := map[string]Attribute{}
loadouts := map[string]Loadout{}
p.Items.RangeItems(func(id string, item *Item) bool {
items[id] = item.Snapshot()
@ -145,12 +159,18 @@ func (p *Profile) Snapshot() *ProfileSnapshot {
return true
})
p.Loadouts.RangeLoadouts(func(id string, loadout *Loadout) bool {
loadouts[id] = *loadout
return true
})
return &ProfileSnapshot{
ID: p.ID,
Items: items,
Gifts: gifts,
Quests: quests,
Attributes: attributes,
Loadouts: loadouts,
Type: p.Type,
Revision: p.Revision,
}
@ -205,6 +225,18 @@ func (p *Profile) Diff(b *ProfileSnapshot) []diff.Change {
if change.Type == "update" && change.Path[2] == "ValueJSON" {
p.CreateStatModifiedChange(p.Attributes.GetAttribute(change.Path[1]))
}
case "Loadouts":
if change.Type == "create" && change.Path[2] == "ID" {
p.CreateLoadoutAddedChange(p.Loadouts.GetLoadout(change.Path[1]))
}
if change.Type == "delete" && change.Path[2] == "ID" {
p.CreateLoadoutRemovedChange(change.Path[1])
}
if change.Type == "update" && change.Path[2] != "ID" {
p.CreateLoadoutChangedChange(p.Loadouts.GetLoadout(change.Path[1]), change.Path[2])
}
}
}
@ -315,6 +347,55 @@ func (p *Profile) CreateFullProfileUpdateChange() {
}}
}
func (p *Profile) CreateLoadoutAddedChange(loadout *Loadout) {
if loadout == nil {
fmt.Println("error getting item from profile", loadout.ID)
return
}
p.Changes = append(p.Changes, ItemAdded{
ChangeType: "itemAdded",
ItemId: loadout.ID,
Item: loadout.GenerateFortniteLoadoutEntry(),
})
}
func (p *Profile) CreateLoadoutRemovedChange(loadoutId string) {
p.Changes = append(p.Changes, ItemRemoved{
ChangeType: "itemRemoved",
ItemId: loadoutId,
})
}
func (p *Profile) CreateLoadoutChangedChange(loadout *Loadout, attribute string) {
if loadout == nil {
fmt.Println("error getting item from profile", loadout.ID)
return
}
lookup := map[string]string{
"LockerName": "locker_name",
"BannerID": "banner_icon_template",
"BannerColorID": "banner_color_template",
"CharacterID": "locker_slots_data",
"PickaxeID": "locker_slots_data",
"BackpackID": "locker_slots_data",
"GliderID": "locker_slots_data",
"DanceID": "locker_slots_data",
"ItemWrapID": "locker_slots_data",
"ContrailID": "locker_slots_data",
"LoadingScreenID": "locker_slots_data",
"MusicPackID": "locker_slots_data",
}
p.Changes = append(p.Changes, ItemAttributeChanged{
ChangeType: "itemAttributeChanged",
ItemId: loadout.ID,
AttributeName: lookup[attribute],
AttributeValue: loadout.GetAttribute(lookup[attribute]),
})
}
func (p *Profile) ClearProfileChanges() {
p.Changes = []interface{}{}
}

View File

@ -15,6 +15,7 @@ type ProfileSnapshot struct {
Gifts map[string]GiftSnapshot
Quests map[string]Quest
Attributes map[string]Attribute
Loadouts map[string]Loadout
Revision int
Type string
}

View File

@ -19,11 +19,12 @@ func NewItemMutex(profile *storage.DB_Profile) *ItemMutex {
}
}
func (m *ItemMutex) AddItem(item *Item) {
func (m *ItemMutex) AddItem(item *Item) *Item {
item.ProfileType = m.ProfileType
item.ProfileID = m.ProfileID
m.Store(item.ID, item)
storage.Repo.SaveItem(item.ToDatabase(m.ProfileID))
// storage.Repo.SaveItem(item.ToDatabase(m.ProfileID))
return item
}
func (m *ItemMutex) DeleteItem(id string) {
@ -89,10 +90,11 @@ func NewGiftMutex(profile *storage.DB_Profile) *GiftMutex {
}
}
func (m *GiftMutex) AddGift(gift *Gift) {
func (m *GiftMutex) AddGift(gift *Gift) *Gift {
gift.ProfileID = m.ProfileID
m.Store(gift.ID, gift)
storage.Repo.SaveGift(gift.ToDatabase(m.ProfileID))
// storage.Repo.SaveGift(gift.ToDatabase(m.ProfileID))
return gift
}
func (m *GiftMutex) DeleteGift(id string) {
@ -137,10 +139,11 @@ func NewQuestMutex(profile *storage.DB_Profile) *QuestMutex {
}
}
func (m *QuestMutex) AddQuest(quest *Quest) {
func (m *QuestMutex) AddQuest(quest *Quest) *Quest {
quest.ProfileID = m.ProfileID
m.Store(quest.ID, quest)
storage.Repo.SaveQuest(quest.ToDatabase(m.ProfileID))
// storage.Repo.SaveQuest(quest.ToDatabase(m.ProfileID))
return quest
}
func (m *QuestMutex) DeleteQuest(id string) {
@ -184,10 +187,11 @@ func NewAttributeMutex(profile *storage.DB_Profile) *AttributeMutex {
}
}
func (m *AttributeMutex) AddAttribute(attribute *Attribute) {
func (m *AttributeMutex) AddAttribute(attribute *Attribute) *Attribute {
attribute.ProfileID = m.ProfileID
m.Store(attribute.ID, attribute)
storage.Repo.SaveAttribute(attribute.ToDatabase(m.ProfileID))
// storage.Repo.SaveAttribute(attribute.ToDatabase(m.ProfileID))
return attribute
}
func (m *AttributeMutex) DeleteAttribute(id string) {
@ -232,4 +236,73 @@ func (m *AttributeMutex) Count() int {
return true
})
return count
}
type LoadoutMutex struct {
sync.Map
ProfileType string
ProfileID string
}
func NewLoadoutMutex(profile *storage.DB_Profile) *LoadoutMutex {
return &LoadoutMutex{
ProfileID: profile.ID,
}
}
func (m *LoadoutMutex) AddLoadout(loadout *Loadout) *Loadout {
loadout.ProfileID = m.ProfileID
m.Store(loadout.ID, loadout)
// storage.Repo.SaveLoadout(loadout.ToDatabase(m.ProfileID))
return loadout
}
func (m *LoadoutMutex) DeleteItem(id string) {
loadout := m.GetLoadout(id)
if loadout == nil {
return
}
loadout.Delete()
m.Delete(id)
storage.Repo.DeleteItem(id)
}
func (m *LoadoutMutex) GetLoadout(id string) *Loadout {
loadout, ok := m.Load(id)
if !ok {
return nil
}
return loadout.(*Loadout)
}
func (m *LoadoutMutex) GetLoadoutByName(name string) *Loadout {
var loadout *Loadout
m.Range(func(key, value interface{}) bool {
if value.(*Loadout).LockerName == name {
loadout = value.(*Loadout)
return false
}
return true
})
return loadout
}
func (m *LoadoutMutex) RangeLoadouts(f func(key string, value *Loadout) bool) {
m.Range(func(key, value interface{}) bool {
return f(key.(string), value.(*Loadout))
})
}
func (m *LoadoutMutex) Count() int {
count := 0
m.Range(func(key, value interface{}) bool {
count++
return true
})
return count
}

View File

@ -59,7 +59,8 @@ user.CommonCoreProfile.Diff(snapshot)
- **_Chapter 1 Season 2_** `Fortnite+Release-2.5-CL-3889387-Windows` I started with this build of the game as it requires more work to get working, this means snow can support _most_ versions of the game.
- **_Chapter 1 Season 5_** `Fortnite+Release-5.41-CL-4363240-Windows` This build was used to make sure challenges, variants and lobby backgrounds work.
- **_Chapter 1 Season 8_** `Fortnite+Release-8.51-CL-6165369-Windows` Fixed the invisible player bugs and other specific issues.
- **_Chapter 1 Season 8_** `Fortnite+Release-8.51-CL-6165369-Windows` Fixed the invisible player bug caused by invalid account responses. Also fixed the issue with the item shop spamming the api.
- **_Chapter 3 Season 1_** `Fortnite+Release-19.10-CL-Unknown-Windows` This is a very new build of fortnite that introfuces alot of different methods e.g. locker data is now stored as an item. Every MCP action is now fully working and tested. You need to start using easy anticheat otherwise this will not work.
### Broken

6304
storage/mem/cosmetics.json Normal file

File diff suppressed because it is too large Load Diff

View File

@ -4,6 +4,7 @@ import (
"github.com/ectrc/snow/aid"
"gorm.io/driver/postgres"
"gorm.io/gorm"
"gorm.io/gorm/logger"
)
type PostgresStorage struct {
@ -11,7 +12,9 @@ type PostgresStorage struct {
}
func NewPostgresStorage() *PostgresStorage {
db, err := gorm.Open(postgres.Open(aid.Config.Database.URI), &gorm.Config{})
db, err := gorm.Open(postgres.Open(aid.Config.Database.URI), &gorm.Config{
Logger: logger.Default.LogMode(logger.Info),
})
if err != nil {
panic(err)
}
@ -34,6 +37,7 @@ func (s *PostgresStorage) MigrateAll() {
s.Migrate(&DB_Loot{}, "Loot")
s.Migrate(&DB_VariantChannel{}, "Variants")
s.Migrate(&DB_PAttribute{}, "Attributes")
s.Migrate(&DB_Loadout{}, "Loadouts")
}
func (s *PostgresStorage) DropTables() {
@ -50,6 +54,8 @@ func (s *PostgresStorage) GetPerson(personId string) *DB_Person {
Preload("Profiles.Items").
Preload("Profiles.Gifts").
Preload("Profiles.Quests").
Preload("Profiles.Loadouts").
Where("id = ?", personId).
Find(&dbPerson)
if dbPerson.ID == "" {
@ -62,13 +68,14 @@ func (s *PostgresStorage) GetPerson(personId string) *DB_Person {
func (s *PostgresStorage) GetPersonByDisplay(displayName string) *DB_Person {
var dbPerson DB_Person
s.Postgres.
Preload("Profiles").
Preload("Profiles.Items.Variants").
Preload("Profiles.Gifts.Loot").
Preload("Profiles.Attributes").
Preload("Profiles.Items").
Preload("Profiles.Gifts").
Preload("Profiles.Quests").
// Preload("Profiles").
// Preload("Profiles.Items.Variants").
// Preload("Profiles.Gifts.Loot").
// Preload("Profiles.Attributes").
// Preload("Profiles.Items").
// Preload("Profiles.Gifts").
// Preload("Profiles.Quests").
// Preload("Profiles.Loadouts").
Where("display_name = ?", displayName).
Find(&dbPerson)
@ -90,6 +97,7 @@ func (s *PostgresStorage) GetAllPersons() []*DB_Person {
Preload("Profiles.Items").
Preload("Profiles.Gifts").
Preload("Profiles.Quests").
Preload("Profiles.Loadouts").
Find(&dbPersons)
return dbPersons
@ -158,3 +166,11 @@ func (s *PostgresStorage) SaveAttribute(attribute *DB_PAttribute) {
func (s *PostgresStorage) DeleteAttribute(attributeId string) {
s.Postgres.Delete(&DB_PAttribute{}, "id = ?", attributeId)
}
func (s *PostgresStorage) SaveLoadout(loadout *DB_Loadout) {
s.Postgres.Save(loadout)
}
func (s *PostgresStorage) DeleteLoadout(loadoutId string) {
s.Postgres.Delete(&DB_Loadout{}, "id = ?", loadoutId)
}

View File

@ -32,6 +32,9 @@ type Storage interface {
SaveAttribute(attribute *DB_PAttribute)
DeleteAttribute(attributeId string)
SaveLoadout(loadout *DB_Loadout)
DeleteLoadout(loadoutId string)
}
type Repository struct {
@ -124,4 +127,12 @@ func (r *Repository) SaveAttribute(attribute *DB_PAttribute) {
func (r *Repository) DeleteAttribute(attributeId string) {
r.Storage.DeleteAttribute(attributeId)
}
func (r *Repository) SaveLoadout(loadout *DB_Loadout) {
r.Storage.SaveLoadout(loadout)
}
func (r *Repository) DeleteLoadout(loadoutId string) {
r.Storage.DeleteLoadout(loadoutId)
}

View File

@ -24,6 +24,7 @@ type DB_Profile struct {
Gifts []DB_Gift `gorm:"foreignkey:ProfileID"`
Quests []DB_Quest `gorm:"foreignkey:ProfileID"`
Attributes []DB_PAttribute `gorm:"foreignkey:ProfileID"`
Loadouts []DB_Loadout `gorm:"foreignkey:ProfileID"`
Type string
Revision int
}
@ -44,6 +45,28 @@ func (DB_PAttribute) TableName() string {
return "Attributes"
}
type DB_Loadout struct {
ID string `gorm:"primary_key"`
ProfileID string
TemplateID string
LockerName string
BannerID string
BannerColorID string
CharacterID string
PickaxeID string
BackpackID string
GliderID string
DanceID pq.StringArray `gorm:"type:text[]"`
ItemWrapID pq.StringArray `gorm:"type:text[]"`
ContrailID string
LoadingScreenID string
MusicPackID string
}
func (DB_Loadout) TableName() string {
return "Loadouts"
}
type DB_Item struct {
ID string `gorm:"primary_key"`
ProfileID string