Redesigned whole UI

This commit is contained in:
Alessandro Autiero
2023-02-25 01:28:36 +01:00
parent 63c7cc5c5b
commit 760e336bc0
77 changed files with 693 additions and 387816 deletions

View File

@@ -0,0 +1,2 @@
for /f "tokens=5" %%a in ('netstat -aon ^| find ":3551" ^| find "LISTENING"') do taskkill /f /pid %%a
for /f "tokens=5" %%a in ('netstat -aon ^| find ":8080" ^| find "LISTENING"') do taskkill /f /pid %%a

BIN
assets/binaries/server.zip Normal file

Binary file not shown.

Binary file not shown.

View File

@@ -1,14 +0,0 @@
[ConsoleVariables]
FortMatchmakingV2.EnableContentBeacon=0
FortMatchmakingV2.ContentBeaconFailureCancelsMatchmaking=0
[PatchCheck]
ModuleName=FortnitePatchCheck
bCheckPlatformOSSForUpdate=false
bCheckOSSForUpdate=false
[XMPP]
bEnableWebsockets=false
[/Script/Engine.InputSettings]
ConsoleKey=F8

View File

@@ -1,31 +0,0 @@
[/Script/FortniteGame.FortGlobals]
bAthenaLeaderboardFrontEndEnabled=false
bAthenaStatsFrontendEnabled=false
bGlobalLeaderboardsFrontEndEnabled=false
SubGameAccess=(SubGame=Campaign,AccessStatus=OpenAccess,MatchmakingStatus=Enabled)
+SubGameAccess=(SubGame=Athena,AccessStatus=OpenAccess,MatchmakingStatus=Enabled)
+SubGameAccess=(SubGame=Campaign,AccessStatus=OpenAccess,MatchmakingStatus=Enabled)
bUploadAthenaStats=false
bUploadAthenaStatsV2=false
[/Script/FortniteGame.FortMatchmakingV2]
bCustomKeyEnabled=true
[/Script/FortniteGame.FortChatManager]
bShouldRequestGeneralChatRooms=false
bShouldJoinGlobalChat=false
bShouldJoinFoaunderChat=false
bIsAthenaGlobalChatEnabled=false
[/Script/FortniteGame.FortGameInstance]
!FrontEndPlaylistData=ClearArray
bBattleRoyaleMatchmakingEnabled=true
+FrontEndPlaylistData=(PlaylistName=Playlist_DefaultSolo, PlaylistAccess=(bEnabled=True, bIsDefaultPlaylist=true, bVisibleWhenDisabled=false, bDisplayAsNew=false, CategoryIndex=0, bDisplayAsLimitedTime=false, DisplayPriority=3))
+FrontEndPlaylistData=(PlaylistName=Playlist_DefaultDuo, PlaylistAccess=(bEnabled=True, bIsDefaultPlaylist=true, bVisibleWhenDisabled=false, bDisplayAsNew=false, CategoryIndex=0, bDisplayAsLimitedTime=false, DisplayPriority=4))
+FrontEndPlaylistData=(PlaylistName=Playlist_Trios, PlaylistAccess=(bEnabled=true, bIsDefaultPlaylist=true, bVisibleWhenDisabled=false, bDisplayAsNew=False, bDisplayAsLimitedTime=false, DisplayPriority=5, CategoryIndex=0))
+FrontEndPlaylistData=(PlaylistName=Playlist_DefaultSquad, PlaylistAccess=(bEnabled=true, bIsDefaultPlaylist=true, bVisibleWhenDisabled=false, bDisplayAsNew=false, CategoryIndex=0, bDisplayAsLimitedTime=false, DisplayPriority=6))
+FrontEndPlaylistData=(PlaylistName=Playlist_PlaygroundV2, PlaylistAccess=(bEnabled=true, bIsDefaultPlaylist=false, bVisibleWhenDisabled=false, bDisplayAsNew=false, CategoryIndex=2, bDisplayAsLimitedTime=false, DisplayPriority=16))
+FrontEndPlaylistData=(PlaylistName=Playlist_Campaign, PlaylistAccess=(bEnabled=true, bInvisibleWhenEnabled=true))
[/Script/Engine.InputSettings]
ConsoleKey=F8

View File

@@ -1,21 +0,0 @@
[/Script/FortniteGame.FortRuntimeOptions]
bEnableGlobalChat=false
bEnableMexiCola=false
bLoadDirectlyIntoLobby=true
bEnableSocialTab=false
bMOTDSameNewsForCreative=true
bForceBRMode=true
bEnableSavedLoadouts=false
bIsOutOfSeasonMode=true
!DisabledTabsForOutOfSeason=ClearArray
+DisabledTabsForOutOfSeason=(TabName="Lobby",TabState=EFortRuntimeOptionTabState::Hidden)
+DisabledTabsForOutOfSeason=(TabName="AthenaCompete",TabState=EFortRuntimeOptionTabState::Hidden)
+DisabledTabsForOutOfSeason=(TabName="AthenaCareer",TabState=EFortRuntimeOptionTabState::Hidden)
+DisabledTabsForOutOfSeason=(TabName="AthenaStore",TabState=EFortRuntimeOptionTabState::Hidden)
+DisabledTabsForOutOfSeason=(TabName="CareerScreen",TabState=EFortRuntimeOptionTabState::Hidden)
+DisabledTabsForOutOfSeason=(TabName="AthenaDirectAcquisition",TabState=EFortRuntimeOptionTabState::Hidden)
+DisabledTabsForOutOfSeason=(TabName="BattlePass",TabState=EFortRuntimeOptionTabState::Hidden)
+DisabledTabsForOutOfSeason=(TabName="AthenaCustomize",TabState=EFortRuntimeOptionTabState::Hidden)
[/Script/Engine.InputSettings]
ConsoleKey=F8

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,178 +0,0 @@
{
"_id": "LawinServer",
"created": "0001-01-01T00:00:00.000Z",
"updated": "0001-01-01T00:00:00.000Z",
"rvn": 0,
"wipeNumber": 1,
"accountId": "LawinServer",
"profileId": "collection_book_people0",
"version": "no_version",
"items": {
"CollectionBookPage:pageHeroes_Commando": {
"templateId": "CollectionBookPage:pageHeroes_Commando",
"attributes": {
"sectionStates": [],
"state": "Active"
},
"quantity": 1
},
"CollectionBookPage:pageHeroes_Constructor": {
"templateId": "CollectionBookPage:pageHeroes_Constructor",
"attributes": {
"sectionStates": [],
"state": "Active"
},
"quantity": 1
},
"CollectionBookPage:pageHeroes_Ninja": {
"templateId": "CollectionBookPage:pageHeroes_Ninja",
"attributes": {
"sectionStates": [],
"state": "Active"
},
"quantity": 1
},
"CollectionBookPage:pageHeroes_Outlander": {
"templateId": "CollectionBookPage:pageHeroes_Outlander",
"attributes": {
"sectionStates": [],
"state": "Active"
},
"quantity": 1
},
"CollectionBookPage:pagePeople_Defenders": {
"templateId": "CollectionBookPage:pagePeople_Defenders",
"attributes": {
"sectionStates": [],
"state": "Active"
},
"quantity": 1
},
"CollectionBookPage:pagePeople_Survivors": {
"templateId": "CollectionBookPage:pagePeople_Survivors",
"attributes": {
"sectionStates": [],
"state": "Active"
},
"quantity": 1
},
"CollectionBookPage:pagePeople_Leads": {
"templateId": "CollectionBookPage:pagePeople_Leads",
"attributes": {
"sectionStates": [],
"state": "Active"
},
"quantity": 1
},
"CollectionBookPage:pagePeople_UniqueLeads": {
"templateId": "CollectionBookPage:pagePeople_UniqueLeads",
"attributes": {
"sectionStates": [],
"state": "Active"
},
"quantity": 1
},
"CollectionBookPage:PageSpecial_Winter2017_Heroes": {
"templateId": "CollectionBookPage:PageSpecial_Winter2017_Heroes",
"attributes": {
"sectionStates": [],
"state": "Active"
},
"quantity": 1
},
"CollectionBookPage:PageSpecial_Halloween2017_Heroes": {
"templateId": "CollectionBookPage:PageSpecial_Halloween2017_Heroes",
"attributes": {
"sectionStates": [],
"state": "Active"
},
"quantity": 1
},
"CollectionBookPage:PageSpecial_Halloween2017_Workers": {
"templateId": "CollectionBookPage:PageSpecial_Halloween2017_Workers",
"attributes": {
"sectionStates": [],
"state": "Active"
},
"quantity": 1
},
"CollectionBookPage:PageSpecial_ChineseNewYear2018_Heroes": {
"templateId": "CollectionBookPage:PageSpecial_ChineseNewYear2018_Heroes",
"attributes": {
"sectionStates": [],
"state": "Active"
},
"quantity": 1
},
"CollectionBookPage:PageSpecial_SpringItOn2018_People": {
"templateId": "CollectionBookPage:PageSpecial_SpringItOn2018_People",
"attributes": {
"sectionStates": [],
"state": "Active"
},
"quantity": 1
},
"CollectionBookPage:PageSpecial_StormZoneCyber_Heroes": {
"templateId": "CollectionBookPage:PageSpecial_StormZoneCyber_Heroes",
"attributes": {
"sectionStates": [],
"state": "Active"
},
"quantity": 1
},
"CollectionBookPage:PageSpecial_Blockbuster2018_Heroes": {
"templateId": "CollectionBookPage:PageSpecial_Blockbuster2018_Heroes",
"attributes": {
"sectionStates": [],
"state": "Active"
},
"quantity": 1
},
"CollectionBookPage:PageSpecial_ShadowOps_Heroes": {
"templateId": "CollectionBookPage:PageSpecial_ShadowOps_Heroes",
"attributes": {
"sectionStates": [],
"state": "Active"
},
"quantity": 1
},
"CollectionBookPage:PageSpecial_RoadTrip2018_Heroes": {
"templateId": "CollectionBookPage:PageSpecial_RoadTrip2018_Heroes",
"attributes": {
"sectionStates": [],
"state": "Active"
},
"quantity": 1
},
"CollectionBookPage:PageSpecial_WildWest_Heroes": {
"templateId": "CollectionBookPage:PageSpecial_WildWest_Heroes",
"attributes": {
"sectionStates": [],
"state": "Active"
},
"quantity": 1
},
"CollectionBookPage:PageSpecial_StormZone_Heroes": {
"templateId": "CollectionBookPage:PageSpecial_StormZone_Heroes",
"attributes": {
"sectionStates": [],
"state": "Active"
},
"quantity": 1
},
"CollectionBookPage:PageSpecial_Scavenger_Heroes": {
"templateId": "CollectionBookPage:PageSpecial_Scavenger_Heroes",
"attributes": {
"sectionStates": [],
"state": "Active"
},
"quantity": 1
}
},
"stats": {
"attributes": {
"inventory_limit_bonus": 0
}
},
"commandRevision": 0
}

View File

@@ -1,458 +0,0 @@
{
"_id": "LawinServer",
"created": "0001-01-01T00:00:00.000Z",
"updated": "0001-01-01T00:00:00.000Z",
"rvn": 0,
"wipeNumber": 1,
"accountId": "LawinServer",
"profileId": "collection_book_schematics0",
"version": "no_version",
"items": {
"CollectionBookPage:pageMelee_Axes_Weapons": {
"templateId": "CollectionBookPage:pageMelee_Axes_Weapons",
"attributes": {
"sectionStates": [],
"state": "Active"
},
"quantity": 1
},
"CollectionBookPage:pageMelee_Axes_Weapons_Crystal": {
"templateId": "CollectionBookPage:pageMelee_Axes_Weapons_Crystal",
"attributes": {
"sectionStates": [],
"state": "Active"
},
"quantity": 1
},
"CollectionBookPage:pageMelee_Clubs_Weapons": {
"templateId": "CollectionBookPage:pageMelee_Clubs_Weapons",
"attributes": {
"sectionStates": [],
"state": "Active"
},
"quantity": 1
},
"CollectionBookPage:pageMelee_Clubs_Weapons_Crystal": {
"templateId": "CollectionBookPage:pageMelee_Clubs_Weapons_Crystal",
"attributes": {
"sectionStates": [],
"state": "Active"
},
"quantity": 1
},
"CollectionBookPage:pageMelee_Scythes_Weapons": {
"templateId": "CollectionBookPage:pageMelee_Scythes_Weapons",
"attributes": {
"sectionStates": [],
"state": "Active"
},
"quantity": 1
},
"CollectionBookPage:pageMelee_Scythes_Weapons_Crystal": {
"templateId": "CollectionBookPage:pageMelee_Scythes_Weapons_Crystal",
"attributes": {
"sectionStates": [],
"state": "Active"
},
"quantity": 1
},
"CollectionBookPage:pageMelee_Spears_Weapons": {
"templateId": "CollectionBookPage:pageMelee_Spears_Weapons",
"attributes": {
"sectionStates": [],
"state": "Active"
},
"quantity": 1
},
"CollectionBookPage:pageMelee_Spears_Weapons_Crystal": {
"templateId": "CollectionBookPage:pageMelee_Spears_Weapons_Crystal",
"attributes": {
"sectionStates": [],
"state": "Active"
},
"quantity": 1
},
"CollectionBookPage:pageMelee_Swords_Weapons": {
"templateId": "CollectionBookPage:pageMelee_Swords_Weapons",
"attributes": {
"sectionStates": [],
"state": "Active"
},
"quantity": 1
},
"CollectionBookPage:pageMelee_Swords_Weapons_Crystal": {
"templateId": "CollectionBookPage:pageMelee_Swords_Weapons_Crystal",
"attributes": {
"sectionStates": [],
"state": "Active"
},
"quantity": 1
},
"CollectionBookPage:pageMelee_Tools_Weapons": {
"templateId": "CollectionBookPage:pageMelee_Tools_Weapons",
"attributes": {
"sectionStates": [],
"state": "Active"
},
"quantity": 1
},
"CollectionBookPage:pageMelee_Tools_Weapons_Crystal": {
"templateId": "CollectionBookPage:pageMelee_Tools_Weapons_Crystal",
"attributes": {
"sectionStates": [],
"state": "Active"
},
"quantity": 1
},
"CollectionBookPage:pageRanged_Assault_Weapons": {
"templateId": "CollectionBookPage:pageRanged_Assault_Weapons",
"attributes": {
"sectionStates": [],
"state": "Active"
},
"quantity": 1
},
"CollectionBookPage:pageRanged_Assault_Weapons_Crystal": {
"templateId": "CollectionBookPage:pageRanged_Assault_Weapons_Crystal",
"attributes": {
"sectionStates": [],
"state": "Active"
},
"quantity": 1
},
"CollectionBookPage:pageRanged_Shotgun_Weapons": {
"templateId": "CollectionBookPage:pageRanged_Shotgun_Weapons",
"attributes": {
"sectionStates": [],
"state": "Active"
},
"quantity": 1
},
"CollectionBookPage:pageRanged_Shotgun_Weapons_Crystal": {
"templateId": "CollectionBookPage:pageRanged_Shotgun_Weapons_Crystal",
"attributes": {
"sectionStates": [],
"state": "Active"
},
"quantity": 1
},
"CollectionBookPage:page_Ranged_Pistols_Weapons": {
"templateId": "CollectionBookPage:page_Ranged_Pistols_Weapons",
"attributes": {
"sectionStates": [],
"state": "Active"
},
"quantity": 1
},
"CollectionBookPage:page_Ranged_Pistols_Weapons_Crystal": {
"templateId": "CollectionBookPage:page_Ranged_Pistols_Weapons_Crystal",
"attributes": {
"sectionStates": [],
"state": "Active"
},
"quantity": 1
},
"CollectionBookPage:pageRanged_Snipers_Weapons": {
"templateId": "CollectionBookPage:pageRanged_Snipers_Weapons",
"attributes": {
"sectionStates": [],
"state": "Active"
},
"quantity": 1
},
"CollectionBookPage:pageRanged_Snipers_Weapons_Crystal": {
"templateId": "CollectionBookPage:pageRanged_Snipers_Weapons_Crystal",
"attributes": {
"sectionStates": [],
"state": "Active"
},
"quantity": 1
},
"CollectionBookPage:pageRanged_Explosive_Weapons": {
"templateId": "CollectionBookPage:pageRanged_Explosive_Weapons",
"attributes": {
"sectionStates": [],
"state": "Active"
},
"quantity": 1
},
"CollectionBookPage:pageTraps_Wall": {
"templateId": "CollectionBookPage:pageTraps_Wall",
"attributes": {
"sectionStates": [],
"state": "Active"
},
"quantity": 1
},
"CollectionBookPage:pageTraps_Ceiling": {
"templateId": "CollectionBookPage:pageTraps_Ceiling",
"attributes": {
"sectionStates": [],
"state": "Active"
},
"quantity": 1
},
"CollectionBookPage:pageTraps_Floor": {
"templateId": "CollectionBookPage:pageTraps_Floor",
"attributes": {
"sectionStates": [],
"state": "Active"
},
"quantity": 1
},
"CollectionBookPage:PageSpecial_Weapons_Ranged_Medieval": {
"templateId": "CollectionBookPage:PageSpecial_Weapons_Ranged_Medieval",
"attributes": {
"sectionStates": [],
"state": "Active"
},
"quantity": 1
},
"CollectionBookPage:PageSpecial_Weapons_Ranged_Medieval_Crystal": {
"templateId": "CollectionBookPage:PageSpecial_Weapons_Ranged_Medieval_Crystal",
"attributes": {
"sectionStates": [],
"state": "Active"
},
"quantity": 1
},
"CollectionBookPage:PageSpecial_Weapons_Melee_Medieval": {
"templateId": "CollectionBookPage:PageSpecial_Weapons_Melee_Medieval",
"attributes": {
"sectionStates": [],
"state": "Active"
},
"quantity": 1
},
"CollectionBookPage:PageSpecial_Weapons_Melee_Medieval_Crystal": {
"templateId": "CollectionBookPage:PageSpecial_Weapons_Melee_Medieval_Crystal",
"attributes": {
"sectionStates": [],
"state": "Active"
},
"quantity": 1
},
"CollectionBookPage:PageSpecial_Winter2017_Weapons": {
"templateId": "CollectionBookPage:PageSpecial_Winter2017_Weapons",
"attributes": {
"sectionStates": [],
"state": "Active"
},
"quantity": 1
},
"CollectionBookPage:PageSpecial_Winter2017_Weapons_Crystal": {
"templateId": "CollectionBookPage:PageSpecial_Winter2017_Weapons_Crystal",
"attributes": {
"sectionStates": [],
"state": "Active"
},
"quantity": 1
},
"CollectionBookPage:PageSpecial_RatRod_Weapons": {
"templateId": "CollectionBookPage:PageSpecial_RatRod_Weapons",
"attributes": {
"sectionStates": [],
"state": "Active"
},
"quantity": 1
},
"CollectionBookPage:PageSpecial_RatRod_Weapons_Crystal": {
"templateId": "CollectionBookPage:PageSpecial_RatRod_Weapons_Crystal",
"attributes": {
"sectionStates": [],
"state": "Active"
},
"quantity": 1
},
"CollectionBookPage:PageSpecial_Weapons_Ranged_Winter2017": {
"templateId": "CollectionBookPage:PageSpecial_Weapons_Ranged_Winter2017",
"attributes": {
"sectionStates": [],
"state": "Active"
},
"quantity": 1
},
"CollectionBookPage:PageSpecial_Weapons_Ranged_Winter2017_Crystal": {
"templateId": "CollectionBookPage:PageSpecial_Weapons_Ranged_Winter2017_Crystal",
"attributes": {
"sectionStates": [],
"state": "Active"
},
"quantity": 1
},
"CollectionBookPage:PageSpecial_Weapons_Melee_Winter2017": {
"templateId": "CollectionBookPage:PageSpecial_Weapons_Melee_Winter2017",
"attributes": {
"sectionStates": [],
"state": "Active"
},
"quantity": 1
},
"CollectionBookPage:PageSpecial_Weapons_Melee_Winter2017_Crystal": {
"templateId": "CollectionBookPage:PageSpecial_Weapons_Melee_Winter2017_Crystal",
"attributes": {
"sectionStates": [],
"state": "Active"
},
"quantity": 1
},
"CollectionBookPage:PageSpecial_Weapons_ChineseNewYear2018": {
"templateId": "CollectionBookPage:PageSpecial_Weapons_ChineseNewYear2018",
"attributes": {
"sectionStates": [],
"state": "Active"
},
"quantity": 1
},
"CollectionBookPage:PageSpecial_Weapons_Crystal_ChineseNewYear2018": {
"templateId": "CollectionBookPage:PageSpecial_Weapons_Crystal_ChineseNewYear2018",
"attributes": {
"sectionStates": [],
"state": "Active"
},
"quantity": 1
},
"CollectionBookPage:PageSpecial_StormZoneCyber_Ranged": {
"templateId": "CollectionBookPage:PageSpecial_StormZoneCyber_Ranged",
"attributes": {
"sectionStates": [],
"state": "Active"
},
"quantity": 1
},
"CollectionBookPage:PageSpecial_StormZoneCyber_Melee": {
"templateId": "CollectionBookPage:PageSpecial_StormZoneCyber_Melee",
"attributes": {
"sectionStates": [],
"state": "Active"
},
"quantity": 1
},
"CollectionBookPage:PageSpecial_StormZoneCyber_Ranged_Crystal": {
"templateId": "CollectionBookPage:PageSpecial_StormZoneCyber_Ranged_Crystal",
"attributes": {
"sectionStates": [],
"state": "Active"
},
"quantity": 1
},
"CollectionBookPage:PageSpecial_StormZoneCyber_Melee_Crystal": {
"templateId": "CollectionBookPage:PageSpecial_StormZoneCyber_Melee_Crystal",
"attributes": {
"sectionStates": [],
"state": "Active"
},
"quantity": 1
},
"CollectionBookPage:PageSpecial_Blockbuster2018_Ranged": {
"templateId": "CollectionBookPage:PageSpecial_Blockbuster2018_Ranged",
"attributes": {
"sectionStates": [],
"state": "Active"
},
"quantity": 1
},
"CollectionBookPage:PageSpecial_Blockbuster2018_Ranged_Crystal": {
"templateId": "CollectionBookPage:PageSpecial_Blockbuster2018_Ranged_Crystal",
"attributes": {
"sectionStates": [],
"state": "Active"
},
"quantity": 1
},
"CollectionBookPage:PageSpecial_RoadTrip2018_Weapons": {
"templateId": "CollectionBookPage:PageSpecial_RoadTrip2018_Weapons",
"attributes": {
"sectionStates": [],
"state": "Active"
},
"quantity": 1
},
"CollectionBookPage:PageSpecial_RoadTrip2018_Weapons_Crystal": {
"templateId": "CollectionBookPage:PageSpecial_RoadTrip2018_Weapons_Crystal",
"attributes": {
"sectionStates": [],
"state": "Active"
},
"quantity": 1
},
"CollectionBookPage:PageSpecial_Weapons_Ranged_StormZone2": {
"templateId": "CollectionBookPage:PageSpecial_Weapons_Ranged_StormZone2",
"attributes": {
"sectionStates": [],
"state": "Active"
},
"quantity": 1
},
"CollectionBookPage:PageSpecial_Weapons_Ranged_StormZone2_Crystal": {
"templateId": "CollectionBookPage:PageSpecial_Weapons_Ranged_StormZone2_Crystal",
"attributes": {
"sectionStates": [],
"state": "Active"
},
"quantity": 1
},
"CollectionBookPage:PageSpecial_Weapons_Melee_StormZone2": {
"templateId": "CollectionBookPage:PageSpecial_Weapons_Melee_StormZone2",
"attributes": {
"sectionStates": [],
"state": "Active"
},
"quantity": 1
},
"CollectionBookPage:PageSpecial_Weapons_Melee_StormZone2_Crystal": {
"templateId": "CollectionBookPage:PageSpecial_Weapons_Melee_StormZone2_Crystal",
"attributes": {
"sectionStates": [],
"state": "Active"
},
"quantity": 1
},
"CollectionBookPage:PageSpecial_Hydraulic": {
"templateId": "CollectionBookPage:PageSpecial_Hydraulic",
"attributes": {
"sectionStates": [],
"state": "Active"
},
"quantity": 1
},
"CollectionBookPage:PageSpecial_Hydraulic_Crystal": {
"templateId": "CollectionBookPage:PageSpecial_Hydraulic_Crystal",
"attributes": {
"sectionStates": [],
"state": "Active"
},
"quantity": 1
},
"CollectionBookPage:PageSpecial_Scavenger": {
"templateId": "CollectionBookPage:PageSpecial_Scavenger",
"attributes": {
"sectionStates": [],
"state": "Active"
},
"quantity": 1
},
"CollectionBookPage:PageSpecial_Scavenger_Crystal": {
"templateId": "CollectionBookPage:PageSpecial_Scavenger_Crystal",
"attributes": {
"sectionStates": [],
"state": "Active"
},
"quantity": 1
},
"CollectionBookPage:test_TestPage": {
"templateId": "CollectionBookPage:test_TestPage",
"attributes": {
"sectionStates": [],
"state": "Active"
},
"quantity": 1
}
},
"stats": {
"attributes": {
"inventory_limit_bonus": 0
}
},
"commandRevision": 0
}

View File

@@ -1,15 +0,0 @@
{
"_id": "LawinServer",
"created": "0001-01-01T00:00:00.000Z",
"updated": "0001-01-01T00:00:00.000Z",
"rvn": 0,
"wipeNumber": 1,
"accountId": "LawinServer",
"profileId": "collections",
"version": "no_version",
"items": {},
"stats": {
"attributes": {}
},
"commandRevision": 0
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,18 +0,0 @@
{
"created": "0001-01-01T00:00:00.000Z",
"updated": "0001-01-01T00:00:00.000Z",
"rvn": 0,
"wipeNumber": 1,
"accountId": "LawinServer",
"profileId": "common_public",
"version": "no_version",
"items": {},
"stats": {
"attributes": {
"banner_color": "DefaultColor15",
"homebase_name": "",
"banner_icon": "SurvivalBannerStonewoodComplete"
}
},
"commandRevision": 0
}

View File

@@ -1,15 +0,0 @@
{
"_id": "LawinServer",
"created": "0001-01-01T00:00:00.000Z",
"updated": "0001-01-01T00:00:00.000Z",
"rvn": 0,
"wipeNumber": 1,
"accountId": "LawinServer",
"profileId": "creative",
"version": "no_version",
"items": {},
"stats": {
"attributes": {}
},
"commandRevision": 0
}

View File

@@ -1,231 +0,0 @@
{
"_id": "LawinServer",
"created": "0001-01-01T00:00:00.000Z",
"updated": "0001-01-01T00:00:00.000Z",
"rvn": 0,
"wipeNumber": 1,
"accountId": "LawinServer",
"profileId": "metadata",
"version": "no_version",
"items": {
"Outpost:outpostcore_pve_03": {
"templateId": "Outpost:outpostcore_pve_03",
"attributes": {
"cloud_save_info": {
"saveCount": 319,
"savedRecords": [
{
"recordIndex": 0,
"archiveNumber": 1,
"recordFilename": "eb192023-7db8-4bc0-b3e4-bf060c7baf87_r0_a1.sav"
}
]
},
"level": 10,
"outpost_core_info": {
"placedBuildings": [
{
"buildingTag": "Outpost.BuildingActor.Building.00",
"placedTag": "Outpost.PlacementActor.Placement.01"
},
{
"buildingTag": "Outpost.BuildingActor.Building.01",
"placedTag": "Outpost.PlacementActor.Placement.00"
},
{
"buildingTag": "Outpost.BuildingActor.Building.02",
"placedTag": "Outpost.PlacementActor.Placement.05"
},
{
"buildingTag": "Outpost.BuildingActor.Building.03",
"placedTag": "Outpost.PlacementActor.Placement.02"
}
],
"accountsWithEditPermission": [],
"highestEnduranceWaveReached": 30
}
},
"quantity": 1
},
"Outpost:outpostcore_pve_02": {
"templateId": "Outpost:outpostcore_pve_02",
"attributes": {
"cloud_save_info": {
"saveCount": 603,
"savedRecords": [
{
"recordIndex": 0,
"archiveNumber": 0,
"recordFilename": "76fe0295-aee2-463a-9229-d9933b4969b8_r0_a0.sav"
}
]
},
"level": 10,
"outpost_core_info": {
"placedBuildings": [
{
"buildingTag": "Outpost.BuildingActor.Building.00",
"placedTag": "Outpost.PlacementActor.Placement.00"
},
{
"buildingTag": "Outpost.BuildingActor.Building.01",
"placedTag": "Outpost.PlacementActor.Placement.01"
},
{
"buildingTag": "Outpost.BuildingActor.Building.02",
"placedTag": "Outpost.PlacementActor.Placement.04"
},
{
"buildingTag": "Outpost.BuildingActor.Building.03",
"placedTag": "Outpost.PlacementActor.Placement.03"
},
{
"buildingTag": "Outpost.BuildingActor.Building.04",
"placedTag": "Outpost.PlacementActor.Placement.02"
}
],
"accountsWithEditPermission": [],
"highestEnduranceWaveReached": 30
}
},
"quantity": 1
},
"Outpost:outpostcore_pve_04": {
"templateId": "Outpost:outpostcore_pve_04",
"attributes": {
"cloud_save_info": {
"saveCount": 77,
"savedRecords": [
{
"recordIndex": 0,
"archiveNumber": 1,
"recordFilename": "940037e4-87d2-499e-8d00-cdb2dfa326b9_r0_a1.sav"
}
]
},
"level": 10,
"outpost_core_info": {
"placedBuildings": [
{
"buildingTag": "Outpost.BuildingActor.Building.00",
"placedTag": "Outpost.PlacementActor.Placement.00"
},
{
"buildingTag": "Outpost.BuildingActor.Building.01",
"placedTag": "Outpost.PlacementActor.Placement.01"
},
{
"buildingTag": "Outpost.BuildingActor.Building.02",
"placedTag": "Outpost.PlacementActor.Placement.03"
},
{
"buildingTag": "Outpost.BuildingActor.Building.03",
"placedTag": "Outpost.PlacementActor.Placement.05"
}
],
"accountsWithEditPermission": [],
"highestEnduranceWaveReached": 30
}
},
"quantity": 1
},
"Outpost:outpostcore_pve_01": {
"templateId": "Outpost:outpostcore_pve_01",
"attributes": {
"cloud_save_info": {
"saveCount": 851,
"savedRecords": [
{
"recordIndex": 0,
"archiveNumber": 0,
"recordFilename": "a1d68ce6-63a5-499a-946f-9e0c825572d7_r0_a0.sav"
}
]
},
"level": 10,
"outpost_core_info": {
"placedBuildings": [
{
"buildingTag": "Outpost.BuildingActor.Building.00",
"placedTag": "Outpost.PlacementActor.Placement.00"
},
{
"buildingTag": "Outpost.BuildingActor.Building.01",
"placedTag": "Outpost.PlacementActor.Placement.02"
},
{
"buildingTag": "Outpost.BuildingActor.Building.02",
"placedTag": "Outpost.PlacementActor.Placement.01"
},
{
"buildingTag": "Outpost.BuildingActor.Building.03",
"placedTag": "Outpost.PlacementActor.Placement.05"
}
],
"accountsWithEditPermission": [],
"highestEnduranceWaveReached": 30
}
},
"quantity": 1
},
"DeployableBaseCloudSave:testdeployablebaseitemdef": {
"templateId": "DeployableBaseCloudSave:testdeployablebaseitemdef",
"attributes": {
"tier_progression": {
"progressionInfo": [
{
"progressionLayoutGuid": "B70B5C69-437E-75C5-CB91-7E913F3B5294",
"highestDefeatedTier": 0
},
{
"progressionLayoutGuid": "04FD086F-4A99-823B-06C3-979A8F408960",
"highestDefeatedTier": 4
},
{
"progressionLayoutGuid": "D3D31F40-45D8-FD77-67E6-5FBAB0550417",
"highestDefeatedTier": 1
},
{
"progressionLayoutGuid": "92A17A43-4EDC-8F69-688F-24BB3A3D8AEF",
"highestDefeatedTier": 3
},
{
"progressionLayoutGuid": "A2D8DB3E-457E-279B-58F5-AA9BA2FDC547",
"highestDefeatedTier": 4
},
{
"progressionLayoutGuid": "5AAB9A15-49F5-0D74-0B22-BB9686396E8F",
"highestDefeatedTier": 1
},
{
"progressionLayoutGuid": "9077163A-4664-1993-5A20-D28170404FD6",
"highestDefeatedTier": 3
},
{
"progressionLayoutGuid": "FB679125-49BC-0025-48F3-22A1B8085189",
"highestDefeatedTier": 4
}
]
},
"cloud_save_info": {
"saveCount": 11,
"savedRecords": [
{
"recordIndex": 0,
"archiveNumber": 1,
"recordFilename": "2FA8CFBB-4973-CCF0-EEA8-BEBC37D99F52_r0_a1.sav"
}
]
},
"level": 0
},
"quantity": 1
}
},
"stats": {
"attributes": {
"inventory_limit_bonus": 0
}
},
"commandRevision": 0
}

View File

@@ -1,17 +0,0 @@
{
"_id": "LawinServer",
"created": "0001-01-01T00:00:00.000Z",
"updated": "0001-01-01T00:00:00.000Z",
"rvn": 0,
"wipeNumber": 1,
"accountId": "LawinServer",
"profileId": "outpost0",
"version": "no_version",
"items": {},
"stats": {
"attributes": {
"inventory_limit_bonus": 0
}
},
"commandRevision": 0
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,694 +0,0 @@
{
"_id": "LawinServer",
"created": "0001-01-01T00:00:00.000Z",
"updated": "0001-01-01T00:00:00.000Z",
"rvn": 0,
"wipeNumber": 1,
"accountId": "LawinServer",
"profileId": "theater0",
"version": "no_version",
"items": {
"3d81f6f3-1290-326e-dfee-e577af2e9fbb": {
"templateId": "Ingredient:ingredient_blastpowder",
"attributes": {
"loadedAmmo": 0,
"inventory_overflow_date": false,
"level": 0,
"alterationDefinitions": [],
"durability": 1,
"itemSource": ""
},
"quantity": 999
},
"70ff3716-d732-c472-b1d8-0a20d48dd607": {
"templateId": "Ingredient:ingredient_ore_silver",
"attributes": {
"loadedAmmo": 0,
"inventory_overflow_date": false,
"level": 0,
"alterationDefinitions": [],
"durability": 1,
"itemSource": ""
},
"quantity": 999
},
"48059439-88b0-a779-daae-36d9495f079e": {
"templateId": "Ingredient:ingredient_ore_alloy",
"attributes": {
"loadedAmmo": 0,
"inventory_overflow_date": false,
"level": 0,
"alterationDefinitions": [],
"durability": 1,
"itemSource": ""
},
"quantity": 999
},
"7dd4a423-0b6f-3abb-757c-88077adcaacc": {
"templateId": "Ingredient:ingredient_crystal_sunbeam",
"attributes": {
"loadedAmmo": 0,
"inventory_overflow_date": false,
"level": 0,
"alterationDefinitions": [],
"durability": 1,
"itemSource": ""
},
"quantity": 999
},
"694a4c8d-67b6-f903-85bf-f33d4e7a6859": {
"templateId": "Ingredient:ingredient_ore_obsidian",
"attributes": {
"loadedAmmo": 0,
"inventory_overflow_date": false,
"level": 0,
"alterationDefinitions": [],
"durability": 1,
"itemSource": ""
},
"quantity": 999
},
"97feb4c9-2290-fd4b-c356-7f346ba67e39": {
"templateId": "Ingredient:ingredient_mechanical_parts_t05",
"attributes": {
"loadedAmmo": 0,
"inventory_overflow_date": false,
"level": 0,
"alterationDefinitions": [],
"durability": 1,
"itemSource": ""
},
"quantity": 999
},
"25ff4168-8eb9-a5cc-4900-d06cdb8004ab": {
"templateId": "Ingredient:ingredient_mechanical_parts_t02",
"attributes": {
"loadedAmmo": 0,
"inventory_overflow_date": false,
"level": 0,
"alterationDefinitions": [],
"durability": 1,
"itemSource": ""
},
"quantity": 999
},
"a63022b8-6467-a347-7c11-37d483d45d08": {
"templateId": "Ingredient:ingredient_rare_powercell",
"attributes": {
"loadedAmmo": 0,
"inventory_overflow_date": false,
"level": 0,
"alterationDefinitions": [],
"durability": 1,
"itemSource": ""
},
"quantity": 999
},
"7e0d23d2-24dc-9579-4f32-507758107bd3": {
"templateId": "Ingredient:ingredient_resin",
"attributes": {
"loadedAmmo": 0,
"inventory_overflow_date": false,
"level": 0,
"alterationDefinitions": [],
"durability": 1,
"itemSource": ""
},
"quantity": 999
},
"d44ad9ed-a5d3-0642-a865-083061aeb4e6": {
"templateId": "Ingredient:ingredient_powder_t05",
"attributes": {
"loadedAmmo": 0,
"inventory_overflow_date": false,
"level": 0,
"alterationDefinitions": [],
"durability": 1,
"itemSource": ""
},
"quantity": 999
},
"2011030e-dbce-a02b-c086-ec8a99f16aeb": {
"templateId": "Ingredient:ingredient_crystal_quartz",
"attributes": {
"loadedAmmo": 0,
"inventory_overflow_date": false,
"level": 0,
"alterationDefinitions": [],
"durability": 1,
"itemSource": ""
},
"quantity": 999
},
"7d43d569-e170-bd46-dfb2-92828ff0c98d": {
"templateId": "Ingredient:ingredient_rare_mechanism",
"attributes": {
"loadedAmmo": 0,
"inventory_overflow_date": false,
"level": 0,
"alterationDefinitions": [],
"durability": 1,
"itemSource": ""
},
"quantity": 999
},
"1db23fbf-1ab5-72d9-2b20-50eacebda6d5": {
"templateId": "Ingredient:ingredient_nuts_bolts",
"attributes": {
"loadedAmmo": 0,
"inventory_overflow_date": false,
"level": 0,
"alterationDefinitions": [],
"durability": 0,
"itemSource": ""
},
"quantity": 999
},
"024aa359-e313-168a-3738-31ae4f04cfb2": {
"templateId": "Ingredient:ingredient_ore_brightcore",
"attributes": {
"loadedAmmo": 0,
"inventory_overflow_date": false,
"level": 0,
"alterationDefinitions": [],
"durability": 1,
"itemSource": ""
},
"quantity": 999
},
"da7680a3-072e-3e3f-3ed6-bf71159f6df0": {
"templateId": "Ingredient:ingredient_planks",
"attributes": {
"loadedAmmo": 0,
"inventory_overflow_date": false,
"level": 0,
"alterationDefinitions": [],
"durability": 1,
"itemSource": ""
},
"quantity": 999
},
"10aec620-f4b9-aadd-da3e-5d4a8f87225b": {
"templateId": "Ingredient:ingredient_ore_malachite",
"attributes": {
"loadedAmmo": 0,
"inventory_overflow_date": false,
"level": 0,
"alterationDefinitions": [],
"durability": 1,
"itemSource": ""
},
"quantity": 999
},
"161217ac-5b39-a93e-a1d8-7f7667646624": {
"templateId": "Ingredient:ingredient_crystal_shadowshard",
"attributes": {
"loadedAmmo": 0,
"inventory_overflow_date": false,
"level": 0,
"alterationDefinitions": [],
"durability": 1,
"itemSource": ""
},
"quantity": 999
},
"f33663f5-bf16-9315-8df2-91800944b3e8": {
"templateId": "Ingredient:ingredient_twine_t05",
"attributes": {
"loadedAmmo": 0,
"inventory_overflow_date": false,
"level": 0,
"alterationDefinitions": [],
"durability": 1,
"itemSource": ""
},
"quantity": 999
},
"aa4a3cce-9bb5-50ef-4c59-f959e89c3992": {
"templateId": "Ingredient:ingredient_twine_t01",
"attributes": {
"loadedAmmo": 0,
"inventory_overflow_date": false,
"level": 0,
"alterationDefinitions": [],
"durability": 1,
"itemSource": ""
},
"quantity": 999
},
"e9eca1e1-7665-1315-de97-6583394e0af1": {
"templateId": "Ingredient:ingredient_batteries",
"attributes": {
"loadedAmmo": 0,
"inventory_overflow_date": false,
"level": 0,
"alterationDefinitions": [],
"durability": 1,
"itemSource": ""
},
"quantity": 999
},
"d1fd9cb3-0d51-6d7c-e937-9b07406ba42e": {
"templateId": "Ingredient:ingredient_twine_t04",
"attributes": {
"loadedAmmo": 0,
"inventory_overflow_date": false,
"level": 0,
"alterationDefinitions": [],
"durability": 1,
"itemSource": ""
},
"quantity": 999
},
"7d8f824d-cec2-01d5-0efc-c30073402de2": {
"templateId": "Ingredient:ingredient_herbs",
"attributes": {
"loadedAmmo": 0,
"inventory_overflow_date": false,
"level": 0,
"alterationDefinitions": [],
"durability": 1,
"itemSource": ""
},
"quantity": 999
},
"bdb45648-18f6-fdc6-8252-b717043f0021": {
"templateId": "Ingredient:ingredient_mechanical_parts_t04",
"attributes": {
"loadedAmmo": 0,
"inventory_overflow_date": false,
"level": 0,
"alterationDefinitions": [],
"durability": 1,
"itemSource": ""
},
"quantity": 999
},
"ddc2382e-faf9-14dd-c721-c659660540a8": {
"templateId": "Ingredient:ingredient_ore_copper",
"attributes": {
"loadedAmmo": 0,
"inventory_overflow_date": false,
"level": 0,
"alterationDefinitions": [],
"durability": 1,
"itemSource": ""
},
"quantity": 999
},
"75582a66-bc3e-958a-1943-79a56150d0bb": {
"templateId": "Ingredient:ingredient_powder_t03",
"attributes": {
"loadedAmmo": 0,
"inventory_overflow_date": false,
"level": 0,
"alterationDefinitions": [],
"durability": 1,
"itemSource": ""
},
"quantity": 999
},
"b4616de1-caf4-3652-a613-edb932df71e0": {
"templateId": "Ingredient:ingredient_mechanical_parts_t01",
"attributes": {
"loadedAmmo": 0,
"inventory_overflow_date": false,
"level": 0,
"alterationDefinitions": [],
"durability": 1,
"itemSource": ""
},
"quantity": 999
},
"bdc338a8-3667-e7e4-280d-5d4e4255b3f1": {
"templateId": "Ingredient:ingredient_twine_t02",
"attributes": {
"loadedAmmo": 0,
"inventory_overflow_date": false,
"level": 0,
"alterationDefinitions": [],
"durability": 1,
"itemSource": ""
},
"quantity": 999
},
"d17582f7-eb63-a4a6-cd4d-ff3d68e69757": {
"templateId": "Ingredient:ingredient_powder_t01",
"attributes": {
"loadedAmmo": 0,
"inventory_overflow_date": false,
"level": 0,
"alterationDefinitions": [],
"durability": 1,
"itemSource": ""
},
"quantity": 999
},
"25e43cee-7dc7-348b-40bc-20b8850468ba": {
"templateId": "Ingredient:ingredient_duct_tape",
"attributes": {
"loadedAmmo": 0,
"inventory_overflow_date": false,
"level": 0,
"alterationDefinitions": [],
"durability": 1,
"itemSource": ""
},
"quantity": 999
},
"720bc675-44e2-ff74-6e5c-eec23b493bd1": {
"templateId": "Ingredient:ingredient_twine_t03",
"attributes": {
"loadedAmmo": 0,
"inventory_overflow_date": false,
"level": 0,
"alterationDefinitions": [],
"durability": 1,
"itemSource": ""
},
"quantity": 999
},
"0e2094f2-9c35-9e51-58ea-a87ec89fa758": {
"templateId": "Ingredient:ingredient_powder_t02",
"attributes": {
"loadedAmmo": 0,
"inventory_overflow_date": false,
"level": 0,
"alterationDefinitions": [],
"durability": 1,
"itemSource": ""
},
"quantity": 999
},
"1cf850a0-1797-4fe8-dd94-34152756c80b": {
"templateId": "Ingredient:ingredient_bacon",
"attributes": {
"loadedAmmo": 0,
"inventory_overflow_date": false,
"level": 0,
"alterationDefinitions": [],
"durability": 0,
"itemSource": ""
},
"quantity": 999
},
"7fe47331-1cbd-4606-c12e-6df2c1dc13a3": {
"templateId": "Ingredient:ingredient_mechanical_parts_t03",
"attributes": {
"loadedAmmo": 0,
"inventory_overflow_date": false,
"level": 0,
"alterationDefinitions": [],
"durability": 1,
"itemSource": ""
},
"quantity": 999
},
"42d429fc-a4cf-974d-2bce-17c6b872c96e": {
"templateId": "Ingredient:ingredient_ore_coal",
"attributes": {
"loadedAmmo": 0,
"inventory_overflow_date": false,
"level": 0,
"alterationDefinitions": [],
"durability": 1,
"itemSource": ""
},
"quantity": 999
},
"124d77cc-8cd4-fdcc-efe1-c18ee63587eb": {
"templateId": "Ingredient:ingredient_flowers",
"attributes": {
"loadedAmmo": 0,
"inventory_overflow_date": false,
"level": 0,
"alterationDefinitions": [],
"durability": 1,
"itemSource": ""
},
"quantity": 999
},
"ea2a6495-4b9e-59df-0163-5e5e8f52467e": {
"templateId": "Ingredient:ingredient_powder_t04",
"attributes": {
"loadedAmmo": 0,
"inventory_overflow_date": false,
"level": 0,
"alterationDefinitions": [],
"durability": 1,
"itemSource": ""
},
"quantity": 999
},
"6291ab77-ec9b-1b35-ccb0-063519415f6d": {
"templateId": "WorldItem:wooditemdata",
"attributes": {
"loadedAmmo": 0,
"inventory_overflow_date": false,
"level": 0,
"alterationDefinitions": [],
"durability": 1,
"itemSource": ""
},
"quantity": 999
},
"2d7953c0-752f-c2a7-ebef-90b45cb30b5b": {
"templateId": "WorldItem:stoneitemdata",
"attributes": {
"loadedAmmo": 0,
"inventory_overflow_date": false,
"level": 0,
"alterationDefinitions": [],
"durability": 1,
"itemSource": ""
},
"quantity": 999
},
"06f471d5-046b-50f6-3f07-9aa670b6fecb": {
"templateId": "WorldItem:metalitemdata",
"attributes": {
"loadedAmmo": 0,
"inventory_overflow_date": false,
"level": 0,
"alterationDefinitions": [],
"durability": 1,
"itemSource": ""
},
"quantity": 999
},
"003e7a8c-92eb-13c1-6b0e-aafad8f3d81d": {
"templateId": "Weapon:edittool",
"attributes": {
"clipSizeScale": 0,
"loadedAmmo": 0,
"inventory_overflow_date": false,
"level": 0,
"alterationDefinitions": [],
"baseClipSize": 0,
"durability": 1,
"itemSource": ""
},
"quantity": 1
},
"bcb13e35-c030-cf2a-a003-16377320beda": {
"templateId": "Weapon:buildingitemdata_wall",
"attributes": {
"clipSizeScale": 0,
"loadedAmmo": 0,
"inventory_overflow_date": false,
"level": 0,
"alterationDefinitions": [],
"baseClipSize": 0,
"durability": 1,
"itemSource": ""
},
"quantity": 1
},
"97ba026b-a36c-6827-e9d7-21bc6a1f9c53": {
"templateId": "Weapon:buildingitemdata_floor",
"attributes": {
"clipSizeScale": 0,
"loadedAmmo": 0,
"inventory_overflow_date": false,
"level": 0,
"alterationDefinitions": [],
"baseClipSize": 0,
"durability": 1,
"itemSource": ""
},
"quantity": 1
},
"15c02d16-11f6-ffd1-e8bb-4b5d56bd5bd9": {
"templateId": "Weapon:buildingitemdata_stair_w",
"attributes": {
"clipSizeScale": 0,
"loadedAmmo": 0,
"inventory_overflow_date": false,
"level": 0,
"alterationDefinitions": [],
"baseClipSize": 0,
"durability": 1,
"itemSource": ""
},
"quantity": 1
},
"f603c8af-e326-202e-3b12-b5fd2517e5c2": {
"templateId": "Weapon:buildingitemdata_roofs",
"attributes": {
"clipSizeScale": 0,
"loadedAmmo": 0,
"inventory_overflow_date": false,
"level": 0,
"alterationDefinitions": [],
"baseClipSize": 0,
"durability": 1,
"itemSource": ""
},
"quantity": 1
},
"baa4f86c-708c-7689-0859-fbfdb1bc623a": {
"templateId": "Ammo:ammodatabulletsmedium",
"attributes": {
"loadedAmmo": 0,
"inventory_overflow_date": false,
"level": 0,
"alterationDefinitions": [],
"durability": 1,
"itemSource": ""
},
"quantity": 999
},
"240894a2-f99a-214d-ce50-2dac39394699": {
"templateId": "Ammo:ammodatashells",
"attributes": {
"loadedAmmo": 0,
"inventory_overflow_date": false,
"level": 0,
"alterationDefinitions": [],
"durability": 1,
"itemSource": "None"
},
"quantity": 999
},
"1a0c69f4-2c23-a5c9-34a0-f48c45637171": {
"templateId": "Ammo:ammodataenergycell",
"attributes": {
"loadedAmmo": 0,
"inventory_overflow_date": false,
"level": 0,
"alterationDefinitions": [],
"durability": 1,
"itemSource": ""
},
"quantity": 999
},
"a92b1e7e-5812-0bfd-0107-f7b97ed166fa": {
"templateId": "Ammo:ammodatabulletsheavy",
"attributes": {
"loadedAmmo": 0,
"inventory_overflow_date": false,
"level": 0,
"alterationDefinitions": [],
"durability": 1,
"itemSource": "None"
},
"quantity": 999
},
"7516967c-e831-4c3a-1a24-03834756f532": {
"templateId": "Ammo:ammodatabulletslight",
"attributes": {
"loadedAmmo": 0,
"inventory_overflow_date": false,
"level": 0,
"alterationDefinitions": [],
"durability": 1,
"itemSource": "None"
},
"quantity": 999
},
"23220ed0-29d4-3da1-6e0e-6df46a19752d": {
"templateId": "Ammo:ammodataexplosive",
"attributes": {
"loadedAmmo": 0,
"inventory_overflow_date": false,
"level": 0,
"alterationDefinitions": [],
"durability": 1,
"itemSource": "None"
},
"quantity": 999
}
},
"stats": {
"attributes": {
"player_loadout": {
"bPlayerIsNew": false,
"pinnedSchematicInstances": [],
"primaryQuickBarRecord": {
"slots": [
{
"items": []
},
{
"items": []
},
{
"items": []
},
{
"items": []
},
{
"items": []
},
{
"items": []
},
{
"items": []
},
{
"items": []
},
{
"items": []
}
]
},
"secondaryQuickBarRecord": {
"slots": [
{
"items": []
},
{
"items": []
},
{
"items": []
},
{
"items": []
},
{
"items": []
},
{
"items": []
},
{
"items": []
}
]
},
"zonesCompleted": 0
},
"theater_unique_id": "",
"past_lifetime_zones_completed": 0,
"last_event_instance_key": "",
"last_zones_completed": 0,
"inventory_limit_bonus": 0
}
},
"profileLockExpiration": "0001-01-01T00:00:00.000Z",
"commandRevision": 0
}

View File

@@ -1,459 +0,0 @@
[
"Schematic:SID_Wall_Wood_Spikes_vR_T01",
"Hero:HID_Commando_Sony_R_T01",
"Schematic:SID_Wall_Wood_Spikes_UC_T01",
"Hero:HID_Commando_ShockDamage_VR_T01",
"Schematic:SID_Wall_Wood_Spikes_SR_T01",
"Hero:HID_Commando_ShockDamage_SR_T01",
"Schematic:SID_Wall_Wood_Spikes_R_T01",
"Hero:HID_Commando_ShockDamage_R_T01",
"Schematic:SID_Wall_Wood_Spikes_C_T01",
"Hero:HID_Commando_GunTough_VR_T01",
"Schematic:SID_Wall_Light_VR_T01",
"Hero:HID_Commando_GunTough_UC_T01",
"Schematic:SID_Wall_Light_SR_T01",
"Hero:HID_Commando_GunTough_SR_T01",
"Schematic:SID_Wall_Light_R_T01",
"Hero:HID_Commando_GunTough_R_T01",
"Schematic:SID_Wall_Launcher_VR_T01",
"Hero:HID_Commando_GunHeadshotHW_SR_T01",
"Schematic:SID_Wall_Launcher_UC_T01",
"Hero:HID_Commando_GunHeadshot_VR_T01",
"Schematic:SID_Wall_Launcher_SR_T01",
"Hero:HID_Commando_GunHeadshot_SR_T01",
"Schematic:SID_Wall_Launcher_R_T01",
"Hero:HID_Commando_GrenadeMaster_SR_T01",
"Schematic:SID_Wall_Electric_VR_T01",
"Hero:HID_Commando_GrenadeGun_VR_T01",
"Schematic:SID_Wall_Electric_UC_T01",
"Hero:HID_Commando_GrenadeGun_SR_T01",
"Schematic:SID_Wall_Electric_SR_T01",
"Hero:HID_Commando_GrenadeGun_R_T01",
"Schematic:SID_Wall_Electric_R_T01",
"Hero:HID_Commando_GCGrenade_VR_T01",
"Schematic:SID_Wall_Darts_VR_T01",
"Hero:HID_Commando_GCGrenade_SR_T01",
"Schematic:SID_Wall_Darts_UC_T01",
"Hero:HID_Commando_GCGrenade_R_T01",
"Schematic:SID_Wall_Darts_SR_T01",
"Hero:HID_Commando_010_VR_T01",
"Schematic:SID_Wall_Darts_R_T01",
"Hero:HID_Commando_010_SR_T01",
"Schematic:SID_Floor_Ward_VR_T01",
"Hero:HID_Commando_009_VR_T01",
"Schematic:SID_Floor_Ward_UC_T01",
"Hero:HID_Commando_009_SR_T01",
"Schematic:SID_Floor_Ward_SR_T01",
"Hero:HID_Commando_009_R_T01",
"Schematic:SID_Floor_Ward_R_T01",
"Hero:HID_Commando_008_VR_T01",
"Schematic:SID_Floor_Spikes_Wood_VR_T01",
"Hero:HID_Commando_008_SR_T01",
"Schematic:SID_Floor_Spikes_Wood_UC_T01",
"Hero:HID_Commando_008_R_T01",
"Schematic:SID_Floor_Spikes_Wood_SR_T01",
"Hero:HID_Commando_008_FoundersM_SR_T01",
"Schematic:SID_Floor_Spikes_Wood_R_T01",
"Hero:HID_Commando_008_FoundersF_SR_T01",
"Schematic:SID_Floor_Spikes_Wood_C_T01",
"Hero:HID_Commando_007_VR_T01",
"Schematic:SID_Floor_Spikes_VR_T01",
"Hero:HID_Commando_007_UC_T01",
"Schematic:SID_Floor_Spikes_UC_T01",
"Hero:HID_Commando_007_SR_T01",
"Schematic:SID_Floor_Spikes_SR_T01",
"Hero:HID_Commando_007_R_T01",
"Schematic:SID_Floor_Spikes_R_T01",
"Hero:HID_Commando_GrenadeGun_UC_T01",
"Schematic:SID_Floor_Launcher_VR_T01",
"Hero:HID_Constructor_Sony_R_T01",
"Schematic:SID_Floor_Launcher_UC_T01",
"Hero:HID_Constructor_RushBASE_VR_T01",
"Schematic:SID_Floor_Launcher_SR_T01",
"Hero:HID_Constructor_RushBASE_UC_T01",
"Schematic:SID_Floor_Launcher_R_T01",
"Hero:HID_Constructor_RushBASE_SR_T01",
"Schematic:SID_Floor_Health_VR_T01",
"Hero:HID_Constructor_RushBASE_R_T01",
"Schematic:SID_Floor_Health_UC_T01",
"Hero:HID_Constructor_PlasmaDamage_VR_T01",
"Schematic:SID_Floor_Health_SR_T01",
"Hero:HID_Constructor_PlasmaDamage_SR_T01",
"Schematic:SID_Floor_Health_R_T01",
"Hero:HID_Constructor_PlasmaDamage_R_T01",
"Schematic:SID_Ceiling_Gas_VR_T01",
"Hero:HID_Constructor_HammerTank_VR_T01",
"Schematic:SID_Ceiling_Gas_UC_T01",
"Hero:HID_Constructor_HammerTank_UC_T01",
"Schematic:SID_Ceiling_Gas_SR_T01",
"Hero:HID_Constructor_HammerTank_SR_T01",
"Schematic:SID_Ceiling_Gas_R_T01",
"Hero:HID_Constructor_HammerTank_R_T01",
"Schematic:SID_Ceiling_Electric_Single_VR_T01",
"Hero:HID_Constructor_HammerPlasma_VR_T01",
"Schematic:SID_Ceiling_Electric_Single_UC_T01",
"Hero:HID_Constructor_HammerPlasma_SR_T01",
"Schematic:SID_Ceiling_Electric_Single_SR_T01",
"Hero:HID_Constructor_BaseHyperHW_SR_T01",
"Schematic:SID_Ceiling_Electric_Single_R_T01",
"Hero:HID_Constructor_BaseHyper_VR_T01",
"Schematic:SID_Ceiling_Electric_Single_C_T01",
"Hero:HID_Constructor_BaseHyper_SR_T01",
"Schematic:SID_Ceiling_Electric_AOE_VR_T01",
"Hero:HID_Constructor_BaseHyper_R_T01",
"Schematic:SID_Ceiling_Electric_AOE_SR_T01",
"Hero:HID_Constructor_BASEBig_SR_T01",
"Schematic:SID_Ceiling_Electric_AOE_R_T01",
"Hero:HID_Constructor_010_VR_T01",
"Schematic:SID_Sniper_TripleShot_VR_Ore_T01",
"Hero:HID_Constructor_010_SR_T01",
"Schematic:SID_Sniper_TripleShot_SR_Ore_T01",
"Hero:HID_Constructor_009_VR_T01",
"Schematic:SID_Sniper_Standard_Scope_VR_Ore_T01",
"Hero:HID_Constructor_009_SR_T01",
"Schematic:SID_Sniper_Standard_Scope_SR_Ore_T01",
"Hero:HID_Constructor_009_R_T01",
"Schematic:SID_Sniper_Standard_VR_Ore_T01",
"Hero:HID_Constructor_008_VR_T01",
"Schematic:SID_Sniper_Standard_SR_Ore_T01",
"Hero:HID_Constructor_008_SR_T01",
"Schematic:SID_Sniper_Standard_Founders_VR_Ore_T01",
"Hero:HID_Constructor_008_R_T01",
"Schematic:SID_Sniper_Standard_UC_Ore_T01",
"Hero:HID_Constructor_008_FoundersM_SR_T01",
"Schematic:SID_Sniper_Standard_R_Ore_T01",
"Hero:HID_Constructor_008_FoundersF_SR_T01",
"Schematic:SID_Sniper_Standard_C_Ore_T01",
"Hero:HID_Constructor_007_VR_T01",
"Schematic:SID_Sniper_Shredder_VR_Ore_T01",
"Hero:HID_Constructor_007_UC_T01",
"Schematic:SID_Sniper_Shredder_SR_Ore_T01",
"Hero:HID_Constructor_007_SR_T01",
"Schematic:SID_Sniper_Hydraulic_VR_Ore_T01",
"Hero:HID_Constructor_007_R_T01",
"Schematic:SID_Sniper_Hydraulic_SR_Ore_T01",
"Hero:HID_Ninja_Swordmaster_SR_T01",
"Schematic:SID_Sniper_BoltAction_Scope_VR_Ore_T01",
"Hero:HID_Ninja_StarsRainHW_SR_T01",
"Schematic:SID_Sniper_BoltAction_Scope_SR_Ore_T01",
"Hero:HID_Ninja_StarsRain_VR_T01",
"Schematic:SID_Sniper_BoltAction_Scope_R_Ore_T01",
"Hero:HID_Ninja_StarsRain_SR_T01",
"Schematic:SID_Sniper_BoltAction_UC_Ore_T01",
"Hero:HID_Ninja_StarsAssassin_VR_T01",
"Schematic:SID_Sniper_BoltAction_R_Ore_T01",
"Hero:HID_Ninja_StarsAssassin_UC_T01",
"Schematic:SID_Sniper_BoltAction_C_Ore_T01",
"Hero:HID_Ninja_StarsAssassin_SR_T01",
"Schematic:SID_Sniper_Auto_VR_Ore_T01",
"Hero:HID_Ninja_StarsAssassin_R_T01",
"Schematic:SID_Sniper_Auto_SR_Ore_T01",
"Hero:HID_Ninja_StarsAssassin_FoundersM_SR_T01",
"Schematic:SID_Sniper_Auto_Founders_VR_Ore_T01",
"Hero:HID_Ninja_StarsAssassin_FoundersF_SR_T01",
"Schematic:SID_Sniper_Auto_UC_Ore_T01",
"Hero:HID_Ninja_Sony_R_T01",
"Schematic:SID_Sniper_Auto_R_Ore_T01",
"Hero:HID_Ninja_SmokeDimMak_VR_T01",
"Schematic:SID_Sniper_AMR_VR_Ore_T01",
"Hero:HID_Ninja_SmokeDimMak_SR_T01",
"Schematic:SID_Sniper_AMR_SR_Ore_T01",
"Hero:HID_Ninja_SmokeDimMak_R_T01",
"Schematic:SID_Sniper_AMR_R_Ore_T01",
"Hero:HID_Ninja_SlashTail_VR_T01",
"Schematic:SID_Shotgun_Tactical_Precision_VR_Ore_T01",
"Hero:HID_Ninja_SlashTail_UC_T01",
"Schematic:SID_Shotgun_Tactical_Precision_SR_Ore_T01",
"Hero:HID_Ninja_SlashTail_SR_T01",
"Schematic:SID_Shotgun_Tactical_Precision_R_Ore_T01",
"Hero:HID_Ninja_SlashTail_R_T01",
"Schematic:SID_Shotgun_Tactical_UC_Ore_T01",
"Hero:HID_Ninja_SlashBreath_VR_T01",
"Schematic:SID_Shotgun_Tactical_R_Ore_T01",
"Hero:HID_Ninja_SlashBreath_SR_T01",
"Schematic:SID_Shotgun_Tactical_Founders_VR_Ore_T01",
"Hero:HID_Ninja_SlashBreath_R_T01",
"Schematic:SID_Shotgun_Tactical_Founders_SR_Ore_T01",
"Hero:HID_Ninja_010_VR_T01",
"Schematic:SID_Shotgun_Tactical_Founders_R_Ore_T01",
"Hero:HID_Ninja_010_SR_T01",
"Schematic:SID_Shotgun_Tactical_C_Ore_T01",
"Hero:HID_Ninja_009_VR_T01",
"Schematic:SID_Shotgun_Standard_VR_Ore_T01",
"Hero:HID_Ninja_009_SR_T01",
"Schematic:SID_Shotgun_Standard_SR_Ore_T01",
"Hero:HID_Ninja_009_R_T01",
"Schematic:SID_Shotgun_Standard_UC_Ore_T01",
"Hero:HID_Ninja_008_VR_T01",
"Schematic:SID_Shotgun_Standard_R_Ore_T01",
"Hero:HID_Ninja_008_SR_T01",
"Schematic:SID_Shotgun_Standard_C_Ore_T01",
"Hero:HID_Ninja_008_R_T01",
"Schematic:SID_Shotgun_SemiAuto_VR_Ore_T01",
"Hero:HID_Ninja_007_VR_T01",
"Schematic:SID_Shotgun_SemiAuto_UC_Ore_T01",
"Hero:HID_Ninja_007_UC_T01",
"Schematic:SID_Shotgun_SemiAuto_SR_Ore_T01",
"Hero:HID_Ninja_007_SR_T01",
"Schematic:SID_Shotgun_SemiAuto_R_Ore_T01",
"Hero:HID_Ninja_007_R_T01",
"Schematic:SID_Shotgun_Minigun_SR_Ore_T01",
"Hero:HID_Outlander_ZonePistolHW_SR_T01",
"Schematic:SID_Shotgun_Longarm_VR_Ore_T01",
"Hero:HID_Outlander_ZonePistol_VR_T01",
"Schematic:SID_Shotgun_Longarm_SR_Ore_T01",
"Hero:HID_Outlander_ZonePistol_SR_T01",
"Schematic:SID_Shotgun_Heavy_SR_Ore_T01",
"Hero:HID_Outlander_ZonePistol_R_T01",
"Schematic:SID_Shotgun_Break_OU_VR_Ore_T01",
"Hero:HID_Outlander_ZoneHarvest_VR_T01",
"Schematic:SID_Shotgun_Break_OU_SR_Ore_T01",
"Hero:HID_Outlander_ZoneHarvest_UC_T01",
"Schematic:SID_Shotgun_Break_OU_UC_Ore_T01",
"Hero:HID_Outlander_ZoneHarvest_SR_T01",
"Schematic:SID_Shotgun_Break_OU_R_Ore_T01",
"Hero:HID_Outlander_ZoneHarvest_R_T01",
"Schematic:SID_Shotgun_Break_VR_Ore_T01",
"Hero:HID_Outlander_ZoneFragment_SR_T01",
"Schematic:SID_Shotgun_Break_SR_Ore_T01",
"Hero:HID_Outlander_SphereFragment_VR_T01",
"Schematic:SID_Shotgun_Break_UC_Ore_T01",
"Hero:HID_Outlander_SphereFragment_SR_T01",
"Schematic:SID_Shotgun_Break_R_Ore_T01",
"Hero:HID_Outlander_SphereFragment_R_T01",
"Schematic:SID_Shotgun_Break_C_Ore_T01",
"Hero:HID_Outlander_Sony_R_T01",
"Schematic:SID_Shotgun_Auto_VR_Ore_T01",
"Hero:HID_Outlander_PunchPhase_VR_T01",
"Schematic:SID_Shotgun_Auto_SR_Ore_T01",
"Hero:HID_Outlander_PunchPhase_UC_T01",
"Schematic:SID_Shotgun_Auto_Founders_VR_Ore_T01",
"Hero:HID_Outlander_PunchPhase_SR_T01",
"Schematic:SID_Shotgun_Auto_UC_Ore_T01",
"Hero:HID_Outlander_PunchPhase_R_T01",
"Schematic:SID_Shotgun_Auto_R_Ore_T01",
"Hero:HID_Outlander_PunchDamage_VR_T01",
"Schematic:SID_Pistol_Zapper_VR_Ore_T01",
"Hero:HID_Outlander_PunchDamage_SR_T01",
"Schematic:SID_Pistol_Zapper_SR_Ore_T01",
"Hero:HID_Outlander_010_VR_T01",
"Schematic:SID_Pistol_Space_VR_Ore_T01",
"Hero:HID_Outlander_010_SR_T01",
"Schematic:SID_Pistol_Space_SR_Ore_T01",
"Hero:HID_Outlander_009_VR_T01",
"Schematic:SID_Pistol_SixShooter_UC_Ore_T01",
"Hero:HID_Outlander_009_SR_T01",
"Schematic:SID_Pistol_SixShooter_R_Ore_T01",
"Hero:HID_Outlander_009_R_T01",
"Schematic:SID_Pistol_SixShooter_C_Ore_T01",
"Hero:HID_Outlander_008_VR_T01",
"Schematic:SID_Pistol_SemiAuto_VR_Ore_T01",
"Hero:HID_Outlander_008_SR_T01",
"Schematic:SID_Pistol_SemiAuto_SR_Ore_T01",
"Hero:HID_Outlander_008_R_T01",
"Schematic:SID_Pistol_SemiAuto_Founders_VR_Ore_T01",
"Hero:HID_Outlander_008_FoundersM_SR_T01",
"Schematic:SID_Pistol_SemiAuto_UC_Ore_T01",
"Hero:HID_Outlander_008_FoundersF_SR_T01",
"Schematic:SID_Pistol_SemiAuto_R_Ore_T01",
"Hero:HID_Outlander_007_VR_T01",
"Schematic:SID_Pistol_SemiAuto_C_Ore_T01",
"Hero:HID_Outlander_007_UC_T01",
"Schematic:SID_Pistol_Rocket_SR_Ore_T01",
"Hero:HID_Outlander_007_SR_T01",
"Schematic:SID_Pistol_Rapid_VR_Ore_T01",
"Hero:HID_Outlander_007_R_T01",
"Schematic:SID_Pistol_Rapid_SR_Ore_T01",
"Defender:DID_DefenderSniper_Basic_VR_T01",
"Schematic:SID_Pistol_Rapid_R_Ore_T01",
"Defender:DID_DefenderSniper_Basic_UC_T01",
"Schematic:SID_Pistol_Rapid_Founders_VR_Ore_T01",
"Defender:DID_DefenderSniper_Basic_SR_T01",
"Schematic:SID_Pistol_Hydraulic_VR_Ore_T01",
"Defender:DID_DefenderSniper_Basic_R_T01",
"Schematic:SID_Pistol_Hydraulic_SR_Ore_T01",
"Defender:DID_DefenderSniper_Basic_C_T01",
"Schematic:SID_Pistol_Handcannon_Semi_VR_Ore_T01",
"Defender:DID_DefenderShotgun_Basic_VR_T01",
"Schematic:SID_Pistol_Handcannon_Semi_SR_Ore_T01",
"Defender:DID_DefenderShotgun_Basic_UC_T01",
"Schematic:SID_Pistol_Handcannon_Semi_R_Ore_T01",
"Defender:DID_DefenderShotgun_Basic_SR_T01",
"Schematic:SID_Pistol_Handcannon_VR_Ore_T01",
"Defender:DID_DefenderShotgun_Basic_R_T01",
"Schematic:SID_Pistol_Handcannon_SR_Ore_T01",
"Defender:DID_DefenderShotgun_Basic_C_T01",
"Schematic:SID_Pistol_Handcannon_R_Ore_T01",
"Defender:DID_DefenderPistol_Founders_VR_T01",
"Schematic:SID_Pistol_Handcannon_Founders_VR_Ore_T01",
"Defender:DID_DefenderPistol_Basic_VR_T01",
"Schematic:SID_Pistol_Gatling_VR_Ore_T01",
"Defender:DID_DefenderPistol_Basic_UC_T01",
"Schematic:SID_Pistol_Gatling_SR_Ore_T01",
"Defender:DID_DefenderPistol_Basic_SR_T01",
"Schematic:SID_Pistol_FireCracker_VR_Ore_T01",
"Defender:DID_DefenderPistol_Basic_R_T01",
"Schematic:SID_Pistol_FireCracker_SR_Ore_T01",
"Defender:DID_DefenderPistol_Basic_C_T01",
"Schematic:SID_Pistol_FireCracker_R_Ore_T01",
"Defender:DID_DefenderMelee_Basic_VR_T01",
"Schematic:SID_Pistol_Dragon_VR_Ore_T01",
"Defender:DID_DefenderMelee_Basic_UC_T01",
"Schematic:SID_Pistol_Dragon_SR_Ore_T01",
"Defender:DID_DefenderMelee_Basic_SR_T01",
"Schematic:SID_Pistol_BoltRevolver_UC_Ore_T01",
"Defender:DID_DefenderMelee_Basic_R_T01",
"Schematic:SID_Pistol_BoltRevolver_R_Ore_T01",
"Defender:DID_DefenderMelee_Basic_C_T01",
"Schematic:SID_Pistol_BoltRevolver_C_Ore_T01",
"Defender:DID_DefenderAssault_Founders_VR_T01",
"Schematic:SID_Pistol_Bolt_VR_Ore_T01",
"Defender:DID_DefenderAssault_Basic_VR_T01",
"Schematic:SID_Pistol_Bolt_SR_Ore_T01",
"Defender:DID_DefenderAssault_Basic_UC_T01",
"Schematic:SID_Pistol_AutoHeavy_VR_Ore_T01",
"Defender:DID_DefenderAssault_Basic_SR_T01",
"Schematic:SID_Pistol_AutoHeavy_SR_Ore_T01",
"Defender:DID_DefenderAssault_Basic_R_T01",
"Schematic:SID_Pistol_AutoHeavy_Founders_VR_Ore_T01",
"Defender:DID_DefenderAssault_Basic_C_T01",
"Schematic:SID_Pistol_AutoHeavy_Founders_SR_Ore_T01",
"Schematic:SID_Pistol_AutoHeavy_R_Ore_T01",
"Schematic:SID_Pistol_AutoHeavy_Founders_R_Ore_T01",
"Schematic:SID_Pistol_Auto_VR_Ore_T01",
"Schematic:SID_Pistol_Auto_SR_Ore_T01",
"Schematic:SID_Pistol_Auto_UC_Ore_T01",
"Schematic:SID_Pistol_Auto_R_Ore_T01",
"Schematic:SID_Pistol_Auto_C_Ore_T01",
"Schematic:SID_Launcher_Rocket_VR_Ore_T01",
"Schematic:SID_Launcher_Rocket_SR_Ore_T01",
"Schematic:SID_Launcher_Rocket_R_Ore_T01",
"Schematic:SID_Launcher_Pumpkin_RPG_SR_Ore_T01",
"Schematic:SID_Launcher_Hydraulic_VR_Ore_T01",
"Schematic:SID_Launcher_Hydraulic_SR_Ore_T01",
"Schematic:SID_Launcher_Grenade_VR_Ore_T01",
"Schematic:SID_Launcher_Grenade_SR_Ore_T01",
"Schematic:SID_Launcher_Grenade_R_Ore_T01",
"Schematic:SID_Assault_Surgical_VR_Ore_T01",
"Schematic:SID_Assault_Surgical_SR_Ore_T01",
"Schematic:SID_Assault_SingleShot_VR_Ore_T01",
"Schematic:SID_Assault_SingleShot_SR_Ore_T01",
"Schematic:SID_Assault_SingleShot_R_Ore_T01",
"Schematic:SID_Assault_SemiAuto_VR_Ore_T01",
"Schematic:SID_Assault_SemiAuto_SR_Ore_T01",
"Schematic:SID_Assault_SemiAuto_Founders_VR_Ore_T01",
"Schematic:SID_Assault_SemiAuto_UC_Ore_T01",
"Schematic:SID_Assault_SemiAuto_R_Ore_T01",
"Schematic:SID_Assault_SemiAuto_C_Ore_T01",
"Schematic:SID_Assault_Raygun_VR_Ore_T01",
"Schematic:SID_Assault_Raygun_SR_Ore_T01",
"Schematic:SID_Assault_Surgical_Drum_Founders_R_Ore_T01",
"Schematic:SID_Assault_LMG_VR_Ore_T01",
"Schematic:SID_Assault_LMG_SR_Ore_T01",
"Schematic:SID_Assault_LMG_R_Ore_T01",
"Schematic:SID_Assault_LMG_Drum_Founders_VR_Ore_T01",
"Schematic:SID_Assault_LMG_Drum_Founders_SR_Ore_T01",
"Schematic:SID_Assault_Hydra_SR_Ore_T01",
"Schematic:SID_Assault_Doubleshot_VR_Ore_T01",
"Schematic:SID_Assault_Doubleshot_SR_Ore_T01",
"Schematic:SID_Assault_Burst_VR_Ore_T01",
"Schematic:SID_Assault_Burst_SR_Ore_T01",
"Schematic:SID_Assault_Burst_UC_Ore_T01",
"Schematic:SID_Assault_Burst_R_Ore_T01",
"Schematic:SID_Assault_Burst_C_Ore_T01",
"Schematic:SID_Assault_Auto_VR_Ore_T01",
"Schematic:SID_Assault_Auto_SR_Ore_T01",
"Schematic:SID_Assault_Auto_Halloween_SR_Ore_T01",
"Schematic:SID_Assault_Auto_Founders_SR_Ore_T01",
"Schematic:SID_Assault_Auto_UC_Ore_T01",
"Schematic:SID_Assault_Auto_R_Ore_T01",
"Schematic:SID_Assault_Auto_C_Ore_T01",
"Schematic:SID_Piercing_Spear_Military_VR_Ore_T01",
"Schematic:SID_Piercing_Spear_Military_SR_Ore_T01",
"Schematic:SID_Piercing_Spear_Military_R_Ore_T01",
"Schematic:SID_Piercing_Spear_C_Ore_T01",
"Schematic:SID_Piercing_Spear_Laser_VR_Ore_T01",
"Schematic:SID_Piercing_Spear_Laser_SR_Ore_T01",
"Schematic:SID_Piercing_Spear_VR_Ore_T01",
"Schematic:SID_Piercing_Spear_SR_Ore_T01",
"Schematic:SID_Piercing_Spear_UC_Ore_T01",
"Schematic:SID_Piercing_Spear_R_Ore_T01",
"Schematic:SID_Edged_Sword_Medium_C_Ore_T01",
"Schematic:SID_Edged_Sword_Medium_Laser_VR_Ore_T01",
"Schematic:SID_Edged_Sword_Medium_Laser_SR_Ore_T01",
"Schematic:SID_Edged_Sword_Medium_Laser_Founders_VR_Ore_T01",
"Schematic:SID_Edged_Sword_Medium_Laser_Founders_SR_Ore_T01",
"Schematic:SID_Edged_Sword_Medium_Laser_Founders_R_Ore_T01",
"Schematic:SID_Edged_Sword_Medium_VR_Ore_T01",
"Schematic:SID_Edged_Sword_Medium_SR_Ore_T01",
"Schematic:SID_Edged_Sword_Medium_UC_Ore_T01",
"Schematic:SID_Edged_Sword_Medium_R_Ore_T01",
"Schematic:SID_Edged_Sword_Light_VR_Ore_T01",
"Schematic:SID_Edged_Sword_Light_UC_Ore_T01",
"Schematic:SID_Edged_Sword_Light_SR_Ore_T01",
"Schematic:SID_Edged_Sword_Light_R_Ore_T01",
"Schematic:SID_Edged_Sword_Light_Founders_VR_Ore_T01",
"Schematic:SID_Edged_Sword_Light_C_Ore_T01",
"Schematic:SID_Edged_Sword_Hydraulic_VR_Ore_T01",
"Schematic:SID_Edged_Sword_Hydraulic_SR_Ore_T01",
"Schematic:SID_Edged_Sword_Heavy_VR_Ore_T01",
"Schematic:SID_Edged_Sword_Heavy_SR_Ore_T01",
"Schematic:SID_Edged_Sword_Heavy_R_Ore_T01",
"Schematic:SID_Edged_Sword_Heavy_Founders_VR_Ore_T01",
"Schematic:SID_Edged_Sword_Heavy_UC_Ore_T01",
"Schematic:SID_Edged_Sword_Heavy_C_Ore_T01",
"Schematic:SID_Edged_Scythe_C_Ore_T01",
"Schematic:SID_Edged_Scythe_Laser_VR_Ore_T01",
"Schematic:SID_Edged_Scythe_Laser_SR_Ore_T01",
"Schematic:SID_Edged_Scythe_VR_Ore_T01",
"Schematic:SID_Edged_Scythe_SR_Ore_T01",
"Schematic:SID_Edged_Scythe_UC_Ore_T01",
"Schematic:SID_Edged_Scythe_R_Ore_T01",
"Schematic:SID_Edged_Axe_Medium_C_Ore_T01",
"Schematic:SID_Edged_Axe_Medium_Laser_VR_Ore_T01",
"Schematic:SID_Edged_Axe_Medium_Laser_SR_Ore_T01",
"Schematic:SID_Edged_Axe_Medium_VR_Ore_T01",
"Schematic:SID_Edged_Axe_Medium_SR_Ore_T01",
"Schematic:SID_Edged_Axe_Medium_Founders_VR_Ore_T01",
"Schematic:SID_Edged_Axe_Medium_UC_Ore_T01",
"Schematic:SID_Edged_Axe_Medium_R_Ore_T01",
"Schematic:SID_Edged_Axe_Light_VR_Ore_T01",
"Schematic:SID_Edged_Axe_Light_SR_Ore_T01",
"Schematic:SID_Edged_Axe_Light_UC_Ore_T01",
"Schematic:SID_Edged_Axe_Light_R_Ore_T01",
"Schematic:SID_Edged_Axe_Light_C_Ore_T01",
"Schematic:SID_Edged_Axe_Heavy_C_Ore_T01",
"Schematic:SID_Edged_Axe_Heavy_VR_Ore_T01",
"Schematic:SID_Edged_Axe_Heavy_SR_Ore_T01",
"Schematic:SID_Edged_Axe_Heavy_UC_Ore_T01",
"Schematic:SID_Edged_Axe_Heavy_R_Ore_T01",
"Schematic:SID_Blunt_Medium_C_Ore_T01",
"Schematic:SID_Blunt_Medium_VR_Ore_T01",
"Schematic:SID_Blunt_Medium_SR_Ore_T01",
"Schematic:SID_Blunt_Medium_UC_Ore_T01",
"Schematic:SID_Blunt_Medium_R_Ore_T01",
"Schematic:SID_Blunt_Light_VR_Ore_T01",
"Schematic:SID_Blunt_Light_SR_Ore_T01",
"Schematic:SID_Blunt_Tool_Light_UC_Ore_T01",
"Schematic:SID_Blunt_Tool_Light_R_Ore_T01",
"Schematic:SID_Blunt_Hammer_Rocket_VR_Ore_T01",
"Schematic:SID_Blunt_Hammer_Rocket_SR_Ore_T01",
"Schematic:SID_Blunt_Hammer_Heavy_C_Ore_T01",
"Schematic:SID_Blunt_Hammer_Heavy_VR_Ore_T01",
"Schematic:SID_Blunt_Hammer_Heavy_SR_Ore_T01",
"Schematic:SID_Blunt_Hammer_Heavy_Founders_VR_Ore_T01",
"Schematic:SID_Blunt_Hammer_Heavy_UC_Ore_T01",
"Schematic:SID_Blunt_Hammer_Heavy_R_Ore_T01",
"Schematic:SID_Blunt_Light_Rocketbat_VR_Ore_T01",
"Schematic:SID_Blunt_Light_Rocketbat_SR_Ore_T01",
"Schematic:SID_Blunt_Light_C_Ore_T01",
"Schematic:SID_Blunt_Light_Bat_UC_Ore_T01",
"Schematic:SID_Blunt_Light_Bat_R_Ore_T01",
"Schematic:SID_Blunt_Club_Light_VR_Ore_T01",
"Schematic:SID_Blunt_Club_Light_SR_Ore_T01",
"Schematic:SID_Blunt_Light_UC_Ore_T01",
"Schematic:SID_Blunt_Light_R_Ore_T01",
"Schematic:SID_Blunt_Heavy_Paddle_UC_Ore_T01",
"Schematic:SID_Blunt_Heavy_Paddle_R_Ore_T01",
"Schematic:SID_Blunt_Heavy_Paddle_C_Ore_T01"
]

View File

@@ -1,7 +0,0 @@
[
"lawin",
"ti93",
"pro100katyt",
"playeereq",
"matteoki"
]

View File

@@ -1,56 +0,0 @@
{
"Season2": {
"battlePassPurchased": false,
"battlePassTier": 1,
"battlePassXPBoost": 0,
"battlePassXPFriendBoost": 0
},
"Season3": {
"battlePassPurchased": false,
"battlePassTier": 1,
"battlePassXPBoost": 0,
"battlePassXPFriendBoost": 0
},
"Season4": {
"battlePassPurchased": false,
"battlePassTier": 1,
"battlePassXPBoost": 0,
"battlePassXPFriendBoost": 0
},
"Season5": {
"battlePassPurchased": false,
"battlePassTier": 1,
"battlePassXPBoost": 0,
"battlePassXPFriendBoost": 0
},
"Season6": {
"battlePassPurchased": false,
"battlePassTier": 1,
"battlePassXPBoost": 0,
"battlePassXPFriendBoost": 0
},
"Season7": {
"battlePassPurchased": false,
"battlePassTier": 1,
"battlePassXPBoost": 0,
"battlePassXPFriendBoost": 0
},
"Season8": {
"battlePassPurchased": false,
"battlePassTier": 1,
"battlePassXPBoost": 0,
"battlePassXPFriendBoost": 0
},
"Season9": {
"battlePassPurchased": false,
"battlePassTier": 1,
"battlePassXPBoost": 0,
"battlePassXPFriendBoost": 0
},
"Season10": {
"battlePassPurchased": false,
"battlePassTier": 1,
"battlePassXPBoost": 0,
"battlePassXPFriendBoost": 0
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1 +0,0 @@
[]

View File

@@ -1,10 +0,0 @@
{
"friends": [],
"incoming": [],
"outgoing": [],
"suggested": [],
"blocklist": [],
"settings": {
"acceptInvites": "public"
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,67 +0,0 @@
{
"contentType": "collection",
"contentId": "motd-default-collection",
"tcId": "634e8e85-e2fc-4c68-bb10-93604cf6605f",
"contentItems": [
{
"contentType": "content-item",
"contentId": "46874c56-0973-4cbe-ac98-b580c5b36df5",
"tcId": "61fb3dd8-f23d-45cc-9058-058ab223ba5c",
"contentFields": {
"body": {
"ar": "استمتع بتجربة لعب استثنائية!",
"en": "Have a phenomenal gaming experience!",
"de": "Wünsche allen ein wunderbares Spielerlebnis!",
"es": "¡Que disfrutes de tu experiencia de videojuegos!",
"es-419": "¡Ten una experiencia de juego espectacular!",
"fr": "Un bon jeu à toutes et à tous !",
"it": "Ti auguriamo un'esperienza di gioco fenomenale!",
"ja": "驚きの体験をしよう!",
"ko": "게임에서 환상적인 경험을 해보세요!",
"pl": "Życzymy fenomenalnej gry!",
"pt-BR": "Tenha uma experiência de jogo fenomenal!",
"ru": "Желаю невероятно приятной игры!",
"tr": "Muhteşem bir oyun deneyimi yaşamanı dileriz!"
},
"entryType": "Website",
"image": [
{
"width": 1920,
"height": 1080,
"url": "https://fortnite-public-service-prod11.ol.epicgames.com/images/motd.png"
}
],
"tabTitleOverride": "LawinServer",
"tileImage": [
{
"width": 1024,
"height": 512,
"url": "https://fortnite-public-service-prod11.ol.epicgames.com/images/motd-s.png"
}
],
"title": {
"ar": "مرحبًا بك في LawinServer!",
"en": "Welcome to LawinServer!",
"de": "Willkommen bei LawinServer!",
"es": "¡Bienvenidos a LawinServer!",
"es-419": "¡Bienvenidos a LawinServer!",
"fr": "Bienvenue sur LawinServer !",
"it": "Benvenuto in LawinServer!",
"ja": "LawinServerへようこそ",
"ko": "LawinServer에 오신 것을 환영합니다!",
"pl": "Witaj w LawinServerze!",
"pt-BR": "Bem-vindo ao LawinServer!",
"ru": "Добро пожаловать в LawinServer!",
"tr": "LavinServer'a Hoş Geldiniz!"
},
"videoAutoplay": false,
"videoLoop": false,
"videoMute": false,
"videoStreamingEnabled": false,
"websiteButtonText": "Discord",
"websiteURL": "https://discord.gg/KJ8UaHZ"
},
"contentSchemaName": "MotdWebsiteNewsWithVideo"
}
]
}

View File

@@ -1,4 +0,0 @@
{
"accountId": "",
"optOutOfPublicLeaderboards": false
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,36 +0,0 @@
{
"author": "List made by PRO100KatYT",
"Season11": {
"ERG.Node.A.1": "AthenaCharacter:cid_645_athena_commando_f_wolly",
"ERG.Node.B.1": "AthenaGlider:glider_id_188_galileorocket_g7oki",
"ERG.Node.C.1": "AthenaBackpack:bid_430_galileospeedboat_9rxe3",
"ERG.Node.D.1": "AthenaCharacter:cid_643_athena_commando_m_ornamentsoldier",
"ERG.Node.A.2": "AthenaPickaxe:pickaxe_id_329_gingerbreadcookie1h",
"ERG.Node.A.3": "AthenaPickaxe:pickaxe_id_332_mintminer",
"ERG.Node.A.4": "AthenaDance:eid_snowglobe",
"ERG.Node.A.5": "AthenaGlider:glider_id_191_pinetree",
"ERG.Node.A.6": "AthenaItemWrap:wrap_188_wrappingpaper",
"ERG.Node.A.7": "AthenaItemWrap:wrap_183_newyear2020",
"ERG.Node.A.8": "AthenaSkyDiveContrail:trails_id_082_holidaygarland",
"ERG.Node.A.9": "AthenaMusicPack:musicpack_040_xmaschiptunes",
"ERG.Node.A.10": "AthenaLoadingScreen:lsid_208_smpattern",
"ERG.Node.A.11": "AthenaLoadingScreen:lsid_209_akcrackshot"
},
"Season19": {
"ERG.Node.A.1": "Token:14daysoffortnite_small_giftbox",
"ERG.Node.B.1": "AthenaDance:emoji_s19_animwinterfest2021",
"ERG.Node.C.1": "AthenaGlider:glider_id_335_logarithm_40qgl",
"ERG.Node.D.1": "AthenaCharacter:cid_a_323_athena_commando_m_bananawinter",
"ERG.Node.A.2": "HomebaseBannerIcon:brs19_winterfest2021",
"ERG.Node.A.3": "AthenaSkyDiveContrail:trails_id_137_turtleneckcrystal",
"ERG.Node.A.4": "AthenaItemWrap:wrap_429_holidaysweater",
"ERG.Node.A.5": "AthenaLoadingScreen:lsid_393_winterfest2021",
"ERG.Node.A.6": "AthenaMusicPack:musicpack_117_winterfest2021",
"ERG.Node.A.7": "AthenaDance:eid_epicyarn",
"ERG.Node.A.8": "AthenaCharacter:cid_a_310_athena_commando_F_scholarfestive",
"ERG.Node.A.9": "AthenaPickaxe:pickaxe_id_731_scholarfestivefemale1h",
"ERG.Node.A.10": "AthenaItemWrap:wrap_430_winterlights",
"ERG.Node.A.11": "AthenaDance:spid_346_winterfest_2021",
"ERG.Node.A.12": "AthenaPickaxe:pickaxe_ID_732_shovelmale"
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -11,6 +11,7 @@ import 'package:reboot_launcher/src/model/game_type.dart';
import 'package:reboot_launcher/src/util/os.dart'; import 'package:reboot_launcher/src/util/os.dart';
import 'package:reboot_launcher/src/util/patcher.dart'; import 'package:reboot_launcher/src/util/patcher.dart';
import 'package:reboot_launcher/src/util/reboot.dart'; import 'package:reboot_launcher/src/util/reboot.dart';
import 'package:reboot_launcher/src/util/server.dart' as server;
late String? username; late String? username;
late GameType type; late GameType type;
@@ -79,12 +80,13 @@ void main(List<String> args) async {
var serverType = getServerType(result); var serverType = getServerType(result);
var host = result["server-host"] ?? serverJson["${serverType.id}_host"]; var host = result["server-host"] ?? serverJson["${serverType.id}_host"];
var port = result["server-port"] ?? serverJson["${serverType.id}_port"]; var port = result["server-port"] ?? serverJson["${serverType.id}_port"];
var started = await startServer(host, port, serverType, result["matchmaking-address"]); var started = await startServer(host, port, serverType);
if(!started){ if(!started){
stderr.writeln("Cannot start server!"); stderr.writeln("Cannot start server!");
return; return;
} }
server.writeMatchmakingIp(result["matchmaking-address"]);
autoRestart = result["auto-restart"]; autoRestart = result["auto-restart"];
await startGame(); await startGame();
} }

View File

@@ -77,8 +77,8 @@ class _RebootApplicationState extends State<RebootApplication> {
); );
} }
ThemeData _createTheme(Brightness brightness) { FluentThemeData _createTheme(Brightness brightness) {
return ThemeData( return FluentThemeData(
brightness: brightness, brightness: brightness,
accentColor: SystemTheme.accentColor.accent.toAccentColor(), accentColor: SystemTheme.accentColor.accent.toAccentColor(),
visualDensity: VisualDensity.standard, visualDensity: VisualDensity.standard,

View File

@@ -38,7 +38,7 @@ Future<void> startGame() async {
stdout.writeln("No username was specified, using $username by default. Use --username to specify one"); stdout.writeln("No username was specified, using $username by default. Use --username to specify one");
} }
_gameProcess = await Process.start(gamePath, createRebootArgs(username!, type)) _gameProcess = await Process.start(gamePath, createRebootArgs(username!, type, ""))
..exitCode.then((_) => _onClose()) ..exitCode.then((_) => _onClose())
..outLines.forEach((line) => _onGameOutput(line, dll, hosting, verbose)); ..outLines.forEach((line) => _onGameOutput(line, dll, hosting, verbose));
} }

View File

@@ -1,19 +1,17 @@
import 'dart:io'; import 'dart:io';
import 'package:process_run/shell.dart'; import 'package:process_run/shell.dart';
import 'package:reboot_launcher/src/embedded/server.dart';
import 'package:shelf/shelf_io.dart' as shelf_io; import 'package:shelf/shelf_io.dart' as shelf_io;
import 'package:shelf_proxy/shelf_proxy.dart'; import 'package:shelf_proxy/shelf_proxy.dart';
import '../model/server_type.dart'; import '../model/server_type.dart';
import '../util/server.dart'; import '../util/server.dart' as server;
import 'game.dart';
Future<bool> startServer(String? host, String? port, ServerType type, String? matchmakingIp) async { Future<bool> startServer(String? host, String? port, ServerType type) async {
stdout.writeln("Starting backend server..."); stdout.writeln("Starting backend server...");
switch(type){ switch(type){
case ServerType.local: case ServerType.local:
var result = await ping(host ?? "127.0.0.1", port ?? "3551"); var result = await server.ping(host ?? "127.0.0.1", port ?? "3551");
if(result == null){ if(result == null){
throw Exception("Local backend server is not running"); throw Exception("Local backend server is not running");
} }
@@ -22,11 +20,8 @@ Future<bool> startServer(String? host, String? port, ServerType type, String? ma
return true; return true;
case ServerType.embedded: case ServerType.embedded:
stdout.writeln("Starting an embedded server..."); stdout.writeln("Starting an embedded server...");
await startEmbeddedServer( await server.startServer();
() => matchmakingIp ?? "127.0.0.1" var result = await server.ping(host ?? "127.0.0.1", port ?? "3551");
);
await startEmbeddedMatchmaker();
var result = await ping(host ?? "127.0.0.1", port ?? "3551");
if(result == null){ if(result == null){
throw Exception("Cannot start embedded server"); throw Exception("Cannot start embedded server");
} }
@@ -62,7 +57,7 @@ Future<HttpServer?> _changeReverseProxyState(String host, String port) async {
} }
try{ try{
var uri = await ping(host, port); var uri = await server.ping(host, port);
if(uri == null){ if(uri == null){
return null; return null;
} }

View File

@@ -1,9 +1,7 @@
import 'dart:async'; import 'dart:async';
import 'dart:collection'; import 'dart:collection';
import 'dart:convert'; import 'dart:convert';
import 'dart:io';
import 'package:async/async.dart';
import 'package:fluent_ui/fluent_ui.dart'; import 'package:fluent_ui/fluent_ui.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:get_storage/get_storage.dart'; import 'package:get_storage/get_storage.dart';
@@ -14,12 +12,13 @@ import 'package:reboot_launcher/src/model/game_type.dart';
class GameController extends GetxController { class GameController extends GetxController {
late final GetStorage _storage; late final GetStorage _storage;
late final TextEditingController username; late final TextEditingController username;
late final TextEditingController version; late final TextEditingController customLaunchArgs;
late final Rx<List<FortniteVersion>> versions; late final Rx<List<FortniteVersion>> versions;
late final Rxn<FortniteVersion> _selectedVersion; late final Rxn<FortniteVersion> _selectedVersion;
late final Rx<GameType> type; late final Rx<GameType> type;
late final HashMap<GameType, GameInstance> gameInstancesMap; late final HashMap<GameType, GameInstance> gameInstancesMap;
late final RxBool started; late final RxBool started;
late final RxBool autostartGameServer;
late bool updated; late bool updated;
late bool error; late bool error;
late bool failing; late bool failing;
@@ -50,10 +49,16 @@ class GameController extends GetxController {
username = TextEditingController(text: _readUsername()); username = TextEditingController(text: _readUsername());
username.addListener(() => _storage.write("${type.value == GameType.client ? 'game' : 'host'}_username", username.text)); username.addListener(() => _storage.write("${type.value == GameType.client ? 'game' : 'host'}_username", username.text));
customLaunchArgs = TextEditingController(text: _storage.read("custom_launch_args" ?? ""));
customLaunchArgs.addListener(() => _storage.write("custom_launch_args", customLaunchArgs.text));
gameInstancesMap= HashMap(); gameInstancesMap= HashMap();
started = RxBool(false); started = RxBool(false);
autostartGameServer = RxBool(_storage.read("auto_start_game_server") ?? true);
autostartGameServer.listen((value) => _storage.write("auto_start_game_server", value));
updated = false; updated = false;
error = false; error = false;
@@ -95,10 +100,10 @@ class GameController extends GetxController {
bool get hasNoVersions => versions.value.isEmpty; bool get hasNoVersions => versions.value.isEmpty;
Rxn<FortniteVersion> get selectedVersionObs => _selectedVersion;
GameInstance? get currentGameInstance => gameInstancesMap[type()]; GameInstance? get currentGameInstance => gameInstancesMap[type()];
FortniteVersion? get selectedVersion => _selectedVersion();
set selectedVersion(FortniteVersion? version) { set selectedVersion(FortniteVersion? version) {
_selectedVersion(version); _selectedVersion(version);
_storage.write("version", version?.name); _storage.write("version", version?.name);

View File

@@ -6,6 +6,7 @@ import 'package:get_storage/get_storage.dart';
import 'package:jaguar/jaguar.dart'; import 'package:jaguar/jaguar.dart';
import '../model/server_type.dart'; import '../model/server_type.dart';
import '../util/server.dart';
class ServerController extends GetxController { class ServerController extends GetxController {
static const String _serverName = "127.0.0.1"; static const String _serverName = "127.0.0.1";
@@ -17,13 +18,14 @@ class ServerController extends GetxController {
late final Rx<ServerType> type; late final Rx<ServerType> type;
late final RxBool warning; late final RxBool warning;
late RxBool started; late RxBool started;
Jaguar? embeddedServer; late RxBool loginAutomatically;
Jaguar? embeddedMatchmaker;
HttpServer? remoteServer; HttpServer? remoteServer;
ServerController() { ServerController() {
_storage = GetStorage("server"); _storage = GetStorage("server");
started = RxBool(false);
loginAutomatically = RxBool(_storage.read("login_automatically") ?? false);
loginAutomatically.listen((value) => _storage.write("login_automatically", value));
type = Rx(ServerType.values.elementAt(_storage.read("type") ?? 0)); type = Rx(ServerType.values.elementAt(_storage.read("type") ?? 0));
type.listen((value) { type.listen((value) {
host.text = _readHost(); host.text = _readHost();
@@ -35,17 +37,12 @@ class ServerController extends GetxController {
stop(); stop();
}); });
host = TextEditingController(text: _readHost()); host = TextEditingController(text: _readHost());
host.addListener(() => _storage.write("${type.value.id}_host", host.text)); host.addListener(() => _storage.write("${type.value.id}_host", host.text));
port = TextEditingController(text: _readPort()); port = TextEditingController(text: _readPort());
port.addListener(() => _storage.write("${type.value.id}_port", port.text)); port.addListener(() => _storage.write("${type.value.id}_port", port.text));
warning = RxBool(_storage.read("lawin_value") ?? true); warning = RxBool(_storage.read("lawin_value") ?? true);
warning.listen((value) => _storage.write("lawin_value", value)); warning.listen((value) => _storage.write("lawin_value", value));
started = RxBool(false);
} }
String _readHost() { String _readHost() {
@@ -63,8 +60,7 @@ class ServerController extends GetxController {
try{ try{
switch(type()){ switch(type()){
case ServerType.embedded: case ServerType.embedded:
await embeddedServer?.close(); stopServer();
await embeddedMatchmaker?.close();
break; break;
case ServerType.remote: case ServerType.remote:
await remoteServer?.close(force: true); await remoteServer?.close(force: true);

View File

@@ -1,8 +1,11 @@
import 'dart:io';
import 'package:fluent_ui/fluent_ui.dart'; import 'package:fluent_ui/fluent_ui.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:get_storage/get_storage.dart'; import 'package:get_storage/get_storage.dart';
import 'package:reboot_launcher/src/model/tutorial_page.dart'; import 'package:ini/ini.dart';
import 'package:reboot_launcher/src/util/os.dart'; import 'package:reboot_launcher/src/util/os.dart';
import 'package:reboot_launcher/src/util/server.dart';
import 'dart:ui'; import 'dart:ui';
import '../util/reboot.dart'; import '../util/reboot.dart';
@@ -15,12 +18,7 @@ class SettingsController extends GetxController {
late final TextEditingController consoleDll; late final TextEditingController consoleDll;
late final TextEditingController authDll; late final TextEditingController authDll;
late final TextEditingController matchmakingIp; late final TextEditingController matchmakingIp;
late final Rx<PaneDisplayMode> displayType;
late final RxBool automaticallyStartMatchmaker;
late final RxBool doNotAskAgain;
late final RxBool advancedMode;
late final RxBool autoUpdate; late final RxBool autoUpdate;
late Rx<TutorialPage> tutorialPage;
late double width; late double width;
late double height; late double height;
late double? offsetX; late double? offsetX;
@@ -43,29 +41,18 @@ class SettingsController extends GetxController {
matchmakingIp.addListener(() async { matchmakingIp.addListener(() async {
var text = matchmakingIp.text; var text = matchmakingIp.text;
_storage.write("ip", text); _storage.write("ip", text);
writeMatchmakingIp(text);
}); });
automaticallyStartMatchmaker = RxBool(_storage.read("start_matchmaker_automatically") ?? false); width = _storage.read("width") ?? 912;
automaticallyStartMatchmaker.listen((value) => _storage.write("start_matchmaker_automatically", value)); height = _storage.read("height") ?? 660;
doNotAskAgain = RxBool(_storage.read("do_not_ask_again") ?? false);
doNotAskAgain.listen((value) => _storage.write("do_not_ask_again", value));
width = _storage.read("width") ?? window.physicalSize.width;
height = _storage.read("height") ?? window.physicalSize.height;
offsetX = _storage.read("offset_x"); offsetX = _storage.read("offset_x");
offsetY = _storage.read("offset_y"); offsetY = _storage.read("offset_y");
advancedMode = RxBool(_storage.read("advanced") ?? false);
advancedMode.listen((value) async => _storage.write("advanced", value));
autoUpdate = RxBool(_storage.read("auto_update") ?? false); autoUpdate = RxBool(_storage.read("auto_update") ?? false);
autoUpdate.listen((value) async => _storage.write("auto_update", value)); autoUpdate.listen((value) async => _storage.write("auto_update", value));
displayType = Rx(PaneDisplayMode.top);
scrollingDistance = 0.0; scrollingDistance = 0.0;
tutorialPage = Rx(TutorialPage.start);
} }
TextEditingController _createController(String key, String name) { TextEditingController _createController(String key, String name) {

View File

@@ -40,7 +40,6 @@ class AddLocalVersion extends StatelessWidget {
), ),
FileSelector( FileSelector(
label: "Location",
placeholder: "Type the game folder", placeholder: "Type the game folder",
windowTitle: "Select game folder", windowTitle: "Select game folder",
controller: _gamePathController, controller: _gamePathController,

View File

@@ -273,7 +273,6 @@ class _AddServerVersionState extends State<AddServerVersion> {
VersionNameInput(controller: _nameController), VersionNameInput(controller: _nameController),
const SizedBox(height: 16.0), const SizedBox(height: 16.0),
FileSelector( FileSelector(
label: "Destination",
placeholder: "Type the download destination", placeholder: "Type the download destination",
windowTitle: "Select download destination", windowTitle: "Select download destination",
controller: _pathController, controller: _pathController,

View File

@@ -6,14 +6,12 @@ import 'package:reboot_launcher/src/controller/settings_controller.dart';
import 'package:reboot_launcher/src/dialog/dialog.dart'; import 'package:reboot_launcher/src/dialog/dialog.dart';
import 'package:reboot_launcher/src/dialog/dialog_button.dart'; import 'package:reboot_launcher/src/dialog/dialog_button.dart';
import 'package:reboot_launcher/src/dialog/snackbar.dart'; import 'package:reboot_launcher/src/dialog/snackbar.dart';
import 'package:reboot_launcher/src/embedded/server.dart';
import 'package:reboot_launcher/src/model/server_type.dart'; import 'package:reboot_launcher/src/model/server_type.dart';
import 'package:reboot_launcher/src/util/os.dart'; import 'package:reboot_launcher/src/util/os.dart';
import 'package:sync/semaphore.dart'; import 'package:sync/semaphore.dart';
import 'package:url_launcher/url_launcher.dart'; import 'package:url_launcher/url_launcher.dart';
import '../../main.dart'; import '../../main.dart';
import '../page/home_page.dart';
import '../util/server.dart'; import '../util/server.dart';
extension ServerControllerDialog on ServerController { extension ServerControllerDialog on ServerController {
@@ -81,10 +79,7 @@ extension ServerControllerDialog on ServerController {
try{ try{
switch(type()){ switch(type()){
case ServerType.embedded: case ServerType.embedded:
embeddedServer = await startEmbeddedServer( startServer();
() => Get.find<SettingsController>().matchmakingIp.text,
);
embeddedMatchmaker = await startEmbeddedMatchmaker();
break; break;
case ServerType.remote: case ServerType.remote:
var uriResult = await _pingRemoteInteractive(); var uriResult = await _pingRemoteInteractive();

View File

@@ -1,333 +0,0 @@
import 'dart:collection';
import 'dart:convert';
import 'dart:io';
import 'package:jaguar/http/context/context.dart';
import 'package:reboot_launcher/src/embedded/utils.dart';
import '../util/os.dart';
final Directory _profiles = Directory("${Platform.environment["UserProfile"]}\\.reboot_launcher\\backend\\profiles");
const String _token = "reboot_token";
const String _clientId = "reboot_client";
const String _device = "reboot_device";
const String _sessionId = "3c3662bcb661d6de679c636744c66b62";
List<Map<String, Object>> getAccounts(Context context) {
return context.query.getList("accountId").map(getAccount).toList();
}
Map<String, Object> getAccount(String account) {
return {"id": account, "displayName": _parseUsername(account), "externalAuths": {}};
}
Map<String, Object> getAccountInfo(Context context) {
var usernameId = context.pathParams.get("accountId")!;
var accountName = _parseUsername(usernameId);
return {
"id": usernameId,
"displayName": accountName,
"name": "Reboot",
"email": usernameId,
"failedLoginAttempts": 0,
"lastLogin": "2022-11-08T18:55:52.341Z",
"numberOfDisplayNameChanges": 0,
"ageGroup": "UNKNOWN",
"headless": false,
"country": "US",
"lastName": "Server",
"preferredLanguage": "en",
"canUpdateDisplayName": false,
"tfaEnabled": false,
"emailVerified": true,
"minorVerified": false,
"minorExpected": false,
"minorStatus": "UNKNOWN"
};
}
List<Map<String, Object>> getExternalAuths(Context context) => [];
Future<Map<String, Object>> getOAuthToken(Context context) async {
var usernameId = await _getUsername(context);
var accountName = _parseUsername(usernameId);
return {
"access_token": _token,
"expires_in": 28800,
"expires_at": "9999-12-02T01:12:01.100Z",
"token_type": "bearer",
"refresh_token": _token,
"refresh_expires": 86400,
"refresh_expires_at": "9999-12-02T01:12:01.100Z",
"account_id": usernameId,
"client_id": _clientId,
"internal_client": true,
"client_service": "fortnite",
"displayName": accountName,
"app": "fortnite",
"in_app_id": usernameId,
"device_id": _device
};
}
Future<String> _getUsername(Context context) async {
var params = await parseBody(context);
var username = params["username"];
return username ?? "unknown@projectreboot.dev";
}
Map<String, Object> verifyOAuthToken(Context context) {
return {
"token": _token,
"session_id": _sessionId,
"token_type": "bearer",
"client_id": _clientId,
"internal_client": true,
"client_service": "fortnite",
"account_id": "unknown",
"expires_in": 28800,
"expires_at": "9999-12-02T01:12:01.100Z",
"auth_method": "exchange_code",
"display_name": "unknown",
"app": "fortnite",
"in_app_id": "unknown",
"device_id": _device
};
}
List<Map<String, Object>> getExchange(Context context) => [];
List<String> getSsoDomains(Context context) => [
"unrealengine.com",
"unrealtournament.com",
"fortnite.com",
"epicgames.com"
];
String tryPlayOnPlatform(Context context) => "true";
List<Map<String, Object>> getFeatures(Context context) => [];
Map<String, Object?> getProfile(Context context){
var profileId = context.query.get("profileId");
if (profileId == null) {
return {"Error": "Profile not defined."};
}
var profileJson = _getProfileJson(profileId, context);
var profileFile = _getProfileFile(context);
var baseRevision = profileJson["rvn"] ?? 0;
var queryRevision = context.query.getInt("rvn") ?? -1;
var profileChanges = _getFullProfileUpdate(context, profileId, profileJson, queryRevision, baseRevision);
if(profileId == "athena" && !profileFile.existsSync()) {
profileFile.writeAsStringSync(json.encode(profileJson), flush: true);
}
return {
"profileRevision": baseRevision,
"profileId": profileId,
"profileChangesBaseRevision": baseRevision,
"profileChanges": profileChanges,
"profileCommandRevision": profileJson["commandRevision"] ?? 0,
"serverTime": "2022-11-08T18:55:52.341Z",
"responseVersion": 1
};
}
Map<String, dynamic> _getProfileJson(String profileId, Context context) {
if(profileId == "athena"){
var profile = _getProfileFile(context);
if(profile.existsSync()){
return json.decode(profile.readAsStringSync());
}
var body = loadEmbedded("profiles/$profileId.json").readAsStringSync();
return json.decode(body);
}
var profileJson = json.decode(loadEmbedded("profiles/$profileId.json").readAsStringSync());
return profileJson;
}
Future<Map<String, Object>> equipItem(Context context) async {
var profileFile = _getProfileFile(context);
var profileJson = json.decode(profileFile.readAsStringSync());
var baseRevision = profileJson["rvn"] ?? 0;
var queryRevision = context.query.getInt("rvn") ?? -1;
var body = json.decode(utf8.decode(await context.body));
var variant = _getReturnVariant(body, profileJson);
var change = _getStatsChanged(body, profileJson);
var profileChanges = _getProfileChanges(queryRevision, baseRevision, profileJson, change, body, variant);
profileFile.writeAsStringSync(json.encode(profileJson));
return {
"profileRevision": baseRevision,
"profileId": "athena",
"profileChangesBaseRevision": baseRevision,
"profileChanges": profileChanges,
"profileCommandRevision": profileJson["commandRevision"] ?? 0,
"serverTime": "2022-11-08T18:55:52.341Z",
"responseVersion": 1
};
}
List<dynamic> _getProfileChanges(int queryRevision, baseRevision, profileJson, bool change, body, bool variant) {
var changes = [];
if (change) {
var category = ("favorite_${body["slotName"] ?? "character"}")
.toLowerCase();
if (category == "favorite_itemwrap") {
category += "s";
}
profileJson["rvn"] = (profileJson["rvn"] ?? 0) + 1;
profileJson["commandRevision"] = (profileJson["commandRevision"] ?? 0) + 1;
changes.add({
"changeType": "statModified",
"name": category,
"value": profileJson["stats"]["attributes"][category]
});
if (variant) {
changes.add({
"changeType": "itemAttrChanged",
"itemId": body["itemToSlot"],
"attributeName": "variants",
"attributeValue": profileJson["items"][body["itemToSlot"]]["attributes"]["variants"]
});
}
}
if(queryRevision != baseRevision){
return [{
"changeType": "fullProfileUpdate",
"profile": profileJson
}];
}
return changes;
}
bool _getStatsChanged(body, profileJson) {
var slotName = body["slotName"];
if (slotName == null) {
return false;
}
switch (slotName) {
case "Character":
profileJson["stats"]["attributes"]["favorite_character"] =
body["itemToSlot"] ?? "";
return true;
case "Backpack":
profileJson["stats"]["attributes"]["favorite_backpack"] =
body["itemToSlot"] ?? "";
return true;
case "Pickaxe":
profileJson["stats"]["attributes"]["favorite_pickaxe"] =
body["itemToSlot"] ?? "";
return true;
case "Glider":
profileJson["stats"]["attributes"]["favorite_glider"] =
body["itemToSlot"] ?? "";
return true;
case "SkyDiveContrail":
profileJson["stats"]["attributes"]["favorite_skydivecontrail"] =
body["itemToSlot"] ?? "";
return true;
case "MusicPack":
profileJson["stats"]["attributes"]["favorite_musicpack"] =
body["itemToSlot"] ?? "";
return true;
case "LoadingScreen":
profileJson["stats"]["attributes"]["favorite_loadingscreen"] =
body["itemToSlot"] ?? "";
return true;
case "Dance":
var index = body["indexWithinSlot"] ?? 0;
if (index >= 0) {
profileJson["stats"]["attributes"]["favorite_dance"][index] =
body["itemToSlot"] ?? "";
}
return true;
case "ItemWrap":
var index = body["indexWithinSlot"] ?? 0;
if (index < 0) {
for (var i = 0; i < 7; i++) {
profileJson["stats"]["attributes"]["favorite_itemwraps"][i] =
body["itemToSlot"] ?? "";
}
} else {
profileJson["stats"]["attributes"]["favorite_itemwraps"][index] =
body["itemToSlot"] ?? "";
}
return true;
default:
return false;
}
}
bool _getReturnVariant(body, profileJson) {
var variantUpdates = body["variantUpdates"] ?? [];
if(!variantUpdates.toString().contains("active")){
return false;
}
try {
var variantJson = profileJson["items"][body["itemToSlot"]]["attributes"]["variants"] ?? [];
if (variantJson.isEmpty) {
variantJson = variantUpdates;
}
for (var i in variantJson) {
try {
if (variantJson[i]["channel"].toLowerCase() == body["variantUpdates"][i]["channel"].toLowerCase()) {
profileJson["items"][body["itemToSlot"]]["attributes"]["variants"][i]["active"] = body["variantUpdates"][i]["active"] ?? "";
}
} catch (_) {
// Ignored
}
}
return true;
} catch (_) {
// Ignored
}
return false;
}
List<Map<String, Object?>> _getFullProfileUpdate(Context context, String profileName, Map<String, dynamic> profileJson, int queryRevision, int baseRevision) {
if (queryRevision == baseRevision) {
return [];
}
if (profileName == "athena") {
var season = parseSeason(context);
profileJson["stats"]["attributes"]["season_num"] = season;
profileJson["stats"]["attributes"]["book_purchased"] = true;
profileJson["stats"]["attributes"]["book_level"] = 100;
profileJson["stats"]["attributes"]["season_match_boost"] = 100;
profileJson["stats"]["attributes"]["season_friend_match_boost"] = 100;
}
return [{
"changeType": "fullProfileUpdate",
"profile": profileJson
}];
}
String _parseUsername(String username) =>
username.contains("@") ? username.split("@")[0] : username;
File _getProfileFile(Context context) {
if(!_profiles.existsSync()){
_profiles.createSync(recursive: true);
}
return File("${_profiles.path}\\ClientProfile.json");
}

View File

@@ -1,43 +0,0 @@
import 'dart:async';
import 'dart:io';
import 'package:jaguar/jaguar.dart';
class EmbeddedErrorWriter extends ErrorWriter {
static const String _errorName404 = "errors.com.lawinserver.common.not_found";
static const String _errorName500 = "errors.com.lawinserver.common.error";
static const String _errorCode = "1004";
@override
FutureOr<Response> make404(Context ctx) {
stdout.writeln("Unknown path: ${ctx.uri} with method ${ctx.method}");
ctx.response.headers.set('X-Epic-Error-Name', _errorName404);
ctx.response.headers.set('X-Epic-Error-Code', _errorCode);
return Response.json(
statusCode: 204,
{
"errorCode": _errorName404,
"errorMessage": "Sorry the resource you were trying to find could not be found",
"numericErrorCode": _errorCode,
"originatingService": "any",
"intent": "prod"
}
);
}
@override
FutureOr<Response> make500(Context ctx, Object error, [StackTrace? stack]) {
ctx.response.headers.set('X-Epic-Error-Name', _errorName500);
ctx.response.headers.set('X-Epic-Error-Code', _errorCode);
return Response.json(
statusCode: 500,
{
"errorCode": _errorName500,
"errorMessage": "Sorry the resource you were trying to find threw an error",
"numericErrorCode": _errorCode,
"originatingService": "any",
"intent": "prod"
}
);
}
}

View File

@@ -1,33 +0,0 @@
import 'package:jaguar/http/context/context.dart';
Map<String, Object?> getFortniteStatus(Context context) => {
"serviceInstanceId": "fortnite",
"status": "UP",
"message": "Fortnite is online",
"maintenanceUri": null,
"overrideCatalogIds": ["a7f138b2e51945ffbfdacc1af0541053"],
"allowedActions": [],
"banned": false,
"launcherInfoDTO": {
"appName": "Fortnite",
"catalogItemId": "4fe75bbc5a674f4f9b356b5c90567da5",
"namespace": "fn"
}
};
List<Map<String, Object?>> getBulkStatus(Context context) => [
{
"serviceInstanceId": "fortnite",
"status": "UP",
"message": "fortnite is up.",
"maintenanceUri": null,
"overrideCatalogIds": ["a7f138b2e51945ffbfdacc1af0541053"],
"allowedActions": ["PLAY", "DOWNLOAD"],
"banned": false,
"launcherInfoDTO": {
"appName": "Fortnite",
"catalogItemId": "4fe75bbc5a674f4f9b356b5c90567da5",
"namespace": "fn"
}
}
];

View File

@@ -1,144 +0,0 @@
import 'dart:convert';
import 'dart:io';
import 'package:crypto/crypto.dart';
import 'package:jaguar/http/context/context.dart';
import 'package:uuid/uuid.dart';
import 'package:jaguar/jaguar.dart';
String _build = "0";
String? _customIp;
Map<String, Object> getPlayerTicket(Context context){
var bucketId = context.query.get("bucketId");
if(bucketId == null){
return {"Error": "Missing bucket id"};
}
_build = bucketId.split(":")[0];
_customIp = context.query.get("player.option.customKey");
return {
"serviceUrl": "ws://127.0.0.1:8080",
"ticketType": "mms-player",
"payload": "69=",
"signature": "420="
};
}
Map<String, Object?> getSessionAccount(Context context) => {
"accountId": context.pathParams.get("accountId"),
"sessionId": context.pathParams.get("sessionId"),
"key": "AOJEv8uTFmUh7XM2328kq9rlAzeQ5xzWzPIiyKn2s7s="
};
Future<Map<String, Object?>> getMatch(Context context, String Function() ipQuery) async {
var ipAndPort = _customIp ?? ipQuery().trim();
var ip = ipAndPort.contains(":") ? ipAndPort.split(":")[0] : ipAndPort;
var port = ipAndPort.contains(":") ? int.parse(ipAndPort.split(":")[1]) : 7777;
return {
"id": context.pathParams.get("sessionId"),
"ownerId": _randomUUID(),
"ownerName": "[DS]fortnite-liveeugcec1c2e30ubrcore0a-z8hj-1968",
"serverName": "[DS]fortnite-liveeugcec1c2e30ubrcore0a-z8hj-1968",
"serverAddress": ip,
"serverPort": port,
"maxPublicPlayers": 220,
"openPublicPlayers": 175,
"maxPrivatePlayers": 0,
"openPrivatePlayers": 0,
"attributes": {
"REGION_s": "EU",
"GAMEMODE_s": "FORTATHENA",
"ALLOWBROADCASTING_b": true,
"SUBREGION_s": "GB",
"DCID_s": "FORTNITE-LIVEEUGCEC1C2E30UBRCORE0A-14840880",
"tenant_s": "Fortnite",
"MATCHMAKINGPOOL_s": "Any",
"STORMSHIELDDEFENSETYPE_i": 0,
"HOTFIXVERSION_i": 0,
"PLAYLISTNAME_s": "Playlist_DefaultSolo",
"SESSIONKEY_s": _randomUUID(),
"TENANT_s": "Fortnite",
"BEACONPORT_i": 15009
},
"publicPlayers": [],
"privatePlayers": [],
"totalPlayers": 45,
"allowJoinInProgress": false,
"shouldAdvertise": false,
"isDedicated": false,
"usesStats": false,
"allowInvites": false,
"usesPresence": false,
"allowJoinViaPresence": true,
"allowJoinViaPresenceFriendsOnly": false,
"buildUniqueId": _build,
"lastUpdated": "2022-11-08T18:55:52.341Z",
"started": false
};
}
List<Map<String, Object>> getMatchmakingRequests() => [];
void queueMatchmaking(WebSocket ws) {
var now = DateTime.now();
var ticketId = md5.convert(utf8.encode("1$now")).toString();
var matchId = md5.convert(utf8.encode("2$now")).toString();
var sessionId = md5.convert(utf8.encode("3$now")).toString();
ws.addUtf8Text(utf8.encode(
jsonEncode({
"payload": {
"state": "Connecting"
},
"name": "StatusUpdate"
})
));
ws.addUtf8Text(utf8.encode(
jsonEncode({
"payload": {
"totalPlayers": 1,
"connectedPlayers": 1,
"state": "Waiting"
},
"name": "StatusUpdate"
})
));
ws.addUtf8Text(utf8.encode(
jsonEncode({
"payload": {
"ticketId": ticketId,
"queuedPlayers": 0,
"estimatedWaitSec": 0,
"status": {},
"state": "Queued"
},
"name": "StatusUpdate"
})
));
ws.addUtf8Text(utf8.encode(
jsonEncode({
"payload": {
"matchId": matchId,
"state": "SessionAssignment"
},
"name": "StatusUpdate"
})
));
ws.addUtf8Text(utf8.encode(
jsonEncode({
"payload": {
"matchId": matchId,
"sessionId": sessionId,
"joinDelaySec": 1
},
"name": "Play"
})
));
}
String _randomUUID() => const Uuid().v4().replaceAll("-", "").toUpperCase();

File diff suppressed because it is too large Load Diff

View File

@@ -1,16 +0,0 @@
import 'package:jaguar/http/context/context.dart';
import 'package:reboot_launcher/src/embedded/utils.dart';
Map<String, Object?> getPrivacy(Context context) => {
"accountId": context.pathParams.get("accountId"),
"optOutOfPublicLeaderboards": false
};
Future<Map<String, Object?>> postPrivacy(Context context) async {
var body = await parseBody(context);
return {
"accountId": context.pathParams.get("accountId"),
"optOutOfPublicLeaderboards": body["optOutOfPublicLeaderboards"]
};
}

View File

@@ -1,120 +0,0 @@
import "dart:async";
import "dart:io";
import "package:jaguar/jaguar.dart";
import "package:reboot_launcher/src/embedded/auth.dart";
import 'package:reboot_launcher/src/embedded/misc.dart';
import 'package:reboot_launcher/src/embedded/privacy.dart';
import "package:reboot_launcher/src/embedded/storage.dart";
import 'package:reboot_launcher/src/embedded/storefront.dart';
import "package:reboot_launcher/src/embedded/version.dart";
import '../util/server.dart';
import "error.dart";
import "lightswitch.dart";
import 'matchmaking.dart';
Future<Jaguar> startEmbeddedServer(String Function() ipQuery) async {
var server = Jaguar(port: 3551, errorWriter: EmbeddedErrorWriter());
// Version
server.getJson("unknown", (context) => Response(body: "lawinserver"));
server.getJson("/fortnite/api/version", getVersion);
server.getJson("/fortnite/api/v2/versioncheck/*", hasUpdate);
server.getJson("/fortnite/api/v2/versioncheck*", hasUpdate);
server.getJson("/fortnite/api/versioncheck*", hasUpdate);
// Auth
server.getJson("/account/api/public/account/displayName/:accountId", getAccountInfo);
server.getJson("/account/api/public/account/:accountId", getAccountInfo);
server.getJson("/account/api/public/account/:accountId/externalAuths", getExternalAuths);
server.getJson("/account/api/public/account", getAccounts);
server.delete("/account/api/oauth/sessions/kill/*", (context) => Response(statusCode: 204));
server.getJson("/account/api/oauth/verify", verifyOAuthToken);
server.postJson("/account/api/oauth/token", getOAuthToken);
server.postJson("/account/api/oauth/exchange", getExchange);
server.getJson("/account/api/epicdomains/ssodomains", getSsoDomains);
server.post("/fortnite/api/game/v2/tryPlayOnPlatform/account/*", tryPlayOnPlatform);
server.post("/datarouter/api/v1/public/data/*", (context) => Response(statusCode: 204));
server.getJson("/fortnite/api/game/v2/enabled_features", getFeatures);
server.postJson("/fortnite/api/game/v2/grant_access/*", (context) => Response(statusCode: 204));
server.postJson("/fortnite/api/game/v2/profile/:profileId/client/EquipBattleRoyaleCustomization", equipItem);
server.postJson("/fortnite/api/game/v2/profile/:profileId/client/*", getProfile);
// Storage
server.getJson("/fortnite/api/cloudstorage/system", getStorageSettings);
server.get("/fortnite/api/cloudstorage/system/:file", getStorageSetting);
server.getJson("/fortnite/api/cloudstorage/user/:accountId", getStorageAccount);
server.getJson("/fortnite/api/cloudstorage/user/:accountId/:file", getStorageFile);
server.put("/fortnite/api/cloudstorage/user/:accountId/:file", addStorageFile);
// Status
server.getJson("/lightswitch/api/service/Fortnite/status", getFortniteStatus);
server.getJson("/lightswitch/api/service/bulk/status", getBulkStatus);
// Keychain and catalog
server.get("/fortnite/api/storefront/v2/catalog", getCatalog);
server.get("/fortnite/api/storefront/v2/keychain", getKeyChain);
server.get("/catalog/api/shared/bulk/offers", getOffers);
// Matchmaking
server.get("/fortnite/api/matchmaking/session/findPlayer/*", (context) => Response(statusCode: 200));
server.getJson("/fortnite/api/game/v2/matchmakingservice/ticket/player/*", getPlayerTicket);
server.getJson("/fortnite/api/game/v2/matchmaking/account/:accountId/session/:sessionId", getSessionAccount);
server.getJson("/fortnite/api/matchmaking/session/:sessionId", (context) => getMatch(context, ipQuery));
server.post("/fortnite/api/matchmaking/session/:accountId/join", (context) => Response(statusCode: 204));
server.postJson("/fortnite/api/matchmaking/session/matchMakingRequest", (context) => getMatchmakingRequests);
// Misc
server.getJson("/api/v1/events/Fortnite/download/*", getDownload);
server.getJson("/fortnite/api/receipts/v1/account/:accountId/receipts", getReceipts);
server.getJson("/content/api/pages/*", getContentPages);
server.getJson("/friends/api/v1/:accountId/settings", getFriendsSettings);
server.getJson("/friends/api/v1/:accountId/blocklist", getFriendsBlocklist);
server.getJson("/friends/api/public/blocklist/:accountId", getFriendsBlocklist);
server.getJson("/friends/api/public/friends/:accountId", getFriendsList);
server.getJson("/friends/api/public/list/fortnite/:accountId/recentPlayers", getRecentPlayers);
server.getJson("/fortnite/api/calendar/v1/timeline", getTimeline);
server.getJson("/fortnite/api/game/v2/events/tournamentandhistory/:accountId/EU/WindowsClient", getTournamentHistory);
server.get("/waitingroom/api/waitingroom", (context) => Response(statusCode: 204));
server.postJson("/api/v1/user/setting", (context) => []);
server.getJson("/eulatracking/api/public/agreements/fn/account/*", (context) => Response(statusCode: 204));
server.getJson("/socialban/api/public/v1/:accountId", getSocialBan);
server.getJson("/party/api/v1/Fortnite/user/*", getParty);
server.getJson("/friends/api/v1/*/settings", (context) => {});
server.getJson("/friends/api/v1/*/blocklist", (context) => {});
server.getJson("/friends/api/public/friends", (context) => []);
server.getJson("/friends/api/v1/:accountId/summary", (context) => []);
server.getJson("/friends/api/public/list/fortnite/*/recentPlayers", (context) => []);
server.getJson("/friends/api/public/blocklist/*", getBlockedFriends);
// Privacy
server.getJson("/fortnite/api/game/v2/privacy/account/:accountId", getPrivacy);
server.postJson("/fortnite/api/game/v2/privacy/account/:accountId", postPrivacy);
await server.serve(logRequests: true);
server.log.onRecord.listen((line) {
stdout.writeln(line);
serverLogFile.writeAsString("$line\n", mode: FileMode.append);
});
server.onException.add((ctx, exception, trace) {
stderr.writeln("An error occurred: $exception");
serverLogFile.writeAsString("An error occurred at ${ctx.uri}: \n$exception\n$trace\n", mode: FileMode.append);
});
return server;
}
Future<Jaguar> startEmbeddedMatchmaker() async {
var server = Jaguar(port: 8080);
WebSocket? ws;
server.wsStream(
"/",
(_, input) => ws = input,
after: [(_) => queueMatchmaking(ws!)]
);
await server.serve(logRequests: true);
return server;
}

View File

@@ -1,96 +0,0 @@
import 'package:path/path.dart' as path;
import 'dart:io';
import 'package:jaguar/jaguar.dart';
import 'package:jaguar/http/context/context.dart';
import 'package:crypto/crypto.dart';
import 'package:reboot_launcher/src/embedded/utils.dart';
import '../util/os.dart';
final Directory _settings = Directory("${Platform.environment["UserProfile"]}\\.reboot_launcher\\backend\\settings");
List getStorageSettings(Context context) =>
loadEmbeddedDirectory("config")
.listSync()
.map((e) => File(e.path))
.map(_getStorageSetting)
.toList();
Map<String, Object> _getStorageSetting(File file){
var name = path.basename(file.path);
var bytes = file.readAsBytesSync();
return {
"uniqueFilename": name,
"filename": name,
"hash": sha1.convert(bytes).toString(),
"hash256": sha256.convert(bytes).toString(),
"length": bytes.length,
"contentType": "application/octet-stream",
"uploaded": "2020-02-23T18:35:53.967Z",
"storageType": "S3",
"storageIds": {},
"doNotCache": true
};
}
Response getStorageSetting(Context context) {
var file = loadEmbedded("config\\${context.pathParams.get("file")}");
return Response(body: file.readAsStringSync());
}
Response getStorageFile(Context context) {
if (context.pathParams.get("file")?.toLowerCase() != "clientsettings.sav") {
return Response.json(
{"error": "File not found"},
statusCode: 404
);
}
var file = _getSettingsFile(context);
return Response(
body: file.existsSync() ? file.readAsBytesSync() : null,
headers: {"content-type": "application/octet-stream"}
);
}
List<Map<String, Object?>> getStorageAccount(Context context) {
var file = _getSettingsFile(context);
if (!file.existsSync()) {
return [];
}
var content = file.readAsBytesSync();
return [{
"uniqueFilename": "ClientSettings.Sav",
"filename": "ClientSettings.Sav",
"hash": sha1.convert(content).toString(),
"hash256": sha256.convert(content).toString(),
"length": content.length,
"contentType": "application/octet-stream",
"uploaded": "2020-02-23T18:35:53.967Z",
"storageType": "S3",
"storageIds": {},
"accountId": context.pathParams.get("accountId"),
"doNotCache": true
}];
}
Future<Response> addStorageFile(Context context) async {
if(!_settings.existsSync()){
await _settings.create(recursive: true);
}
var file = _getSettingsFile(context);
await file.writeAsBytes(await context.body);
return Response(statusCode: 204);
}
File _getSettingsFile(Context context) {
if(!_settings.existsSync()){
_settings.createSync(recursive: true);
}
return File("${_settings.path}\\ClientSettings.Sav");
}

View File

@@ -1,19 +0,0 @@
import 'package:jaguar/http/context/context.dart';
import 'package:jaguar/http/response/response.dart';
import 'package:reboot_launcher/src/util/os.dart';
final String _keyChain = loadEmbedded("responses/keychain.json").readAsStringSync();
final String _catalog = loadEmbedded("responses/catalog.json").readAsStringSync();
Response getCatalog(Context context) {
if (context.headers.value("user-agent")?.contains("2870186") == true) {
return Response(statusCode: 404);
}
return Response(body: _catalog, headers: {"content-type": "application/json"});
}
Response getKeyChain(Context context) => Response(body: _keyChain, headers: {"content-type": "application/json"});
Map<String, Object> getOffers(Context context) => {};

View File

@@ -1,42 +0,0 @@
import 'dart:collection';
import 'dart:convert';
import 'dart:math';
import 'package:jaguar/http/context/context.dart';
const String _chars =
'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz1234567890';
final Random _random = Random();
String randomString(int length) => String.fromCharCodes(
Iterable.generate(length, (_) => _chars.codeUnitAt(_random.nextInt(_chars.length))));
double parseSeasonBuild(Context context){
String? userAgent = context.headers.value("user-agent");
if (userAgent == null) {
return 1.0;
}
try {
var build = userAgent.split("Release-")[1].split("-")[0];
if (build.split(".").length == 3) {
var value = build.split(".");
return double.parse("${value[0]}.${value[1]}${value[2]}");
}
return double.parse(build);
} catch (_) {
return 2.0;
}
}
int parseSeason(Context context) => int.parse(parseSeasonBuild(context).toString().split(".")[0]);
Future<HashMap<String, String?>> parseBody(Context context) async {
var params = HashMap<String, String?>();
utf8.decode(await context.req.body)
.split("&")
.map((entry) => MapEntry(entry.substring(0, entry.indexOf("=")), entry.substring(entry.indexOf("=") + 1)))
.forEach((element) => params[element.key] = Uri.decodeQueryComponent(element.value));
return params;
}

View File

@@ -1,41 +0,0 @@
import 'package:jaguar/http/context/context.dart';
import 'package:reboot_launcher/src/util/time.dart';
Map<String, Object> getVersion(Context context) => {
"app": "fortnite",
"serverDate": "2022-11-08T18:55:52.341Z",
"overridePropertiesVersion": "unknown",
"cln": "17951730",
"build": "444",
"moduleName": "Fortnite-Core",
"buildDate": "2021-10-27T21:00:51.697Z",
"version": "18.30",
"branch": "Release-18.30",
"modules": {
"Epic-LightSwitch-AccessControlCore": {
"cln": "17237679",
"build": "b2130",
"buildDate": "2021-08-19T18:56:08.144Z",
"version": "1.0.0",
"branch": "trunk"
},
"epic-xmpp-api-v1-base": {
"cln": "5131a23c1470acbd9c94fae695ef7d899c1a41d6",
"build": "b3595",
"buildDate": "2019-07-30T09:11:06.587Z",
"version": "0.0.1",
"branch": "master"
},
"epic-common-core": {
"cln": "17909521",
"build": "3217",
"buildDate": "2021-10-25T18:41:12.486Z",
"version": "3.0",
"branch": "TRUNK"
}
}
};
Map<String, Object> hasUpdate(Context context) => {
"type": "NO_UPDATE"
};

View File

@@ -1,12 +1,15 @@
import 'dart:io'; import 'dart:io';
import 'game_type.dart';
class GameInstance { class GameInstance {
final Process gameProcess; final Process gameProcess;
final Process? launcherProcess; final Process? launcherProcess;
final Process? eacProcess; final Process? eacProcess;
bool tokenError; bool tokenError;
bool hasChildServer;
GameInstance(this.gameProcess, this.launcherProcess, this.eacProcess) GameInstance(this.gameProcess, this.launcherProcess, this.eacProcess, this.hasChildServer)
: tokenError = false; : tokenError = false;
void kill() { void kill() {

View File

@@ -19,14 +19,8 @@ enum GameType {
} }
String get name { String get name {
return this == GameType.client ? "Client" return this == GameType.client ? "Game client"
: this == GameType.server ? "Server" : this == GameType.server ? "Game server"
: "Headless Server"; : "Headless game server";
}
String get message {
return this == GameType.client ? "A fortnite client will be launched to play multiplayer games"
: this == GameType.server ? "A fortnite client will be launched to host multiplayer games"
: "A fortnite client will be launched in the background to host multiplayer games";
} }
} }

View File

@@ -19,7 +19,7 @@ enum ServerType {
} }
String get name { String get name {
return this == ServerType.embedded ? "Embedded" return this == ServerType.embedded ? "Embedded (Lawin)"
: this == ServerType.remote ? "Remote" : this == ServerType.remote ? "Remote"
: "Local"; : "Local";
} }

View File

@@ -1,5 +0,0 @@
enum TutorialPage {
start,
someoneElse,
yourOwn
}

View File

@@ -1,5 +1,3 @@
import 'dart:ui';
import 'package:bitsdojo_window/bitsdojo_window.dart' hide WindowBorder; import 'package:bitsdojo_window/bitsdojo_window.dart' hide WindowBorder;
import 'package:fluent_ui/fluent_ui.dart'; import 'package:fluent_ui/fluent_ui.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
@@ -19,7 +17,6 @@ import 'package:window_manager/window_manager.dart';
import '../controller/settings_controller.dart'; import '../controller/settings_controller.dart';
import '../model/server_type.dart'; import '../model/server_type.dart';
import '../model/tutorial_page.dart';
import 'info_page.dart'; import 'info_page.dart';
class HomePage extends StatefulWidget { class HomePage extends StatefulWidget {
@@ -30,10 +27,7 @@ class HomePage extends StatefulWidget {
} }
class _HomePageState extends State<HomePage> with WindowListener { class _HomePageState extends State<HomePage> with WindowListener {
static const double _headerSize = 48.0;
static const double _sectionSize = 100.0;
static const double _defaultPadding = 12.0; static const double _defaultPadding = 12.0;
static const int _headerButtonCount = 3;
final GameController _gameController = Get.find<GameController>(); final GameController _gameController = Get.find<GameController>();
final SettingsController _settingsController = Get.find<SettingsController>(); final SettingsController _settingsController = Get.find<SettingsController>();
@@ -46,18 +40,12 @@ class _HomePageState extends State<HomePage> with WindowListener {
final RxBool _focused = RxBool(true); final RxBool _focused = RxBool(true);
final RxInt _index = RxInt(0); final RxInt _index = RxInt(0);
bool _navigated = false; bool _navigated = false;
bool _shouldMaximize = false; bool _shouldMaximize = false;
@override @override
void initState() { void initState() {
windowManager.addListener(this); windowManager.addListener(this);
_searchController.addListener(_onSearch); _searchController.addListener(_onSearch);
_onEasyMode();
_settingsController.advancedMode.listen((advanced) {
_onEasyMode();
_index.value = _index.value + (advanced ? 1 : -1);
});
super.initState(); super.initState();
} }
@@ -73,15 +61,6 @@ class _HomePageState extends State<HomePage> with WindowListener {
.cast<NavigationPaneItem>(); .cast<NavigationPaneItem>();
} }
void _onEasyMode() {
if(_settingsController.advancedMode.value){
return;
}
_gameController.type.value = GameType.client;
_serverController.type.value = ServerType.embedded;
}
@override @override
void dispose() { void dispose() {
windowManager.removeListener(this); windowManager.removeListener(this);
@@ -100,6 +79,12 @@ class _HomePageState extends State<HomePage> with WindowListener {
_focused.value = false; _focused.value = false;
} }
@override
void onWindowResized() {
_settingsController.saveWindowSize();
super.onWindowResized();
}
@override @override
void onWindowMoved() { void onWindowMoved() {
_settingsController.saveWindowOffset(appWindow.position); _settingsController.saveWindowOffset(appWindow.position);
@@ -131,52 +116,52 @@ class _HomePageState extends State<HomePage> with WindowListener {
), ),
], ],
); );
}, }
); );
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) => Obx(() => Stack(
return NotificationListener<SizeChangedLayoutNotification>( children: [
onNotification: (notification) { NavigationView(
return _calculateSize(); paneBodyBuilder: (body) => Padding(
}, padding: const EdgeInsets.all(_defaultPadding),
child: SizeChangedLayoutNotifier( child: body
child: Obx(_getViewStack)
)
);
}
Widget _getViewStack() {
var view = _createNavigationView();
return Stack(
children: [
view,
if(_settingsController.displayType() == PaneDisplayMode.top)
Align(
alignment: Alignment.topRight,
child: WindowTitleBar(focused: _focused())
), ),
if(_settingsController.displayType() == PaneDisplayMode.top) appBar: NavigationAppBar(
_createTopDisplayGestures(view.pane?.items.length ?? 0), title: _draggableArea,
if(_focused() && isWin11) actions: WindowTitleBar(focused: _focused())
const WindowBorder() ),
] pane: NavigationPane(
); selected: _selectedIndex,
onChanged: _onIndexChanged,
displayMode: PaneDisplayMode.auto,
items: _items,
footerItems: _footerItems,
autoSuggestBox: _autoSuggestBox,
autoSuggestBoxReplacement: const Icon(FluentIcons.search),
),
onOpenSearch: () => _searchFocusNode.requestFocus(),
transitionBuilder: (child, animation) => child
),
if(_focused() && isWin11)
const WindowBorder()
]
));
void _onIndexChanged(int index) {
_index.value = index;
_navigated = true;
} }
Padding _createTopDisplayGestures(int size) => Padding( TextBox get _autoSuggestBox => TextBox(
padding: EdgeInsets.only( key: _searchKey,
left: _sectionSize * size, controller: _searchController,
right: _headerSize * _headerButtonCount, placeholder: 'Search',
), focusNode: _searchFocusNode
child: SizedBox(
height: _headerSize,
child: _createWindowGestures()
)
); );
GestureDetector _createWindowGestures({Widget? child}) => GestureDetector( GestureDetector get _draggableArea => GestureDetector(
onDoubleTap: () { onDoubleTap: () {
if(!_shouldMaximize){ if(!_shouldMaximize){
return; return;
@@ -187,89 +172,9 @@ class _HomePageState extends State<HomePage> with WindowListener {
}, },
onDoubleTapDown: (details) => _shouldMaximize = true, onDoubleTapDown: (details) => _shouldMaximize = true,
onHorizontalDragStart: (event) => appWindow.startDragging(), onHorizontalDragStart: (event) => appWindow.startDragging(),
onVerticalDragStart: (event) => appWindow.startDragging(), onVerticalDragStart: (event) => appWindow.startDragging()
child: child
); );
NavigationView _createNavigationView() {
return NavigationView(
paneBodyBuilder: (body) => _createPage(body),
pane: NavigationPane(
size: const NavigationPaneSize(
topHeight: _headerSize
),
selected: _selectedIndex,
onChanged: _onIndexChanged,
displayMode: _settingsController.displayType(),
items: _createItems(),
indicator: const EndNavigationIndicator(),
footerItems: _createFooterItems(),
header: _settingsController.displayType() != PaneDisplayMode.open ? null : const SizedBox(height: _defaultPadding),
autoSuggestBox: _createAutoSuggestBox(),
autoSuggestBoxReplacement: _settingsController.displayType() == PaneDisplayMode.top ? null : const Icon(FluentIcons.search),
),
onOpenSearch: () => _searchFocusNode.requestFocus(),
transitionBuilder: _settingsController.displayType() == PaneDisplayMode.top ? null : (child, animation) => child
);
}
void _onIndexChanged(int index) {
_index.value = index;
_navigated = true;
}
TextBox? _createAutoSuggestBox() {
if (_settingsController.displayType() == PaneDisplayMode.top) {
return null;
}
return TextBox(
key: _searchKey,
controller: _searchController,
placeholder: 'Search',
focusNode: _searchFocusNode
);
}
Widget _createPage(Widget? body) {
if(_settingsController.displayType() == PaneDisplayMode.top){
return Padding(
padding: const EdgeInsets.all(_defaultPadding),
child: body
);
}
return Column(
children: [
Row(
children: [
Expanded(
child: _createWindowGestures(
child: Container(
height: _headerSize,
color: Colors.transparent
)
)
),
WindowTitleBar(focused: _focused())
],
),
Expanded(
child: Padding(
padding: const EdgeInsets.only(
left: _defaultPadding,
right: _defaultPadding,
bottom: _defaultPadding
),
child: body
)
)
],
);
}
int? get _selectedIndex { int? get _selectedIndex {
var searchItems = _searchItems(); var searchItems = _searchItems();
if (searchItems == null) { if (searchItems == null) {
@@ -288,10 +193,9 @@ class _HomePageState extends State<HomePage> with WindowListener {
return indexOnScreen; return indexOnScreen;
} }
List<NavigationPaneItem> get _allItems => [..._createItems(), ..._createFooterItems()]; List<NavigationPaneItem> get _allItems => [..._items, ..._footerItems];
List<NavigationPaneItem> _createFooterItems() => searchValue.isNotEmpty ? [] : [ List<NavigationPaneItem> get _footerItems => searchValue.isNotEmpty ? [] : [
if(_settingsController.displayType() != PaneDisplayMode.top)
PaneItem( PaneItem(
title: const Text("Settings"), title: const Text("Settings"),
icon: const Icon(FluentIcons.settings), icon: const Icon(FluentIcons.settings),
@@ -299,19 +203,18 @@ class _HomePageState extends State<HomePage> with WindowListener {
) )
]; ];
List<NavigationPaneItem> _createItems() => _searchItems() ?? [ List<NavigationPaneItem> get _items => _searchItems() ?? [
PaneItem( PaneItem(
title: const Text("Home"), title: const Text("Home"),
icon: const Icon(FluentIcons.game), icon: const Icon(FluentIcons.game),
body: const LauncherPage() body: const LauncherPage()
), ),
if(_settingsController.advancedMode.value) PaneItem(
PaneItem( title: const Text("Backend"),
title: const Text("Backend"), icon: const Icon(FluentIcons.server_enviroment),
icon: const Icon(FluentIcons.server_enviroment), body: ServerPage()
body: ServerPage() ),
),
PaneItem( PaneItem(
title: const Text("Tutorial"), title: const Text("Tutorial"),
@@ -319,50 +222,15 @@ class _HomePageState extends State<HomePage> with WindowListener {
body: const InfoPage(), body: const InfoPage(),
onTap: _onTutorial onTap: _onTutorial
), ),
if(_settingsController.displayType() == PaneDisplayMode.top)
PaneItem(
title: const Text("Settings"),
icon: const Icon(FluentIcons.settings),
body: SettingsPage()
)
]; ];
void _onTutorial() { void _onTutorial() {
if(!_navigated){ if(!_navigated){
setState(() { setState(() => _settingsController.scrollingDistance = 0);
_settingsController.tutorialPage.value = TutorialPage.start;
_settingsController.scrollingDistance = 0;
});
} }
_navigated = false; _navigated = false;
} }
bool _calculateSize() {
WidgetsBinding.instance.addPostFrameCallback((_) {
_settingsController.saveWindowSize();
var width = window.physicalSize.width;
PaneDisplayMode? newType;
if (width <= 1000) {
newType = PaneDisplayMode.top;
} else if (width >= 1500) {
newType = PaneDisplayMode.open;
} else if (width > 1000) {
newType = PaneDisplayMode.compact;
}
if(newType == null || newType == _settingsController.displayType()){
return;
}
_settingsController.displayType.value = newType;
_searchItems.value = null;
_searchController.text = "";
});
return true;
}
String get searchValue => _searchController.text; String get searchValue => _searchController.text;
} }

View File

@@ -3,7 +3,7 @@ import 'package:flutter/foundation.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import '../controller/settings_controller.dart'; import '../controller/settings_controller.dart';
import '../model/tutorial_page.dart'; import '../widget/shared/fluent_card.dart';
class InfoPage extends StatefulWidget { class InfoPage extends StatefulWidget {
const InfoPage({Key? key}) : super(key: key); const InfoPage({Key? key}) : super(key: key);
@@ -36,6 +36,7 @@ class _InfoPageState extends State<InfoPage> {
"Once you are in game, click PLAY to enter in-game\n If this doesn't work open the Fortnite console by clicking the button above tab\n If nothing happens, make sure that your keyboard locale is set to English\n Type 'open TYPE_THE_IP' without the quotes, for example: open 85.182.12.1" "Once you are in game, click PLAY to enter in-game\n If this doesn't work open the Fortnite console by clicking the button above tab\n If nothing happens, make sure that your keyboard locale is set to English\n Type 'open TYPE_THE_IP' without the quotes, for example: open 85.182.12.1"
]; ];
final GlobalKey<NavigatorState> _navigatorKey = GlobalKey();
final SettingsController _settingsController = Get.find<SettingsController>(); final SettingsController _settingsController = Get.find<SettingsController>();
late final ScrollController _controller; late final ScrollController _controller;
@@ -55,17 +56,52 @@ class _InfoPageState extends State<InfoPage> {
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) => Navigator(
switch(_settingsController.tutorialPage()) { key: _navigatorKey,
case TutorialPage.start: initialRoute: "home",
return _createHomeScreen(); onGenerateRoute: (settings) {
case TutorialPage.someoneElse: var screen = _createScreen(settings.name);
return FluentPageRoute(
builder: (context) => screen,
settings: settings
);
},
);
Widget _createScreen(String? name) {
switch(name){
case "home":
return _homeScreen;
case "else":
return _createInstructions(false); return _createInstructions(false);
case TutorialPage.yourOwn: case "own":
return _createInstructions(true); return _createInstructions(true);
default:
throw Exception("Unknown page: $name");
} }
} }
Widget get _homeScreen => Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
_createCardWidget(
text: "Play on someone else's server",
description: "If one of your friends is hosting a game server, click here",
onClick: () => _navigatorKey.currentState?.pushNamed("else")
),
const SizedBox(
width: 8.0,
),
_createCardWidget(
text: "Host your own server",
description: "If you want to create your own server to invite your friends or to play around by yourself, click here",
onClick: () => _navigatorKey.currentState?.pushNamed("own")
)
]
);
SizedBox _createInstructions(bool own) { SizedBox _createInstructions(bool own) {
var titles = own ? _ownTitles : _elseTitles; var titles = own ? _ownTitles : _elseTitles;
var codeName = own ? "own" : "else"; var codeName = own ? "own" : "else";
@@ -76,8 +112,7 @@ class _InfoPageState extends State<InfoPage> {
padding: const EdgeInsets.only( padding: const EdgeInsets.only(
right: 20.0 right: 20.0
), ),
child: Card( child: FluentCard(
borderRadius: const BorderRadius.all(Radius.circular(12.0)),
child: ListTile( child: ListTile(
title: SelectableText("${index + 1}. ${titles[index]}"), title: SelectableText("${index + 1}. ${titles[index]}"),
subtitle: Padding( subtitle: Padding(
@@ -93,67 +128,42 @@ class _InfoPageState extends State<InfoPage> {
); );
} }
Widget _createHomeScreen() { Widget _createCardWidget({required String text, required String description, required Function() onClick}) => Expanded(
return Row( child: SizedBox(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
_createCardWidget(
text: "Play on someone else's server",
description: "If one of your friends is hosting a game server, click here",
onClick: () => setState(() => _settingsController.tutorialPage.value = TutorialPage.someoneElse)
),
const SizedBox(
width: 8.0,
),
_createCardWidget(
text: "Host your own server",
description: "If you want to create your own server to invite your friends or to play around by yourself, click here",
onClick: () => setState(() => _settingsController.tutorialPage.value = TutorialPage.yourOwn)
)
]
);
}
Widget _createCardWidget({required String text, required String description, required Function() onClick}) {
return Expanded(
child: SizedBox(
height: double.infinity, height: double.infinity,
child: MouseRegion( child: MouseRegion(
cursor: SystemMouseCursors.click, cursor: SystemMouseCursors.click,
child: GestureDetector( child: GestureDetector(
onTap: onClick, onTap: onClick,
child: Card( child: FluentCard(
child: Padding( child: Padding(
padding: const EdgeInsets.all(8.0), padding: const EdgeInsets.all(8.0),
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
Text( Text(
text, text,
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: const TextStyle( style: const TextStyle(
fontSize: 20.0, fontSize: 20.0,
fontWeight: FontWeight.bold fontWeight: FontWeight.bold
), ),
), ),
const SizedBox( const SizedBox(
height: 8.0, height: 8.0,
), ),
Text( Text(
description, description,
textAlign: TextAlign.center textAlign: TextAlign.center
), ),
], ],
)
) )
) )
) )
)
) )
) )
); );
}
} }

View File

@@ -1,6 +1,5 @@
import 'dart:async'; import 'dart:async';
import 'dart:io';
import 'package:fluent_ui/fluent_ui.dart'; import 'package:fluent_ui/fluent_ui.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
@@ -9,23 +8,18 @@ import 'package:get_storage/get_storage.dart';
import 'package:reboot_launcher/main.dart'; import 'package:reboot_launcher/main.dart';
import 'package:reboot_launcher/src/controller/build_controller.dart'; import 'package:reboot_launcher/src/controller/build_controller.dart';
import 'package:reboot_launcher/src/controller/game_controller.dart'; import 'package:reboot_launcher/src/controller/game_controller.dart';
import 'package:reboot_launcher/src/controller/server_controller.dart';
import 'package:reboot_launcher/src/controller/settings_controller.dart'; import 'package:reboot_launcher/src/controller/settings_controller.dart';
import 'package:reboot_launcher/src/dialog/dialog.dart'; import 'package:reboot_launcher/src/dialog/dialog.dart';
import 'package:reboot_launcher/src/model/reboot_download.dart'; import 'package:reboot_launcher/src/model/reboot_download.dart';
import 'package:reboot_launcher/src/util/os.dart'; import 'package:reboot_launcher/src/util/os.dart';
import 'package:reboot_launcher/src/widget/home/game_type_selector.dart'; import 'package:reboot_launcher/src/widget/home/game_type_selector.dart';
import 'package:reboot_launcher/src/widget/home/launch_button.dart'; import 'package:reboot_launcher/src/widget/home/launch_button.dart';
import 'package:reboot_launcher/src/widget/home/username_box.dart';
import 'package:reboot_launcher/src/widget/home/version_selector.dart'; import 'package:reboot_launcher/src/widget/home/version_selector.dart';
import 'package:reboot_launcher/src/widget/shared/file_selector.dart'; import 'package:reboot_launcher/src/widget/shared/setting_tile.dart';
import '../dialog/dialog_button.dart'; import '../dialog/dialog_button.dart';
import '../model/server_type.dart';
import '../util/checks.dart'; import '../util/checks.dart';
import '../util/reboot.dart'; import '../util/reboot.dart';
import '../widget/shared/smart_input.dart';
import 'home_page.dart';
class LauncherPage extends StatefulWidget { class LauncherPage extends StatefulWidget {
const LauncherPage( const LauncherPage(
@@ -38,7 +32,6 @@ class LauncherPage extends StatefulWidget {
class _LauncherPageState extends State<LauncherPage> { class _LauncherPageState extends State<LauncherPage> {
final GameController _gameController = Get.find<GameController>(); final GameController _gameController = Get.find<GameController>();
final ServerController _serverController = Get.find<ServerController>();
final SettingsController _settingsController = Get.find<SettingsController>(); final SettingsController _settingsController = Get.find<SettingsController>();
final BuildController _buildController = Get.find<BuildController>(); final BuildController _buildController = Get.find<BuildController>();
@@ -152,28 +145,98 @@ class _LauncherPageState extends State<LauncherPage> {
); );
Widget get _homeScreen => Column( Widget get _homeScreen => Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
if(_gameController.error) AnimatedSwitcher(
_updateError, duration: const Duration(milliseconds: 300),
UsernameBox(), child: _gameController.error ? _updateError : const SizedBox(),
Tooltip( ),
message: AnimatedSize(
"The hostname of the server that hosts the multiplayer matches", duration: const Duration(milliseconds: 300),
child: Obx(() => SmartInput( child: SizedBox(height: _gameController.error ? 16.0 : 0.0),
label: "Matchmaking Host", ),
placeholder: SettingTile(
"Type the hostname of the server that hosts the multiplayer matches", title: "Username",
controller: _settingsController.matchmakingIp, subtitle: "Enter the name that others will see once you are in-game",
validatorMode: AutovalidateMode.always, content: TextFormBox(
placeholder: "username",
controller: _gameController.username,
validator: checkMatchmaking, validator: checkMatchmaking,
enabled: _serverController.type() == ServerType.embedded) autovalidateMode: AutovalidateMode.always
) )
), ),
const VersionSelector(), const SizedBox(
if(_settingsController.advancedMode.value) height: 16.0,
GameTypeSelector(), ),
SettingTile(
title: "Matchmaking host",
subtitle: "Enter the IP address of the game server hosting the match",
content: TextFormBox(
placeholder: "ip:port",
controller: _settingsController.matchmakingIp,
validator: checkMatchmaking,
autovalidateMode: AutovalidateMode.always
),
expandedContent: [
ListTile(
title: const Text(
"Automatically start a game server",
style: TextStyle(
fontSize: 14
),
),
subtitle: const Text("Choose whether an headless server should be automatically started when matchmaking is on localhost"),
trailing: Obx(() => ToggleSwitch(
checked: _gameController.autostartGameServer(),
onChanged: (value) => _gameController.autostartGameServer.value = value
))
),
],
),
const SizedBox(
height: 16.0,
),
SettingTile(
title: "Version",
subtitle: "Select the version of Fortnite you want to play with your friends",
content: const VersionSelector(),
expandedContent: [
ListTile(
title: const Text(
"Add a version from this PC's local storage",
style: TextStyle(
fontSize: 14
),
),
trailing: Button(
onPressed: () => VersionSelector.openAddDialog(context),
child: const Text("Add build "),
),
),
ListTile(
title: const Text(
"Download any version from the cloud",
style: TextStyle(
fontSize: 14
),
),
trailing: Button(
onPressed: () => VersionSelector.openDownloadDialog(context),
child: const Text("Download"),
),
),
]
),
const SizedBox(
height: 16.0,
),
SettingTile(
title: "Instance type",
subtitle: "Select the type of instance you want to launch",
content: GameTypeSelector()
),
const Expanded(child: SizedBox()),
const LaunchButton() const LaunchButton()
], ],
); );

View File

@@ -6,36 +6,78 @@ import 'package:reboot_launcher/src/widget/server/server_type_selector.dart';
import 'package:reboot_launcher/src/widget/server/port_input.dart'; import 'package:reboot_launcher/src/widget/server/port_input.dart';
import 'package:reboot_launcher/src/widget/server/server_button.dart'; import 'package:reboot_launcher/src/widget/server/server_button.dart';
import '../model/server_type.dart';
import '../widget/shared/setting_tile.dart';
class ServerPage extends StatelessWidget { class ServerPage extends StatelessWidget {
final ServerController _serverController = Get.find<ServerController>(); final ServerController _serverController = Get.find<ServerController>();
ServerPage({Key? key}) : super(key: key); ServerPage({Key? key}) : super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Obx(() => Column( return Obx(() => Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
if(_serverController.warning.value) const SizedBox(
GestureDetector( width: double.infinity,
onTap: () => _serverController.warning.value = false, child: InfoBar(
child: const MouseRegion( title: Text("The backend server handles authentication and parties, not game hosting"),
cursor: SystemMouseCursors.click, severity: InfoBarSeverity.info
child: SizedBox(
width: double.infinity,
child: InfoBar(
title: Text("The backend server handles authentication and parties, not game hosting"),
severity: InfoBarSeverity.info
),
),
),
), ),
HostInput(), ),
PortInput(), const SizedBox(
ServerTypeSelector(), height: 16.0,
),
SettingTile(
title: "Host",
subtitle: "Enter the host of the backend server",
content: TextFormBox(
placeholder: "host",
controller: _serverController.host,
enabled: _isRemote
)
),
const SizedBox(
height: 16.0,
),
SettingTile(
title: "Host",
subtitle: "Enter the port of the backend server",
content: TextFormBox(
placeholder: "host",
controller: _serverController.port,
enabled: _isRemote
)
),
const SizedBox(
height: 16.0,
),
SettingTile(
title: "Type",
subtitle: "Select the type of backend to use",
content: ServerTypeSelector()
),
const SizedBox(
height: 16.0,
),
Align(
alignment: Alignment.bottomCenter,
child: SettingTile(
title: "Login automatically",
subtitle: "Choose whether the game client should login automatically using random credentials",
contentWidth: null,
content: Obx(() => ToggleSwitch(
checked: _serverController.loginAutomatically(),
onChanged: (value) => _serverController.loginAutomatically.value = value
))
),
),
const Expanded(child: SizedBox()),
const ServerButton() const ServerButton()
] ]
)); ));
} }
bool get _isRemote => _serverController.type.value == ServerType.remote;
} }

View File

@@ -1,121 +1,110 @@
import 'package:fluent_ui/fluent_ui.dart'; import 'package:fluent_ui/fluent_ui.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:reboot_launcher/src/controller/game_controller.dart';
import 'package:reboot_launcher/src/controller/settings_controller.dart'; import 'package:reboot_launcher/src/controller/settings_controller.dart';
import 'package:reboot_launcher/src/util/os.dart';
import 'package:reboot_launcher/src/widget/shared/smart_switch.dart';
import 'package:url_launcher/url_launcher.dart'; import 'package:url_launcher/url_launcher.dart';
import '../util/checks.dart'; import '../util/checks.dart';
import '../widget/setting/url_updater.dart'; import '../widget/shared/setting_tile.dart';
import '../widget/shared/file_selector.dart';
class SettingsPage extends StatelessWidget { class SettingsPage extends StatelessWidget {
final GameController _gameController = Get.find<GameController>();
final SettingsController _settingsController = Get.find<SettingsController>(); final SettingsController _settingsController = Get.find<SettingsController>();
SettingsPage({Key? key}) : super(key: key); SettingsPage({Key? key}) : super(key: key);
@override @override
Widget build(BuildContext context) => Widget build(BuildContext context) => Column(
_settingsController.advancedMode.value ? _advancedSettings : _easySettings;
Widget get _advancedSettings => Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
const RebootUpdaterInput(), SettingTile(
_createFileSelector(), title: "File settings",
_createConsoleSelector(), subtitle: "This section contains all the settings related to files used by Fortnite",
_createGameSelector(), expandedContent: [
_createVersionInfo(), _createFileSetting(
_createAdvancedSwitch() title: "Game server",
] description: "This file is injected to create a game server to host matches",
); controller: _settingsController.rebootDll
),
Widget get _easySettings => SizedBox.expand( _createFileSetting(
child: Column( title: "Unreal engine console",
mainAxisAlignment: MainAxisAlignment.center, description: "This file is injected to unlock the Unreal Engine Console in-game",
children: [ controller: _settingsController.consoleDll
const CircleAvatar( ),
radius: 48, _createFileSetting(
backgroundImage: AssetImage("assets/images/auties.png")), title: "Authentication patcher",
description: "This file is injected to redirect all HTTP requests to the local backend",
controller: _settingsController.authDll
),
],
),
const SizedBox( const SizedBox(
height: 16.0, height: 16.0,
), ),
const Text("Made by Auties00"), SettingTile(
const SizedBox( title: "Automatic updates",
height: 4.0, subtitle: "Choose whether the launcher and its files should be automatically updated",
contentWidth: null,
content: Obx(() => ToggleSwitch(
checked: _settingsController.autoUpdate(),
onChanged: (value) => _settingsController.autoUpdate.value = value
))
), ),
_versionText,
const SizedBox( const SizedBox(
height: 8.0, height: 16.0,
), ),
Button( SettingTile(
child: const Text("Switch to advanced mode"), title: "Custom launch arguments",
onPressed: () => _settingsController.advancedMode.value = true subtitle: "Enter additional arguments to use when launching the game",
) content: TextFormBox(
], placeholder: "args",
), controller: _gameController.customLaunchArgs,
)
),
const SizedBox(
height: 16.0,
),
SettingTile(
title: "Create a bug report",
subtitle: "Help me fix bugs by reporting them",
content: Button(
onPressed: () => launchUrl(Uri.parse("https://discord.com/channels/998020695223193670/1031262639457828910")),
child: const Text("Report a bug"),
)
),
]
); );
Widget _createAdvancedSwitch() => SmartSwitch( Widget _createFileSetting({required String title, required String description, required TextEditingController controller}) => ListTile(
label: "Advanced Mode", title: Text(title),
value: _settingsController.advancedMode subtitle: Text(description),
); trailing: SizedBox(
width: 256,
Widget _createVersionInfo() => Column( child: Row(
crossAxisAlignment: CrossAxisAlignment.start, children: [
children: [ Expanded(
const Text("Version Status"), child: TextFormBox(
const SizedBox(height: 6.0), placeholder: "path",
Button( controller: controller,
child: _versionText, validator: checkDll,
onPressed: () => launchUrl(safeBinariesDirectory.uri) autovalidateMode: AutovalidateMode.always
) ),
], ),
); const SizedBox(
width: 8.0,
Widget _createGameSelector() => Tooltip( ),
message: "The dll that is injected to make the game work", Padding(
child: FileSelector( padding: const EdgeInsets.only(bottom: 21.0),
label: "Cranium DLL", child: Button(
placeholder: onPressed: () { },
"Type the path to the dll used for authentication", child: const Icon(FluentIcons.open_folder_horizontal),
controller: _settingsController.authDll, ),
windowTitle: "Select a dll", )
folder: false, ],
extension: "dll", )
validator: checkDll,
validatorMode: AutovalidateMode.always
) )
); );
Widget _createConsoleSelector() => Tooltip(
message: "The dll that is injected when a client is launched",
child: FileSelector(
label: "Console DLL",
placeholder: "Type the path to the console dll",
controller: _settingsController.consoleDll,
windowTitle: "Select a dll",
folder: false,
extension: "dll",
validator: checkDll,
validatorMode: AutovalidateMode.always),
);
Widget _createFileSelector() => Tooltip(
message: "The dll that is injected when a server is launched",
child: FileSelector(
label: "Reboot DLL",
placeholder: "Type the path to the reboot dll",
controller: _settingsController.rebootDll,
windowTitle: "Select a dll",
folder: false,
extension: "dll",
validator: checkDll,
validatorMode: AutovalidateMode.always),
);
Widget get _versionText => const Text("6.4${kDebugMode ? '-DEBUG' : '-RELEASE'}");
} }

View File

@@ -1,19 +1,13 @@
import 'dart:io'; import 'dart:io';
import 'dart:math';
import 'package:html/parser.dart' show parse; import 'package:html/parser.dart' show parse;
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
import 'package:process_run/shell.dart'; import 'package:process_run/shell.dart';
import 'package:reboot_launcher/src/model/fortnite_build.dart'; import 'package:reboot_launcher/src/model/fortnite_build.dart';
import 'package:reboot_launcher/src/util/time.dart';
import 'package:reboot_launcher/src/util/version.dart' as parser; import 'package:reboot_launcher/src/util/version.dart' as parser;
import 'package:version/version.dart';
import 'os.dart'; import 'os.dart';
const _userAgent =
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.0.0 Safari/537.36";
final _manifestSourceUrl = Uri.parse( final _manifestSourceUrl = Uri.parse(
"https://github.com/VastBlast/FortniteManifestArchive/blob/main/README.md"); "https://github.com/VastBlast/FortniteManifestArchive/blob/main/README.md");
@@ -67,56 +61,3 @@ Future<Process> downloadManifestBuild(
return process; return process;
} }
Future<void> downloadArchiveBuild(String archiveUrl, String destination,
Function(double, String) onProgress, Function() onDecompress) async {
var uuid = Random.secure().nextInt(1000000);
var extension = archiveUrl.substring(archiveUrl.lastIndexOf("."));
var tempFile = File(
"$destination\\.temp\\FortniteBuild$uuid$extension"
);
await tempFile.parent.create(recursive: true);
try {
var client = http.Client();
var request = http.Request("GET", Uri.parse(archiveUrl));
request.headers["User-Agent"] = _userAgent;
var response = await client.send(request);
if (response.statusCode != 200) {
throw Exception("Erroneous status code: ${response.statusCode}");
}
var startTime = DateTime.now();
var lastRemaining = -1;
var length = response.contentLength!;
var received = 0;
var sink = tempFile.openWrite();
var lastEta = toETA(0);
await response.stream.map((entry) {
received += entry.length;
var percentage = (received / length) * 100;
var elapsed = DateTime.now().difference(startTime).inMilliseconds;
var newRemaining = (elapsed * length / received - elapsed).round();
if(lastRemaining < 0 || lastRemaining - newRemaining <= -10000 || lastRemaining > newRemaining) {
lastEta = toETA(lastRemaining = newRemaining);
}
onProgress(percentage, lastEta);
return entry;
}).pipe(sink);
onDecompress();
var output = Directory(destination);
await output.create(recursive: true);
await loadBinary("winrar.exe", true);
var shell = Shell(
commandVerbose: false,
commentVerbose: false,
workingDirectory: safeBinariesDirectory.path
);
await shell.run("./winrar.exe x \"${tempFile.path}\" *.* \"${output.path}\"");
} finally {
if (await tempFile.parent.exists()) {
tempFile.parent.delete(recursive: true);
}
}
}

View File

@@ -67,7 +67,7 @@ Directory get safeBinariesDirectory =>
Directory("${Platform.environment["UserProfile"]}\\.reboot_launcher"); Directory("${Platform.environment["UserProfile"]}\\.reboot_launcher");
Directory get embeddedBackendDirectory => Directory get embeddedBackendDirectory =>
Directory("${safeBinariesDirectory.path}\\backend"); Directory("${safeBinariesDirectory.path}\\backend-lawin");
File loadEmbedded(String file) { File loadEmbedded(String file) {
var safeBinary = File("${embeddedBackendDirectory.path}\\$file"); var safeBinary = File("${embeddedBackendDirectory.path}\\$file");

View File

@@ -1,6 +1,8 @@
import 'dart:convert'; import 'dart:convert';
import 'dart:io'; import 'dart:io';
import 'package:archive/archive_io.dart';
import 'package:ini/ini.dart';
import 'package:process_run/shell.dart'; import 'package:process_run/shell.dart';
import 'package:reboot_launcher/src/model/game_type.dart'; import 'package:reboot_launcher/src/model/game_type.dart';
import 'package:reboot_launcher/src/model/server_type.dart'; import 'package:reboot_launcher/src/model/server_type.dart';
@@ -12,6 +14,41 @@ import 'package:http/http.dart' as http;
final serverLogFile = File("${Platform.environment["UserProfile"]}\\.reboot_launcher\\server.txt"); final serverLogFile = File("${Platform.environment["UserProfile"]}\\.reboot_launcher\\server.txt");
Future<void> writeMatchmakingIp(String text) async {
var file = File("${embeddedBackendDirectory.path}\\Config\\config.ini");
if(!file.existsSync()){
return;
}
var splitIndex = text.indexOf(":");
var ip = splitIndex != -1 ? text.substring(0, splitIndex) : text;
var port = splitIndex != -1 ? text.substring(splitIndex + 1) : "7777";
var config = Config.fromString(file.readAsStringSync());
config.set("GameServer", "ip", ip);
config.set("GameServer", "port", port);
file.writeAsStringSync(config.toString());
}
Future<void> startServer() async {
if(!embeddedBackendDirectory.existsSync()){
var serverZip = await loadBinary("server.zip", true);
await extractFileToDisk(serverZip.path, embeddedBackendDirectory.path);
}
var process = await Process.start(
"${embeddedBackendDirectory.path}\\lawinserver-win.exe",
[],
workingDirectory: embeddedBackendDirectory.path
);
process.outLines.forEach((element) => serverLogFile.writeAsStringSync("$element\n", mode: FileMode.append));
process.errLines.forEach((element) => serverLogFile.writeAsStringSync("$element\n", mode: FileMode.append));
}
Future<void> stopServer() async {
var releaseBat = await loadBinary("kill_both_ports.bat", false);
await Process.run(releaseBat.path, []);
}
Future<bool> isLawinPortFree() async { Future<bool> isLawinPortFree() async {
return http.get(Uri.parse("http://127.0.0.1:3551/unknown")) return http.get(Uri.parse("http://127.0.0.1:3551/unknown"))
.timeout(const Duration(milliseconds: 500)) .timeout(const Duration(milliseconds: 500))
@@ -44,7 +81,7 @@ Future<void> freeMatchmakerPort() async {
} }
} }
List<String> createRebootArgs(String username, GameType type) { List<String> createRebootArgs(String username, GameType type, String additionalArgs) {
var args = [ var args = [
"-epicapp=Fortnite", "-epicapp=Fortnite",
"-epicenv=Prod", "-epicenv=Prod",
@@ -73,6 +110,10 @@ List<String> createRebootArgs(String username, GameType type) {
]); ]);
} }
if(additionalArgs.isNotEmpty){
args.addAll(additionalArgs.split(" "));
}
return args; return args;
} }

View File

@@ -12,33 +12,16 @@ class GameTypeSelector extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Tooltip( return Obx(() => DropDownButton(
message: "The type of Fortnite instance to launch", leading: Text(_gameController.type.value.name),
child: _createAdvancedSelector(), items: GameType.values
); .map((type) => _createItem(type))
.toList()
));
} }
Widget _createAdvancedSelector() => InfoLabel(
label: "Type",
child: SizedBox(
width: double.infinity,
child: Obx(() => DropDownButton(
leading: Text(_gameController.type.value.name),
items: GameType.values
.map((type) => _createItem(type))
.toList())
)
)
);
MenuFlyoutItem _createItem(GameType type) => MenuFlyoutItem( MenuFlyoutItem _createItem(GameType type) => MenuFlyoutItem(
text: SizedBox( text: Text(type.name),
width: double.infinity,
child: Tooltip(
message: type.message,
child: Text(type.name)
)
),
onPressed: () { onPressed: () {
_gameController.type(type); _gameController.type(type);
_gameController.started.value = _gameController.currentGameInstance != null; _gameController.started.value = _gameController.currentGameInstance != null;

View File

@@ -25,9 +25,7 @@ import 'package:reboot_launcher/src/controller/settings_controller.dart';
import 'package:reboot_launcher/src/dialog/snackbar.dart'; import 'package:reboot_launcher/src/dialog/snackbar.dart';
import 'package:reboot_launcher/src/model/game_instance.dart'; import 'package:reboot_launcher/src/model/game_instance.dart';
import '../../page/home_page.dart';
import '../../util/process.dart'; import '../../util/process.dart';
import '../shared/smart_check_box.dart';
class LaunchButton extends StatefulWidget { class LaunchButton extends StatefulWidget {
const LaunchButton( const LaunchButton(
@@ -66,21 +64,24 @@ class _LaunchButtonState extends State<LaunchButton> {
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) => Align(
return Align( alignment: AlignmentDirectional.bottomCenter,
alignment: AlignmentDirectional.bottomCenter, child: SizedBox(
child: SizedBox( width: double.infinity,
width: double.infinity, child: Obx(() => SizedBox(
child: Obx(() => Tooltip( height: 48,
message: _gameController.started() ? "Close the running Fortnite instance" : "Launch a new Fortnite instance", child: Button(
child: Button( child: Align(
onPressed: () => _start(_gameController.type()), alignment: Alignment.center,
child: Text(_gameController.started() ? "Close" : "Launch") child: Text(
_gameController.started() ? "Close fortnite" : "Launch fortnite"
),
), ),
)), onPressed: () => _start(_gameController.type()),
), ),
); )),
} ),
);
void _start(GameType type) async { void _start(GameType type) async {
if (_gameController.started()) { if (_gameController.started()) {
@@ -99,7 +100,7 @@ class _LaunchButtonState extends State<LaunchButton> {
showMessage("No username: expecting self sign in"); showMessage("No username: expecting self sign in");
} }
if (_gameController.selectedVersionObs.value == null) { if (_gameController.selectedVersion == null) {
showMessage("No version is selected"); showMessage("No version is selected");
_onStop(type); _onStop(type);
return; return;
@@ -115,7 +116,7 @@ class _LaunchButtonState extends State<LaunchButton> {
_fail = false; _fail = false;
await _resetLogFile(); await _resetLogFile();
var version = _gameController.selectedVersionObs.value!; var version = _gameController.selectedVersion!;
var gamePath = version.executable?.path; var gamePath = version.executable?.path;
if(gamePath == null){ if(gamePath == null){
showMissingBuildError(version); showMissingBuildError(version);
@@ -132,8 +133,8 @@ class _LaunchButtonState extends State<LaunchButton> {
await compute(patchMatchmaking, version.executable!); await compute(patchMatchmaking, version.executable!);
await compute(patchHeadless, version.executable!); await compute(patchHeadless, version.executable!);
await _startMatchMakingServer(); var automaticallyStartedServer = await _startMatchMakingServer();
await _startGameProcesses(version, type); await _startGameProcesses(version, type, automaticallyStartedServer);
if(type == GameType.headlessServer){ if(type == GameType.headlessServer){
await _showServerLaunchingWarning(); await _showServerLaunchingWarning();
@@ -145,96 +146,40 @@ class _LaunchButtonState extends State<LaunchButton> {
} }
} }
Future<void> _startGameProcesses(FortniteVersion version, GameType type) async { Future<void> _startGameProcesses(FortniteVersion version, GameType type, bool hasChildServer) async {
var launcherProcess = await _createLauncherProcess(version); var launcherProcess = await _createLauncherProcess(version);
var eacProcess = await _createEacProcess(version); var eacProcess = await _createEacProcess(version);
var gameProcess = await _createGameProcess(version.executable!.path, type); var gameProcess = await _createGameProcess(version.executable!.path, type);
_gameController.gameInstancesMap[type] = GameInstance(gameProcess, launcherProcess, eacProcess); _gameController.gameInstancesMap[type] = GameInstance(gameProcess, launcherProcess, eacProcess, hasChildServer);
_injectOrShowError(Injectable.cranium, type); _injectOrShowError(Injectable.cranium, type);
} }
Future<void> _startMatchMakingServer() async { Future<bool> _startMatchMakingServer() async {
if(_gameController.type() != GameType.client){ if(_gameController.type() != GameType.client){
return; return false;
} }
var matchmakingIp = _settingsController.matchmakingIp.text; var matchmakingIp = _settingsController.matchmakingIp.text;
if(!matchmakingIp.contains("127.0.0.1") && !matchmakingIp.contains("localhost")) { if(!matchmakingIp.contains("127.0.0.1") && !matchmakingIp.contains("localhost")) {
return; return false;
} }
var headlessServer = _gameController.gameInstancesMap[GameType.headlessServer] != null; if(!_gameController.autostartGameServer()){
var server = _gameController.gameInstancesMap[GameType.server] != null; return false;
if(headlessServer || server){
return;
} }
var result = await _askToStartMatchMakingServer(); var version = _gameController.selectedVersion!;
if(result != true){
return;
}
var version = _gameController.selectedVersionObs.value!;
await _startGameProcesses( await _startGameProcesses(
version, version,
GameType.headlessServer GameType.headlessServer,
false
); );
} return true;
Future<bool> _askToStartMatchMakingServer() async {
if(_settingsController.doNotAskAgain()) {
return _settingsController.automaticallyStartMatchmaker();
}
var controller = CheckboxController();
var result = await showDialog<bool>(
context: appKey.currentContext!,
builder: (context) =>
ContentDialog(
content: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(
width: double.infinity,
child: Text(
"The matchmaking ip is set to the local machine, but no server is running. "
"If you want to start a match for your friends or just test out Reboot, you need to start a server, either now from this prompt or later manually.",
textAlign: TextAlign.start,
)
),
const SizedBox(height: 12.0),
SmartCheckBox(
controller: controller,
content: const Text("Don't ask again")
)
],
),
actions: [
Button(
onPressed: () => Navigator.of(context).pop(false),
child: const Text('Ignore'),
),
FilledButton(
onPressed: () => Navigator.of(context).pop(true),
child: const Text('Start a server'),
)
],
)
);
_settingsController.doNotAskAgain.value = controller.value;
if(result != null){
_settingsController.automaticallyStartMatchmaker.value = result;
}
return result ?? false;
} }
Future<Process> _createGameProcess(String gamePath, GameType type) async { Future<Process> _createGameProcess(String gamePath, GameType type) async {
var gameProcess = await Process.start(gamePath, createRebootArgs(_gameController.username.text, type)); var gameArgs = createRebootArgs(_gameController.username.text, type, _gameController.customLaunchArgs.text);
var gameProcess = await Process.start(gamePath, gameArgs);
gameProcess gameProcess
..exitCode.then((_) => _onEnd(type)) ..exitCode.then((_) => _onEnd(type))
..outLines.forEach((line) => _onGameOutput(line, type)) ..outLines.forEach((line) => _onGameOutput(line, type))
@@ -369,9 +314,21 @@ class _LaunchButtonState extends State<LaunchButton> {
_start(type); _start(type);
} }
void _onStop(GameType type) { void _onStop(GameType? type) {
_gameController.gameInstancesMap[type]?.kill(); if(type == null){
_gameController.gameInstancesMap.remove(type); return;
}
var value = _gameController.gameInstancesMap[type];
if(value != null){
if(value.hasChildServer){
_onStop(GameType.headlessServer);
}
value.kill();
_gameController.gameInstancesMap.remove(type);
}
if(type == _gameController.type()) { if(type == _gameController.type()) {
_gameController.started.value = false; _gameController.started.value = false;
} }

View File

@@ -1,23 +0,0 @@
import 'package:fluent_ui/fluent_ui.dart';
import 'package:get/get.dart';
import 'package:reboot_launcher/src/controller/game_controller.dart';
import 'package:reboot_launcher/src/model/game_type.dart';
import 'package:reboot_launcher/src/widget/shared/smart_input.dart';
class UsernameBox extends StatelessWidget {
final GameController _gameController = Get.find<GameController>();
UsernameBox({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Obx(() => Tooltip(
message: _gameController.type.value != GameType.client ? "The username of the game hoster" : "The in-game username of your player",
child: SmartInput(
label: "Username",
placeholder: "Type your ${_gameController.type.value != GameType.client ? 'hosting' : "in-game"} username",
controller: _gameController.username
),
));
}
}

View File

@@ -19,6 +19,19 @@ import '../shared/file_selector.dart';
class VersionSelector extends StatefulWidget { class VersionSelector extends StatefulWidget {
const VersionSelector({Key? key}) : super(key: key); const VersionSelector({Key? key}) : super(key: key);
static void openDownloadDialog(BuildContext context) async {
await showDialog<bool>(
context: context,
builder: (dialogContext) => const AddServerVersion()
);
}
static void openAddDialog(BuildContext context) async {
await showDialog<bool>(
context: context,
builder: (context) => AddLocalVersion());
}
@override @override
State<VersionSelector> createState() => _VersionSelectorState(); State<VersionSelector> createState() => _VersionSelectorState();
} }
@@ -26,79 +39,44 @@ class VersionSelector extends StatefulWidget {
class _VersionSelectorState extends State<VersionSelector> { class _VersionSelectorState extends State<VersionSelector> {
final GameController _gameController = Get.find<GameController>(); final GameController _gameController = Get.find<GameController>();
final CheckboxController _deleteFilesController = CheckboxController(); final CheckboxController _deleteFilesController = CheckboxController();
final FlyoutController _flyoutController = FlyoutController();
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) => Obx(() => _createOptionsMenu(
return InfoLabel( version: _gameController.selectedVersion,
label: "Version", close: false,
child: Align( child: FlyoutTarget(
alignment: AlignmentDirectional.centerStart, controller: _flyoutController,
child: Row( child: DropDownButton(
children: [ leading: Text(_gameController.selectedVersion?.name ?? "Select a version"),
Expanded(child: _createSelector(context)), items: _createSelectorItems(context)
const SizedBox(
width: 16,
),
Tooltip(
message: "Add a local fortnite build to the versions list",
child: Button(
child: const Icon(FluentIcons.open_file),
onPressed: () => _openAddLocalVersionDialog(context)),
),
const SizedBox(
width: 16,
),
Tooltip(
message: "Download a fortnite build from the archive",
child: Button(
child: const Icon(FluentIcons.download),
onPressed: () => _openDownloadVersionDialog(context)),
),
],
)));
}
Widget _createSelector(BuildContext context) {
return Tooltip(
message: "The version of Fortnite to launch",
child: Obx(() => _createOptionsMenu(
version: _gameController.selectedVersionObs(),
close: false,
child: DropDownButton(
leading: Text(_gameController.selectedVersionObs.value?.name
?? "Select a version"),
items: _createSelectorItems(context)
)
))
);
}
List<MenuFlyoutItem> _createSelectorItems(BuildContext context) {
return _gameController.hasNoVersions ? [_createDefaultVersionItem()]
: _gameController.versions.value
.map((version) => _createVersionItem(context, version))
.toList();
}
MenuFlyoutItem _createVersionItem(BuildContext context, FortniteVersion version) {
return MenuFlyoutItem(
text: _createOptionsMenu(
version: version,
close: true,
child: SizedBox(
width: double.infinity,
child: Text(version.name)
), ),
), )
onPressed: () => _gameController.selectedVersion = version ));
);
}
Widget _createOptionsMenu({required FortniteVersion? version, required bool close, required Widget child}) { List<MenuFlyoutItem> _createSelectorItems(BuildContext context) => _gameController.hasNoVersions ? [_createDefaultVersionItem()]
return Listener( : _gameController.versions.value
.map((version) => _createVersionItem(context, version))
.toList();
MenuFlyoutItem _createDefaultVersionItem() => MenuFlyoutItem(
text: const SizedBox(
width: double.infinity, child: Text("No versions available. Add it using the buttons on the right.")),
trailing: const Expanded(child: SizedBox()),
onPressed: () {});
MenuFlyoutItem _createVersionItem(BuildContext context, FortniteVersion version) => MenuFlyoutItem(
text: _createOptionsMenu(
version: version,
close: true,
child: Text(version.name),
),
onPressed: () => _gameController.selectedVersion = version
);
Widget _createOptionsMenu({required FortniteVersion? version, required bool close, required Widget child}) => Listener(
onPointerDown: (event) async { onPointerDown: (event) async {
if (event.kind != PointerDeviceKind.mouse || if (event.kind != PointerDeviceKind.mouse || event.buttons != kSecondaryMouseButton) {
event.buttons != kSecondaryMouseButton) {
return; return;
} }
@@ -106,43 +84,19 @@ class _VersionSelectorState extends State<VersionSelector> {
return; return;
} }
await _openMenu(context, version, event.position, close); var result = await _flyoutController.showFlyout<ContextualOption?>(
builder: (context) => MenuFlyout(
items: ContextualOption.values
.map((entry) => _createOption(context, entry))
.toList()
)
);
_handleResult(result, version, close);
}, },
child: child child: child
); );
}
MenuFlyoutItem _createDefaultVersionItem() { void _handleResult(ContextualOption? result, FortniteVersion version, bool close) async {
return MenuFlyoutItem(
text: const SizedBox(
width: double.infinity, child: Text("No versions available. Add it using the buttons on the right.")),
trailing: const Expanded(child: SizedBox()),
onPressed: () {});
}
void _openDownloadVersionDialog(BuildContext context) async {
await showDialog<bool>(
context: context,
builder: (dialogContext) => const AddServerVersion()
);
}
void _openAddLocalVersionDialog(BuildContext context) async {
await showDialog<bool>(
context: context,
builder: (context) => AddLocalVersion());
}
Future<void> _openMenu(
BuildContext context, FortniteVersion version, Offset offset, bool close) async {
var controller = FlyoutController();
var result = await controller.showFlyout(
builder: (context) => MenuFlyout(
items: ContextualOption.values
.map((entry) => _createOption(context, entry))
.toList()
)
);
switch (result) { switch (result) {
case ContextualOption.openExplorer: case ContextualOption.openExplorer:
if(!mounted){ if(!mounted){
@@ -156,7 +110,6 @@ class _VersionSelectorState extends State<VersionSelector> {
launchUrl(version.location.uri) launchUrl(version.location.uri)
.onError((error, stackTrace) => _onExplorerError()); .onError((error, stackTrace) => _onExplorerError());
break; break;
case ContextualOption.modify: case ContextualOption.modify:
if(!mounted){ if(!mounted){
return; return;
@@ -168,7 +121,6 @@ class _VersionSelectorState extends State<VersionSelector> {
await _openRenameDialog(context, version); await _openRenameDialog(context, version);
break; break;
case ContextualOption.delete: case ContextualOption.delete:
if(!mounted){ if(!mounted){
return; return;
@@ -184,8 +136,8 @@ class _VersionSelectorState extends State<VersionSelector> {
} }
_gameController.removeVersion(version); _gameController.removeVersion(version);
if (_gameController.selectedVersionObs.value?.name == version.name || _gameController.hasNoVersions) { if (_gameController.selectedVersion?.name == version.name || _gameController.hasNoVersions) {
_gameController.selectedVersionObs.value = null; _gameController.selectedVersion = null;
} }
if (_deleteFilesController.value && await version.location.exists()) { if (_deleteFilesController.value && await version.location.exists()) {
@@ -193,7 +145,6 @@ class _VersionSelectorState extends State<VersionSelector> {
} }
break; break;
default: default:
break; break;
} }
@@ -276,7 +227,6 @@ class _VersionSelectorState extends State<VersionSelector> {
), ),
FileSelector( FileSelector(
label: "Location",
placeholder: "Type the new game folder", placeholder: "Type the new game folder",
windowTitle: "Select game folder", windowTitle: "Select game folder",
controller: pathController, controller: pathController,

View File

@@ -15,49 +15,32 @@ class _ServerButtonState extends State<ServerButton> {
final ServerController _serverController = Get.find<ServerController>(); final ServerController _serverController = Get.find<ServerController>();
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) => Align(
return Align( alignment: AlignmentDirectional.bottomCenter,
alignment: AlignmentDirectional.bottomCenter, child: SizedBox(
child: SizedBox( width: double.infinity,
width: double.infinity, child: Obx(() => SizedBox(
child: Obx(() => Tooltip( height: 48,
message: _helpMessage, child: Button(
child: Button( child: Align(
onPressed: () async => _serverController.toggle(), alignment: Alignment.center,
child: Text(_buttonText())), child: Text(_buttonText),
)), ),
), onPressed: () => _serverController.toggle()
); ),
} )),
),
);
String _buttonText() { String get _buttonText {
if(_serverController.type.value == ServerType.local){ if(_serverController.type.value == ServerType.local){
return "Check"; return "Check backend";
} }
if(_serverController.started.value){ if(_serverController.started.value){
return "Stop"; return "Stop backend";
} }
return "Start"; return "Start backend";
}
String get _helpMessage {
switch(_serverController.type.value){
case ServerType.embedded:
if (_serverController.started.value) {
return "Stop the backend server currently running";
}
return "Start a new local backend server";
case ServerType.remote:
if (_serverController.started.value) {
return "Stop the reverse proxy currently running";
}
return "Start a reverse proxy targeting the remote backend server";
case ServerType.local:
return "Check if a local backend server is running";
}
} }
} }

View File

@@ -10,30 +10,19 @@ class ServerTypeSelector extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Tooltip( return DropDownButton(
message: "Determines the type of backend server to use", leading: Text(_serverController.type.value.name),
child: InfoLabel( items: ServerType.values
label: "Type", .map((type) => _createItem(type))
child: SizedBox( .toList()
width: double.infinity,
child: Obx(() => DropDownButton(
leading: Text(_serverController.type.value.name),
items: ServerType.values
.map((type) => _createItem(type))
.toList()))
),
),
); );
} }
MenuFlyoutItem _createItem(ServerType type) { MenuFlyoutItem _createItem(ServerType type) {
return MenuFlyoutItem( return MenuFlyoutItem(
text: SizedBox( text: Tooltip(
width: double.infinity, message: type.message,
child: Tooltip( child: Text(type.name)
message: type.message,
child: Text(type.name)
)
), ),
onPressed: () async { onPressed: () async {
await _serverController.stop(); await _serverController.stop();

View File

@@ -51,12 +51,10 @@ class _RebootUpdaterInputState extends State<RebootUpdaterInput> {
const SizedBox(width: 16.0), const SizedBox(width: 16.0),
Tooltip( Tooltip(
message: _settingsController.autoUpdate.value ? "Disable automatic updates" : "Enable automatic updates", message: _settingsController.autoUpdate.value ? "Disable automatic updates" : "Enable automatic updates",
child: Obx(() => Padding( child: Obx(() => Button(
padding: _valid() ? EdgeInsets.zero : const EdgeInsets.only(bottom: 21.0), onPressed: () => _settingsController.autoUpdate.value = !_settingsController.autoUpdate.value,
child: Button( child: Icon(_settingsController.autoUpdate.value ? FluentIcons.disable_updates : FluentIcons.refresh)
onPressed: () => _settingsController.autoUpdate.value = !_settingsController.autoUpdate.value, )
child: Icon(_settingsController.autoUpdate.value ? FluentIcons.disable_updates : FluentIcons.refresh)
))
) )
) )
], ],

View File

@@ -9,7 +9,6 @@ import 'package:reboot_launcher/src/dialog/snackbar.dart';
import 'package:reboot_launcher/src/util/selector.dart'; import 'package:reboot_launcher/src/util/selector.dart';
class FileSelector extends StatefulWidget { class FileSelector extends StatefulWidget {
final String label;
final String placeholder; final String placeholder;
final String windowTitle; final String windowTitle;
final bool allowNavigator; final bool allowNavigator;
@@ -20,8 +19,7 @@ class FileSelector extends StatefulWidget {
final bool folder; final bool folder;
const FileSelector( const FileSelector(
{required this.label, {required this.placeholder,
required this.placeholder,
required this.windowTitle, required this.windowTitle,
required this.controller, required this.controller,
required this.validator, required this.validator,
@@ -38,50 +36,32 @@ class FileSelector extends StatefulWidget {
} }
class _FileSelectorState extends State<FileSelector> { class _FileSelectorState extends State<FileSelector> {
final RxBool _valid = RxBool(true);
late String? Function(String?) validator;
bool _selecting = false; bool _selecting = false;
@override
void initState() {
validator = (value) {
var result = widget.validator(value);
WidgetsBinding.instance.addPostFrameCallback((_) => _valid.value = result == null);
return result;
};
super.initState();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return InfoLabel( return Row(
label: widget.label, crossAxisAlignment: CrossAxisAlignment.center,
child: Row( children: [
crossAxisAlignment: CrossAxisAlignment.center, Expanded(
children: [ child: TextFormBox(
Expanded( controller: widget.controller,
child: TextFormBox( placeholder: widget.placeholder,
controller: widget.controller, validator: widget.validator,
placeholder: widget.placeholder, autovalidateMode: widget.validatorMode ?? AutovalidateMode.onUserInteraction
validator: validator, )
autovalidateMode: widget.validatorMode ?? AutovalidateMode.onUserInteraction ),
) if (widget.allowNavigator)
), const SizedBox(width: 16.0),
if (widget.allowNavigator) const SizedBox(width: 16.0), if (widget.allowNavigator)
if (widget.allowNavigator) Padding(
Tooltip( padding: const EdgeInsets.only(bottom: 21.0),
message: "Select a ${widget.folder ? 'folder' : 'file'}", child: Button(
child: Obx(() => Padding( onPressed: _onPressed,
padding: _valid() ? EdgeInsets.zero : const EdgeInsets.only(bottom: 21.0), child: const Icon(FluentIcons.open_folder_horizontal)
child: Button(
onPressed: _onPressed,
child: const Icon(FluentIcons.open_folder_horizontal)
))
)
) )
], )
) ],
); );
} }

View File

@@ -1,190 +1,16 @@
import 'package:fluent_ui/fluent_ui.dart'; import 'package:fluent_ui/fluent_ui.dart';
class FluentCard extends StatefulWidget { class FluentCard extends StatelessWidget {
const FluentCard({ final Widget child;
Key? key, const FluentCard({Key? key, required this.child}) : super(key: key);
this.leading,
required this.content,
this.icon,
this.trailing,
this.animationCurve,
this.animationDuration,
this.onPressed,
this.onStateChanged,
this.isButton = false,
this.headerHeight = 68.5,
this.headerBackgroundColor,
this.contentBackgroundColor,
}) : super(key: key);
static Color backgroundColor(ThemeData style, Set<ButtonStates> states, [bool isClickable = true]) {
if (style.brightness == Brightness.light) {
if (!states.isDisabled && isClickable) {
if (states.isPressing) return const ColorConst.withOpacity(0xf9f9f9, 0.2);
if (states.isHovering) return const ColorConst.withOpacity(0xf9f9f9, 0.4);
}
return const ColorConst.withOpacity(0xFFFFFF, 0.7);
} else {
if (!states.isDisabled && isClickable) {
if (states.isPressing) return const ColorConst.withOpacity(0xFFFFFF, 0.03);
if (states.isHovering) return const ColorConst.withOpacity(0xFFFFFF, 0.082);
}
return const ColorConst.withOpacity(0xFFFFFF, 0.05);
}
}
static Color borderColor(ThemeData style, Set<ButtonStates> states, [bool isClickable = true]) {
if (style.brightness == Brightness.light) {
if (isClickable && states.isHovering && !states.isPressing) return const Color(0xFF212121).withOpacity(0.22);
return const Color(0xFF212121).withOpacity(0.17);
} else {
if (isClickable && states.isPressing) return Colors.white.withOpacity(0.062);
if (isClickable && states.isHovering) return Colors.white.withOpacity(0.02);
return Colors.black.withOpacity(0.52);
}
}
/// The leading widget.
///
/// See also:
///
/// * [Icon]
/// * [RadioButton]
/// * [Checkbox]
final Widget? leading;
/// The card content
///
/// Usually a [Text]
final Widget content;
/// The icon of the toggle button.
final Widget? icon;
/// Disable when onPressed is null, always show chevron icon in the right
final bool isButton;
/// The trailing widget. It's positioned at the right of [content]
/// and at the left of [icon].
///
/// See also:
///
/// * [ToggleSwitch]
final Widget? trailing;
/// Makes the card clickable
/// is null by default
final VoidCallback? onPressed;
/// The expand-collapse animation duration. If null, defaults to
/// [FluentTheme.fastAnimationDuration]
final Duration? animationDuration;
/// The expand-collapse animation curve. If null, defaults to
/// [FluentTheme.animationCurve]
final Curve? animationCurve;
/// A callback called when the current state is changed. `true` when
/// open and `false` when closed.
final ValueChanged<bool>? onStateChanged;
/// The height of the header.
///
/// Defaults to 48.0
final double headerHeight;
/// The background color of the header. If null, [ThemeData.scaffoldBackgroundColor]
/// is used
final Color? headerBackgroundColor;
/// The content color of the header. If null, [ThemeData.acrylicBackgroundColor]
/// is used
final Color? contentBackgroundColor;
@override @override
FluentCardState createState() => FluentCardState(); Widget build(BuildContext context) => Mica(
} elevation: 1,
child: Card(
class FluentCardState extends State<FluentCard> backgroundColor: FluentTheme.of(context).menuColor,
with SingleTickerProviderStateMixin { borderRadius: const BorderRadius.vertical(top: Radius.circular(4.0)),
late ThemeData theme; child: child
)
late AnimationController _controller;
@override
void initState() {
super.initState();
_controller = AnimationController(
vsync: this,
duration: widget.animationDuration ?? const Duration(milliseconds: 150),
);
}
static void emptyPressMethod() {}
static const double borderSize = 0.5;
static final Color darkBorderColor = Colors.black.withOpacity(0.8);
static const Duration expanderAnimationDuration = Duration(milliseconds: 70);
/// If this widget acts as a button and is disabled, gray out all text and icons
Widget buttonStyled(Widget child) => !widget.isButton || widget.onPressed != null ? child : IconTheme.merge(
data: IconThemeData(color: theme.disabledColor),
child: DefaultTextStyle.merge(style: TextStyle(color: theme.disabledColor), child: child)
); );
@override
Widget build(BuildContext context) {
assert(debugCheckHasFluentTheme(context));
final isLtr = Directionality.of(context) == TextDirection.ltr;
theme = FluentTheme.of(context);
bool isDark = theme.brightness == Brightness.dark;
return buttonStyled(HoverButton(
onPressed: widget.onPressed ?? (widget.isButton ? null : emptyPressMethod),
builder: (context, states) {
return AnimatedContainer(
duration: expanderAnimationDuration,
height: widget.headerHeight,
decoration: BoxDecoration(
color: FluentCard.backgroundColor(theme, states, widget.onPressed != null),
border: Border.all(
width: borderSize,
color: FluentCard.borderColor(theme, states, widget.onPressed != null),
),
borderRadius: const BorderRadius.all(Radius.circular(4.0)),
),
padding: const EdgeInsetsDirectional.only(start: 16.0),
alignment: Alignment.centerLeft,
child: Row(mainAxisSize: MainAxisSize.min, children: [
if (widget.leading != null) Padding(
padding: const EdgeInsetsDirectional.only(end: 17.0),
child: widget.leading!,
),
Expanded(child: widget.content),
if (widget.trailing != null) Padding(
padding: const EdgeInsetsDirectional.only(start: 20.0, end: 13.5),
child: widget.trailing!,
),
if (widget.icon != null || widget.isButton) Container(
margin: EdgeInsetsDirectional.only(
start: widget.trailing != null ? 8.0 : 20.0,
end: 8.0,
top: 8.0,
bottom: 8.0,
),
padding: const EdgeInsets.symmetric(horizontal: 10.0),
alignment: Alignment.center,
child: widget.icon ?? Icon(isLtr ? isDark ? FluentIcons.chevron_right : FluentIcons.chevron_right_med :
isDark ? FluentIcons.chevron_left : FluentIcons.chevron_left_med, size: 11),
),
]),
);
},
));
}
} }
class ColorConst extends Color {
const ColorConst.withOpacity(int value, double opacity) : super(
( (((opacity * 0xff ~/ 1) & 0xff) << 24) | ((0x00ffffff & value)) ) & 0xFFFFFFFF);
}

View File

@@ -0,0 +1,67 @@
import 'package:fluent_ui/fluent_ui.dart';
import 'package:reboot_launcher/src/widget/shared/fluent_card.dart';
class SettingTile extends StatefulWidget {
static const double kDefaultContentWidth = 200.0;
final String title;
final String subtitle;
final Widget? content;
final double? contentWidth;
final List<Widget>? expandedContent;
const SettingTile(
{Key? key,
required this.title,
required this.subtitle,
this.content,
this.contentWidth = kDefaultContentWidth,
this.expandedContent})
: super(key: key);
@override
State<SettingTile> createState() => _SettingTileState();
}
class _SettingTileState extends State<SettingTile> {
@override
Widget build(BuildContext context) {
if(widget.expandedContent == null){
return _contentCard;
}
return Mica(
elevation: 1,
child: Expander(
initiallyExpanded: true,
contentBackgroundColor: FluentTheme.of(context).menuColor,
headerShape: (open) => const RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(top: Radius.circular(4.0)),
),
header: ListTile(
title: Text(widget.title),
subtitle: Text(widget.subtitle)
),
headerHeight: 72,
trailing: SizedBox(
width: widget.contentWidth,
child: widget.content
),
content: Column(
children: widget.expandedContent!
)
),
);
}
Widget get _contentCard => FluentCard(
child: ListTile(
title: Text(widget.title),
subtitle: Text(widget.subtitle),
trailing: SizedBox(
width: widget.contentWidth,
child: widget.content
),
),
);
}

View File

@@ -54,9 +54,6 @@ flutter:
- assets/icons/ - assets/icons/
- assets/binaries/ - assets/binaries/
- assets/images/ - assets/images/
- assets/profiles/
- assets/responses/
- assets/config/
msix_config: msix_config:
display_name: Reboot Launcher display_name: Reboot Launcher