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