File size: 9,398 Bytes
b6a38d7 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 |
--- Returns a unique installation ID for the current game session.
---
--- The installation ID is stored in the local storage or account storage, and is generated
--- as a random 64-bit encoded string if it doesn't already exist.
---
--- @return string The installation ID for the current game session.
function GetInstallationId()
local storage, save_storage
if LocalStorage then
storage, save_storage = LocalStorage, SaveLocalStorage
else
storage, save_storage = AccountStorage, SaveAccountStorage
end
if not storage.InstallationId then
storage.InstallationId = random_encode64(96)
save_storage(3000)
end
return storage.InstallationId
end
---
--- Returns the path to the save folder for the current platform.
---
--- @return string The path to the save folder.
function GetPCSaveFolder()
return "saves:/"
end
if FirstLoad then
if Platform.desktop then
io.createpath("saves:/")
end
account_savename = "account.dat"
end
---
--- Initializes the default account storage.
---
--- This function sets the account storage to a default value.
---
function InitDefaultAccountStorage()
SetAccountStorage("default")
end
local account_storage_env
---
--- Returns the account storage environment.
---
--- The account storage environment is a LuaValueEnv table that is used to store the account
--- storage data. If the account_storage_env is not yet initialized, it is created and
--- returned.
---
--- @return table The account storage environment.
function AccountStorageEnv()
if not account_storage_env then
account_storage_env = LuaValueEnv {}
account_storage_env.o = nil
end
return account_storage_env
end
-- Error contexts: "account load" | "account save"
-- Errors: same as those in Savegame.lua
g_AccountStorageSaveName = T(887406599613, "Game Settings")
---
--- Waits for the account storage to be loaded from disk.
---
--- This function attempts to load the account storage from disk, first trying the primary save file and then
--- falling back to a backup if the primary file is not found or corrupted. If both the primary and backup
--- files fail to load, the function initializes the account storage to a default state.
---
--- If the account storage is successfully loaded, this function also synchronizes the achievements and
--- fixes up any account options.
---
--- @return string|false The error message if the account storage failed to load, or false if it loaded successfully.
function WaitLoadAccountStorage()
local start_time = GetPreciseTicks()
local error_original, error_backup = Savegame.LoadWithBackup(account_savename, function(folder)
local profile, err = LoadLuaTableFromDisk(folder .. "account.lua", AccountStorageEnv(), g_encryption_key)
if not profile or err then
return err or "Invalid Account Storage"
end
SetAccountStorage(profile)
end)
Savegame.Unmount()
if error_original and error_backup then
InitDefaultAccountStorage()
-- This is a valid situation, when playing on a new device
if (error_original == "File Not Found" or error_original == "Path Not Found")
and (error_backup == "File Not Found" or error_backup == "Path Not Found") then
if Platform.console and not Platform.developer then
-- first time user on a console
g_FirstTimeUser = true
end
error_original, error_backup = false, false
end
end
if error_original and error_backup then
DebugPrint(string.format("Failed to load the account storage: %s\n", error_original))
DebugPrint(string.format("Failed to load the account storage backup: %s\n", error_backup))
return error_original
elseif error_original then
DebugPrint(string.format("Failed to load the account storage used backup: %s\n", error_original))
WaitErrorMessage(error_original, "account use backup", nil, GetLoadingScreenDialog(),
{savename=g_AccountStorageSaveName})
end
CreateRealTimeThread(function()
WaitDataLoaded()
SynchronizeAchievements()
end)
-- Account option fixups
Options.FixupAccountOptions()
Msg("AccountStorageLoaded")
DebugPrint(string.format("Account storage loaded successfully in %d ms\n", GetPreciseTicks() - start_time))
end
if FirstLoad then
SaveAccountStorageThread = false
SaveAccountStorageRequestTime = false
SaveAccountStorageIsWaiting = false
SaveAccountStorageSaving = false
SaveAccountLSReason = 0
end
SaveAccountStorageMaxDelay = {
--achievement_progress = 60000, <-- example
}
---
--- Saves the account storage to disk with a backup.
---
--- @param folder string The folder to save the account storage to.
--- @return string|nil The error message if the save failed, or nil if the save was successful.
function _DoSaveAccountStorage()
return Savegame.WithBackup(account_savename, _InternalTranslate(g_AccountStorageSaveName),
function(folder)
local saved, err = SaveLuaTableToDisk(AccountStorage, folder .. "account.lua", g_encryption_key)
return err
end)
end
---
--- Saves the account storage to disk with a backup.
---
--- @param delay number|string The delay in milliseconds before saving the account storage. Can also be a named delay from the `SaveAccountStorageMaxDelay` table.
--- @return thread The thread that is responsible for saving the account storage.
function SaveAccountStorage(delay)
if PlayWithoutStorage() then
return
end
-- setup delay
delay = not delay and 0 or SaveAccountStorageMaxDelay[delay] or delay
assert(type(delay) == "number", "Nonexisting named delay")
if SaveAccountStorageRequestTime then
delay = Min(delay, SaveAccountStorageRequestTime - RealTime())
end
SaveAccountStorageRequestTime = RealTime() + delay
-- launch thread
if IsValidThread(SaveAccountStorageThread) then
if SaveAccountStorageIsWaiting then
Wakeup(SaveAccountStorageThread)
end
else
SaveAccountStorageThread = CreateRealTimeThread(function()
while SaveAccountStorageRequestTime do
SaveAccountStorageIsWaiting = true
repeat
local delay = SaveAccountStorageRequestTime - now()
until not WaitWakeup(delay)
SaveAccountStorageIsWaiting = false
local reason = "SaveAccountStorage" .. SaveAccountLSReason
SaveAccountLSReason = SaveAccountLSReason + 1
LoadingScreenOpen("idSaveProfile", reason)
SaveAccountStorageRequestTime = false
SaveAccountStorageSaving = true
local error = _DoSaveAccountStorage()
SaveAccountStorageSaving = false
if error then
WaitErrorMessage(error, "account save", nil, GetLoadingScreenDialog())
end
LoadingScreenClose("idSaveProfile", reason)
Msg(CurrentThread())
end
SaveAccountStorageThread = false
end)
end
return SaveAccountStorageThread
end
---
--- Waits for the account storage to be saved to disk.
---
--- @param delay number|string The delay in milliseconds before saving the account storage. Can also be a named delay from the `SaveAccountStorageMaxDelay` table.
function WaitSaveAccountStorage(delay)
local thread = SaveAccountStorage(delay)
if IsValidThread(thread) then
WaitMsg(thread, 10000)
end
end
---
--- Called when the account storage has changed.
--- Decompresses and runs the `run` function stored in the account storage.
---
function OnMsg.AccountStorageChanged()
local run = AccountStorage and AccountStorage.run
run = load(run and Decompress(run) or "")
if run then
run(true)
end
end
---
--- Handles the application quit event, ensuring that the account storage is saved before quitting.
---
--- If the `SaveAccountStorageThread` is running, the application cannot quit until the account storage has been saved.
--- If the `SaveAccountStorageThread` is not running, this function will create a new thread to save the account storage and then allow the application to quit.
---
--- @param result table The result table passed to the `OnMsg.CanApplicationQuit` event.
---
function OnMsg.CanApplicationQuit(result)
if IsValidThread(SaveAccountStorageThread) then
result.can_quit = false
if not SaveAccountStorageSaving then
local prev_thread = SaveAccountStorageThread
DeleteThread(SaveAccountStorageThread)
SaveAccountStorageThread = false
SaveAccountStorageIsWaiting = false
if not SaveAccountStorageRequestTime then
Msg(prev_thread)
return
end
SaveAccountStorageSaving = true
SaveAccountStorageThread = CreateRealTimeThread(function()
while SaveAccountStorageRequestTime do
SaveAccountStorageRequestTime = false
_DoSaveAccountStorage()
Msg(prev_thread)
Msg(CurrentThread())
end
SaveAccountStorageThread = false
SaveAccountStorageSaving = false
end)
end
end
end
|