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 ( var (
oatuhTokenGrantTypes = map[string]func(c *fiber.Ctx, body *OAuthTokenBody) error{ oauthTokenGrantTypes = map[string]func(c *fiber.Ctx, body *OAuthTokenBody) error{
"client_credentials": PostOAuthTokenClientCredentials, "client_credentials": PostOAuthTokenClientCredentials,
"password": PostOAuthTokenPassword, "password": PostOAuthTokenPassword,
} }
@ -29,7 +29,7 @@ func PostOAuthToken(c *fiber.Ctx) error {
return c.Status(fiber.StatusBadRequest).JSON(aid.ErrorBadRequest("Invalid Request Body")) 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) return action(c, &body)
} }

View File

@ -1,6 +1,8 @@
package handlers package handlers
import ( import (
"strconv"
"github.com/ectrc/snow/aid" "github.com/ectrc/snow/aid"
"github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2"
) )
@ -26,7 +28,7 @@ func GetFortniteReceipts(c *fiber.Ctx) error {
} }
func GetMatchmakingSession(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 { 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 { 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{ return c.Status(fiber.StatusOK).JSON(aid.JSON{
"battlepassaboutmessages": aid.JSON{ "battlepassaboutmessages": aid.JSON{
@ -77,14 +95,14 @@ func GetContentPages(c *fiber.Ctx) error {
}, },
"dynamicbackgrounds": aid.JSON{ "dynamicbackgrounds": aid.JSON{
"backgrounds": aid.JSON{"backgrounds": []aid.JSON{ "backgrounds": aid.JSON{"backgrounds": []aid.JSON{
// { {
// "key": "lobby", "key": "lobby",
// "stage": "season"+seasonString, "stage": "season" + seasonString,
// }, },
// { {
// "key": "vault", "key": "vault",
// "stage": "season"+seasonString, "stage": "season" + seasonString,
// }, },
}}, }},
"lastModified": "0000-00-00T00:00:00.000Z", "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")) profile := person.GetProfileFromType(c.Query("profileId"))
defer profile.ClearProfileChanges() if action, ok := profileActions[c.Params("action")]; ok && profile != nil {
defer profile.ClearProfileChanges()
before := profile.Snapshot() before := profile.Snapshot()
if action, ok := profileActions[c.Params("action")]; ok {
if err := action(c, person, profile); err != nil { if err := action(c, person, profile); err != nil {
return c.Status(400).JSON(aid.ErrorBadRequest(err.Error())) return c.Status(400).JSON(aid.ErrorBadRequest(err.Error()))
} }
profile.Diff(before)
} }
profile.Diff(before)
revision, _ := strconv.Atoi(c.Query("rvn")) 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 = profile.Revision
} }
revision++ revision++
changes := []interface{}{}
if profile != nil {
changes = profile.Changes
}
return c.Status(200).JSON(aid.JSON{ return c.Status(200).JSON(aid.JSON{
"profileId": profile.Type, "profileId": c.Query("profileId"),
"profileRevision": revision, "profileRevision": revision,
"profileCommandRevision": revision, "profileCommandRevision": revision,
"profileChangesBaseRevision": revision - 1, "profileChangesBaseRevision": revision - 1,
"profileChanges": profile.Changes, "profileChanges": changes,
"multiUpdate": []aid.JSON{}, "multiUpdate": []aid.JSON{},
"notifications": []aid.JSON{}, "notifications": []aid.JSON{},
"responseVersion": 1, "responseVersion": 1,

View File

@ -1,12 +1,119 @@
package handlers package handlers
import ( import (
"crypto/sha1"
"encoding/hex"
"github.com/ectrc/snow/aid" "github.com/ectrc/snow/aid"
"github.com/gofiber/fiber/v2" "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 { 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 { 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.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 { func GetUserStorageFiles(c *fiber.Ctx) error {

View File

@ -45,6 +45,14 @@ func main() {
r.Use(aid.FiberCors()) r.Use(aid.FiberCors())
r.Get("/content/api/pages/fortnite-game", handlers.GetContentPages) 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 := r.Group("/account/api")
account.Get("/public/account", handlers.GetPublicAccounts) account.Get("/public/account", handlers.GetPublicAccounts)

View File

@ -1,9 +1,11 @@
package person package person
import ( import (
"encoding/json"
"strconv" "strconv"
"github.com/ectrc/snow/aid" "github.com/ectrc/snow/aid"
"github.com/ectrc/snow/storage"
) )
var ( var (
@ -25,7 +27,7 @@ func NewFortnitePerson(displayName string, key string) *Person {
person.DisplayName = displayName person.DisplayName = displayName
person.AccessKey = key 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 { for _, item := range defaultAthenaItems {
person.AthenaProfile.Items.AddItem(NewItem(item, 1)) person.AthenaProfile.Items.AddItem(NewItem(item, 1))
@ -34,77 +36,91 @@ func NewFortnitePerson(displayName string, key string) *Person {
for _, item := range defaultCommonCoreItems { for _, item := range defaultCommonCoreItems {
if item == "HomebaseBannerIcon:StandardBanner" { if item == "HomebaseBannerIcon:StandardBanner" {
for i := 1; i < 32; i++ { 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 continue
} }
if item == "HomebaseBannerColor:DefaultColor" { if item == "HomebaseBannerColor:DefaultColor" {
for i := 1; i < 22; i++ { 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 continue
} }
if item == "Currency:MtxPurchased" { if item == "Currency:MtxPurchased" {
person.CommonCoreProfile.Items.AddItem(NewItem(item, 0)) person.CommonCoreProfile.Items.AddItem(NewItem(item, 0)).Save()
continue 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("mfa_reward_claimed", true)).Save()
person.AthenaProfile.Attributes.AddAttribute(NewAttribute("rested_xp_overflow", 0)) person.AthenaProfile.Attributes.AddAttribute(NewAttribute("rested_xp_overflow", 0)).Save()
person.AthenaProfile.Attributes.AddAttribute(NewAttribute("lifetime_wins", 0)) person.AthenaProfile.Attributes.AddAttribute(NewAttribute("lifetime_wins", 0)).Save()
person.AthenaProfile.Attributes.AddAttribute(NewAttribute("party_assist_quest", "")) person.AthenaProfile.Attributes.AddAttribute(NewAttribute("party_assist_quest", "")).Save()
person.AthenaProfile.Attributes.AddAttribute(NewAttribute("quest_manager", aid.JSON{})) person.AthenaProfile.Attributes.AddAttribute(NewAttribute("quest_manager", aid.JSON{})).Save()
person.AthenaProfile.Attributes.AddAttribute(NewAttribute("inventory_limit_bonus", 0)) person.AthenaProfile.Attributes.AddAttribute(NewAttribute("inventory_limit_bonus", 0)).Save()
person.AthenaProfile.Attributes.AddAttribute(NewAttribute("daily_rewards", []aid.JSON{})) person.AthenaProfile.Attributes.AddAttribute(NewAttribute("daily_rewards", []aid.JSON{})).Save()
person.AthenaProfile.Attributes.AddAttribute(NewAttribute("competitive_identity", aid.JSON{})) person.AthenaProfile.Attributes.AddAttribute(NewAttribute("competitive_identity", aid.JSON{})).Save()
person.AthenaProfile.Attributes.AddAttribute(NewAttribute("season_update", 0)) person.AthenaProfile.Attributes.AddAttribute(NewAttribute("season_update", 0)).Save()
person.AthenaProfile.Attributes.AddAttribute(NewAttribute("season_num", aid.Config.Fortnite.Season)) person.AthenaProfile.Attributes.AddAttribute(NewAttribute("season_num", aid.Config.Fortnite.Season)).Save()
person.AthenaProfile.Attributes.AddAttribute(NewAttribute("permissions", []aid.JSON{})) 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("accountLevel", 1)).Save()
person.AthenaProfile.Attributes.AddAttribute(NewAttribute("last_applied_loadout", "")) person.AthenaProfile.Attributes.AddAttribute(NewAttribute("level", 1)).Save()
person.AthenaProfile.Attributes.AddAttribute(NewAttribute("active_loadout_index", 0)) 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("book_purchased", false)).Save()
person.AthenaProfile.Attributes.AddAttribute(NewAttribute("level", 1)) person.AthenaProfile.Attributes.AddAttribute(NewAttribute("book_level", 1)).Save()
person.AthenaProfile.Attributes.AddAttribute(NewAttribute("xp", 0)) person.AthenaProfile.Attributes.AddAttribute(NewAttribute("book_xp", 0)).Save()
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)) 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("book_level", 1)) person.AthenaProfile.Attributes.AddAttribute(NewAttribute("favorite_backpack", "")).Save()
person.AthenaProfile.Attributes.AddAttribute(NewAttribute("book_xp", 0)) 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.CommonCoreProfile.Attributes.AddAttribute(NewAttribute("mfa_enabled", true)).Save()
person.AthenaProfile.Attributes.AddAttribute(NewAttribute("favorite_backpack", "")) person.CommonCoreProfile.Attributes.AddAttribute(NewAttribute("mtx_affiliate", "")).Save()
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("mtx_purchase_history", aid.JSON{ person.CommonCoreProfile.Attributes.AddAttribute(NewAttribute("mtx_purchase_history", aid.JSON{
"refundsUsed": 0, "refundsUsed": 0,
"refundCredits": 3, "refundCredits": 3,
"purchases": []any{}, "purchases": []any{},
})) })).Save()
person.CommonCoreProfile.Attributes.AddAttribute(NewAttribute("current_mtx_platform", "EpicPC")) person.CommonCoreProfile.Attributes.AddAttribute(NewAttribute("current_mtx_platform", "EpicPC")).Save()
person.CommonCoreProfile.Attributes.AddAttribute(NewAttribute("allowed_to_receive_gifts", true)) person.CommonCoreProfile.Attributes.AddAttribute(NewAttribute("allowed_to_receive_gifts", true)).Save()
person.CommonCoreProfile.Attributes.AddAttribute(NewAttribute("allowed_to_send_gifts", true)) person.CommonCoreProfile.Attributes.AddAttribute(NewAttribute("allowed_to_send_gifts", true)).Save()
person.CommonCoreProfile.Attributes.AddAttribute(NewAttribute("gift_history", aid.JSON{})) 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() person.Save()

View File

@ -70,21 +70,12 @@ func FromDatabaseLoot(item *storage.DB_Loot) *Item {
} }
func (i *Item) GenerateFortniteItemEntry() aid.JSON { func (i *Item) GenerateFortniteItemEntry() aid.JSON {
variants := []aid.JSON{}
attributes := aid.JSON{ attributes := aid.JSON{
"variants": variants, "variants": i.GenerateFortniteItemVariantChannels(),
"favorite": i.Favorite, "favorite": i.Favorite,
"item_seen": i.HasSeen, "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" { if i.TemplateID == "Currency:MtxPurchased" {
attributes = aid.JSON{ attributes = aid.JSON{
"platform": "Shared", "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{} { func (i *Item) GetAttribute(attribute string) interface{} {
switch attribute { switch attribute {
case "Favorite": 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 package person
import ( import (
"time"
"github.com/ectrc/snow/storage" "github.com/ectrc/snow/storage"
"github.com/google/uuid" "github.com/google/uuid"
) )
@ -108,10 +106,7 @@ func findHelper(databasePerson *storage.DB_Person) *Person {
Profile0Profile: profile0, Profile0Profile: profile0,
} }
cache.Store(person.ID, &CacheEntry{ cache.SavePerson(person)
Entry: person,
LastAccessed: time.Now(),
})
return person return person
} }
@ -182,6 +177,7 @@ func (p *Person) ToDatabase() *storage.DB_Person {
Items: []storage.DB_Item{}, Items: []storage.DB_Item{},
Gifts: []storage.DB_Gift{}, Gifts: []storage.DB_Gift{},
Quests: []storage.DB_Quest{}, Quests: []storage.DB_Quest{},
Loadouts: []storage.DB_Loadout{},
Attributes: []storage.DB_PAttribute{}, Attributes: []storage.DB_PAttribute{},
Revision: profile.Revision, Revision: profile.Revision,
} }
@ -206,6 +202,11 @@ func (p *Person) ToDatabase() *storage.DB_Person {
return true 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) dbPerson.Profiles = append(dbPerson.Profiles, dbProfile)
} }

View File

@ -14,11 +14,12 @@ type Profile struct {
PersonID string PersonID string
Items *ItemMutex Items *ItemMutex
Gifts *GiftMutex Gifts *GiftMutex
Quests *QuestMutex Quests *QuestMutex
Attributes *AttributeMutex Attributes *AttributeMutex
Type string Loadouts *LoadoutMutex
Revision int Type string
Changes []interface{} Revision int
Changes []interface{}
} }
func NewProfile(profile string) *Profile { func NewProfile(profile string) *Profile {
@ -28,8 +29,9 @@ func NewProfile(profile string) *Profile {
PersonID: "", PersonID: "",
Items: NewItemMutex(&storage.DB_Profile{ID: id, Type: profile}), Items: NewItemMutex(&storage.DB_Profile{ID: id, Type: profile}),
Gifts: NewGiftMutex(&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}), Attributes: NewAttributeMutex(&storage.DB_Profile{ID: id, Type: profile}),
Loadouts: NewLoadoutMutex(&storage.DB_Profile{ID: id, Type: profile}),
Type: profile, Type: profile,
Revision: 0, Revision: 0,
Changes: []interface{}{}, Changes: []interface{}{},
@ -41,6 +43,7 @@ func FromDatabaseProfile(profile *storage.DB_Profile) *Profile {
gifts := NewGiftMutex(profile) gifts := NewGiftMutex(profile)
quests := NewQuestMutex(profile) quests := NewQuestMutex(profile)
attributes := NewAttributeMutex(profile) attributes := NewAttributeMutex(profile)
loadouts := NewLoadoutMutex(profile)
for _, item := range profile.Items { for _, item := range profile.Items {
items.AddItem(FromDatabaseItem(&item)) items.AddItem(FromDatabaseItem(&item))
@ -54,6 +57,10 @@ func FromDatabaseProfile(profile *storage.DB_Profile) *Profile {
quests.AddQuest(FromDatabaseQuest(&quest)) quests.AddQuest(FromDatabaseQuest(&quest))
} }
for _, loadout := range profile.Loadouts {
loadouts.AddLoadout(FromDatabaseLoadout(&loadout))
}
for _, attribute := range profile.Attributes { for _, attribute := range profile.Attributes {
parsed := FromDatabaseAttribute(&attribute) parsed := FromDatabaseAttribute(&attribute)
if parsed == nil { if parsed == nil {
@ -71,6 +78,7 @@ func FromDatabaseProfile(profile *storage.DB_Profile) *Profile {
Gifts: gifts, Gifts: gifts,
Quests: quests, Quests: quests,
Attributes: attributes, Attributes: attributes,
Loadouts: loadouts,
Type: profile.Type, Type: profile.Type,
Revision: profile.Revision, Revision: profile.Revision,
Changes: []interface{}{}, Changes: []interface{}{},
@ -101,6 +109,11 @@ func (p *Profile) GenerateFortniteProfileEntry() aid.JSON {
return true return true
}) })
p.Loadouts.RangeLoadouts(func(id string, loadout *Loadout) bool {
items[id] = loadout.GenerateFortniteLoadoutEntry()
return true
})
return aid.JSON{ return aid.JSON{
"profileId": p.Type, "profileId": p.Type,
"accountId": p.PersonID, "accountId": p.PersonID,
@ -124,6 +137,7 @@ func (p *Profile) Snapshot() *ProfileSnapshot {
gifts := map[string]GiftSnapshot{} gifts := map[string]GiftSnapshot{}
quests := map[string]Quest{} quests := map[string]Quest{}
attributes := map[string]Attribute{} attributes := map[string]Attribute{}
loadouts := map[string]Loadout{}
p.Items.RangeItems(func(id string, item *Item) bool { p.Items.RangeItems(func(id string, item *Item) bool {
items[id] = item.Snapshot() items[id] = item.Snapshot()
@ -145,12 +159,18 @@ func (p *Profile) Snapshot() *ProfileSnapshot {
return true return true
}) })
p.Loadouts.RangeLoadouts(func(id string, loadout *Loadout) bool {
loadouts[id] = *loadout
return true
})
return &ProfileSnapshot{ return &ProfileSnapshot{
ID: p.ID, ID: p.ID,
Items: items, Items: items,
Gifts: gifts, Gifts: gifts,
Quests: quests, Quests: quests,
Attributes: attributes, Attributes: attributes,
Loadouts: loadouts,
Type: p.Type, Type: p.Type,
Revision: p.Revision, Revision: p.Revision,
} }
@ -205,6 +225,18 @@ func (p *Profile) Diff(b *ProfileSnapshot) []diff.Change {
if change.Type == "update" && change.Path[2] == "ValueJSON" { if change.Type == "update" && change.Path[2] == "ValueJSON" {
p.CreateStatModifiedChange(p.Attributes.GetAttribute(change.Path[1])) 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() { func (p *Profile) ClearProfileChanges() {
p.Changes = []interface{}{} p.Changes = []interface{}{}
} }

View File

@ -15,6 +15,7 @@ type ProfileSnapshot struct {
Gifts map[string]GiftSnapshot Gifts map[string]GiftSnapshot
Quests map[string]Quest Quests map[string]Quest
Attributes map[string]Attribute Attributes map[string]Attribute
Loadouts map[string]Loadout
Revision int Revision int
Type string 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.ProfileType = m.ProfileType
item.ProfileID = m.ProfileID item.ProfileID = m.ProfileID
m.Store(item.ID, item) 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) { 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 gift.ProfileID = m.ProfileID
m.Store(gift.ID, gift) 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) { 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 quest.ProfileID = m.ProfileID
m.Store(quest.ID, quest) 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) { 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 attribute.ProfileID = m.ProfileID
m.Store(attribute.ID, attribute) 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) { func (m *AttributeMutex) DeleteAttribute(id string) {
@ -233,3 +237,72 @@ func (m *AttributeMutex) Count() int {
}) })
return count 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 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 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 ### 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" "github.com/ectrc/snow/aid"
"gorm.io/driver/postgres" "gorm.io/driver/postgres"
"gorm.io/gorm" "gorm.io/gorm"
"gorm.io/gorm/logger"
) )
type PostgresStorage struct { type PostgresStorage struct {
@ -11,7 +12,9 @@ type PostgresStorage struct {
} }
func NewPostgresStorage() *PostgresStorage { 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 { if err != nil {
panic(err) panic(err)
} }
@ -34,6 +37,7 @@ func (s *PostgresStorage) MigrateAll() {
s.Migrate(&DB_Loot{}, "Loot") s.Migrate(&DB_Loot{}, "Loot")
s.Migrate(&DB_VariantChannel{}, "Variants") s.Migrate(&DB_VariantChannel{}, "Variants")
s.Migrate(&DB_PAttribute{}, "Attributes") s.Migrate(&DB_PAttribute{}, "Attributes")
s.Migrate(&DB_Loadout{}, "Loadouts")
} }
func (s *PostgresStorage) DropTables() { func (s *PostgresStorage) DropTables() {
@ -50,6 +54,8 @@ func (s *PostgresStorage) GetPerson(personId string) *DB_Person {
Preload("Profiles.Items"). Preload("Profiles.Items").
Preload("Profiles.Gifts"). Preload("Profiles.Gifts").
Preload("Profiles.Quests"). Preload("Profiles.Quests").
Preload("Profiles.Loadouts").
Where("id = ?", personId).
Find(&dbPerson) Find(&dbPerson)
if dbPerson.ID == "" { if dbPerson.ID == "" {
@ -62,13 +68,14 @@ func (s *PostgresStorage) GetPerson(personId string) *DB_Person {
func (s *PostgresStorage) GetPersonByDisplay(displayName string) *DB_Person { func (s *PostgresStorage) GetPersonByDisplay(displayName string) *DB_Person {
var dbPerson DB_Person var dbPerson DB_Person
s.Postgres. s.Postgres.
Preload("Profiles"). // Preload("Profiles").
Preload("Profiles.Items.Variants"). // Preload("Profiles.Items.Variants").
Preload("Profiles.Gifts.Loot"). // Preload("Profiles.Gifts.Loot").
Preload("Profiles.Attributes"). // Preload("Profiles.Attributes").
Preload("Profiles.Items"). // Preload("Profiles.Items").
Preload("Profiles.Gifts"). // Preload("Profiles.Gifts").
Preload("Profiles.Quests"). // Preload("Profiles.Quests").
// Preload("Profiles.Loadouts").
Where("display_name = ?", displayName). Where("display_name = ?", displayName).
Find(&dbPerson) Find(&dbPerson)
@ -90,6 +97,7 @@ func (s *PostgresStorage) GetAllPersons() []*DB_Person {
Preload("Profiles.Items"). Preload("Profiles.Items").
Preload("Profiles.Gifts"). Preload("Profiles.Gifts").
Preload("Profiles.Quests"). Preload("Profiles.Quests").
Preload("Profiles.Loadouts").
Find(&dbPersons) Find(&dbPersons)
return dbPersons return dbPersons
@ -158,3 +166,11 @@ func (s *PostgresStorage) SaveAttribute(attribute *DB_PAttribute) {
func (s *PostgresStorage) DeleteAttribute(attributeId string) { func (s *PostgresStorage) DeleteAttribute(attributeId string) {
s.Postgres.Delete(&DB_PAttribute{}, "id = ?", attributeId) 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) SaveAttribute(attribute *DB_PAttribute)
DeleteAttribute(attributeId string) DeleteAttribute(attributeId string)
SaveLoadout(loadout *DB_Loadout)
DeleteLoadout(loadoutId string)
} }
type Repository struct { type Repository struct {
@ -125,3 +128,11 @@ func (r *Repository) SaveAttribute(attribute *DB_PAttribute) {
func (r *Repository) DeleteAttribute(attributeId string) { func (r *Repository) DeleteAttribute(attributeId string) {
r.Storage.DeleteAttribute(attributeId) 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"` Gifts []DB_Gift `gorm:"foreignkey:ProfileID"`
Quests []DB_Quest `gorm:"foreignkey:ProfileID"` Quests []DB_Quest `gorm:"foreignkey:ProfileID"`
Attributes []DB_PAttribute `gorm:"foreignkey:ProfileID"` Attributes []DB_PAttribute `gorm:"foreignkey:ProfileID"`
Loadouts []DB_Loadout `gorm:"foreignkey:ProfileID"`
Type string Type string
Revision int Revision int
} }
@ -44,6 +45,28 @@ func (DB_PAttribute) TableName() string {
return "Attributes" 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 { type DB_Item struct {
ID string `gorm:"primary_key"` ID string `gorm:"primary_key"`
ProfileID string ProfileID string