// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 #pragma once // GameKit #include <aws/gamekit/core/exports.h> #define S3_PRESIGNED_URL_DEFAULT_TIME_TO_LIVE_SECONDS 120 extern "C" { /** * @brief The recommended action your game should take in order to keep the local and cloud save file in sync. */ enum class SlotSyncStatus : uint8_t { /** * @brief This status should not be possible. */ UNKNOWN = 0, /** * @brief No action needed. * * @details The cloud file and local file are the same. They both have the same last modified timestamp. */ SYNCED = 1, /** * @brief You should call GameKitLoadSlot() to download a newer version of this save from the cloud. * * @details Either the save file does not exist locally, or * the save file exists locally, the cloud file is newer, and the local file has previously been uploaded from this device. */ SHOULD_DOWNLOAD_CLOUD = 2, /** * @brief You should call GameKitSaveSlot() to upload the local save file to the cloud. * * @details Either the save slot does not exist in the cloud, or * the save slot exists in the cloud, the local file is newer, and the last time the cloud save was updated was from this device. */ SHOULD_UPLOAD_LOCAL = 3, /** * @brief You should ask the player to select which file they want to keep: the local file or the cloud file. * * @details The local file and the cloud file are different, and based on their last modified timestamps it is not clear which file should be kept. * * @details This may happen when a player plays on multiple devices, and especially when played in offline mode across multiple devices. */ IN_CONFLICT = 4 }; /** * @brief Contains local and cloud information about a cached slot. * * @details This is also the data that gets written to the SaveInfo.json files. */ struct Slot { Slot() {} Slot(const char* slotName, const char* metadataLocal, const char* metadataCloud, int64_t sizeLocal, int64_t sizeCloud, int64_t lastModifiedLocal, int64_t lastModifiedCloud, int64_t lastSync, SlotSyncStatus slotSyncStatus) : slotName(slotName), metadataLocal(metadataLocal), metadataCloud(metadataCloud), sizeLocal(sizeLocal), sizeCloud(sizeCloud), lastModifiedLocal(lastModifiedLocal), lastModifiedCloud(lastModifiedCloud), lastSync(lastSync), slotSyncStatus(slotSyncStatus) {} /** * @brief The slot name matching one of the cached slots. */ const char* slotName = ""; /** * @brief An arbitrary string you have associated with this save file locally. * * @details For example, this could be used to store information you want to display in the UI before you download the save file from the cloud, * such as a friendly display name, a user provided description, the total playtime, the percentage of the game completed, etc. * * @details The string can be in any format (ex: JSON), fully supporting UTF-8 compliant characters. It is limited to 1410 bytes. */ const char* metadataLocal = ""; /** * @brief An arbitrary string you have associated with the cloud save file. * * @details See Slot::metadataLocal for details. */ const char* metadataCloud = ""; /** * @brief The size of the local save file in bytes. */ int64_t sizeLocal = 0; /** * @brief The size of the cloud save file in bytes. */ int64_t sizeCloud = 0; /** * @brief The last time the local save file was modified in epoch milliseconds. */ int64_t lastModifiedLocal = 0; /** * @brief The last time the cloud save file was modified in epoch milliseconds. */ int64_t lastModifiedCloud = 0; /** * @brief The last time the local save file was uploaded from this device or downloaded to this device. * * @details This time will be equal to lastModifiedLocal after calling GameKitSaveSlot(), and equal to lastModifiedCloud after calling GameKitLoadSlot(). */ int64_t lastSync = 0; /** * @brief The recommended action your game should take in order to keep the local and cloud save file in sync. */ SlotSyncStatus slotSyncStatus = SlotSyncStatus::UNKNOWN; }; /** * @brief A struct containing the request parameters for both GameKitSaveSlot() and GameKitLoadSlot(). * * @details All parameters are required, unless marked otherwise. */ struct GameSavingModel { GameSavingModel() {} GameSavingModel(const char* slotName, const char* metadata, int64_t epochTime, bool overrideSync, uint8_t* data, unsigned int dataSize, const char* localSlotInformationFilePath) : slotName(slotName), metadata(metadata), epochTime(epochTime), overrideSync(overrideSync), data(data), dataSize(dataSize), localSlotInformationFilePath(localSlotInformationFilePath) {} GameSavingModel(const char* slotName, const char* metadata, int64_t epochTime, bool overrideSync, uint8_t* data, unsigned int dataSize, const char* localSlotInformationFilePath, unsigned int urlTimeToLive, bool consistentRead) : slotName(slotName), metadata(metadata), epochTime(epochTime), overrideSync(overrideSync), data(data), dataSize(dataSize), localSlotInformationFilePath(localSlotInformationFilePath), urlTimeToLive(urlTimeToLive), consistentRead(consistentRead) {} /** * (SaveSlot - Required) The name of the save slot to upload to the cloud. The name may be new, it does not have to exist in the cached slots. * * (LoadSlot - Required) The name of the save slot to download from the cloud. The name must exist in the cached slots. */ const char* slotName = ""; /** * (SaveSlot - Optional) An arbitrary string you want to associate with the save file. * * For example, this could be used to store information you want to display in the UI before you download the save file from the cloud, * such as a friendly display name, a user provided description, the total playtime, the percentage of the game completed, etc. * * The string can be in any format (ex: JSON), fully supporting UTF-8 compliant characters. It is limited to 1410 bytes. */ const char* metadata = ""; /** * (SaveSlot - Optional) The millisecond epoch time of when the local save file was last modified in UTC. * * Defaults to 0. If 0, will use the system's current timestamp as the EpochTime. The default is useful for * save files which only exist in memory (i.e. they aren't persisted on the device). */ int64_t epochTime = 0; /** * (SaveSlot & LoadSlot - Optional) If set to true, this method will ignore the SlotSyncStatus and override the cloud/local data. * * Set this to true when you are resolving a sync conflict. */ bool overrideSync = false; /** * (LoadSlot - Required) An array of unsigned bytes large enough to contain the save file after downloading from the cloud. * * We recommend determining how many bytes are needed by caching the Slot array * from the most recent Game Saving API call before calling GameKitLoadSlot(). From this cached array, you * can get the Slot::sizeCloud of the slot you are going to download. Note: the sizeCloud will be incorrect * if the cloud save has been updated from another device since the last time this device cached the * Slot array. In that case, call GameKitGetSlotSyncStatus() to get the accurate size. * * Alternative to caching, you can call GameKitGetSlotSyncStatus(slotName) to get the size of the cloud file. * However, this has extra latency compared to caching the results of the previous Game Saving API call. */ uint8_t* data = nullptr; /** * The number of bytes in the `data` array. */ unsigned int dataSize = 0; /** * (SaveSlot & LoadSlot - Required) The absolute path and filename for where to save the SaveInfo.json file. */ const char* localSlotInformationFilePath = nullptr; /** * (SaveSlot & LoadSlot - Optional) The time to live in seconds for the generated pre-signed S3 urls used to upload/download the save file to/from the cloud. Defaults to 120 seconds. */ unsigned int urlTimeToLive = S3_PRESIGNED_URL_DEFAULT_TIME_TO_LIVE_SECONDS; /** * (SaveSlot & LoadSlot - Optional) Whether to use "Consistent Read" when querying from DynamoDB. Defaults to true. */ bool consistentRead = true; }; /** * @brief A static callback function that will be invoked by GameKitGetAllSlotSyncStatuses() upon completion of the call (both for success or failure). * * @param dispatchReceiver The `receiver` pointer that was passed into the Game Saving API. * @param syncedSlots An array of cached slots. If `callStatus` is true, then this is the complete set of cached slots tracked by the Game Saving instance. * If false, then this is a subset of the cached slots. This subset will not be returned to the callback again until the final call (when `callStatus` is true). * If the call failed, then this array will be empty. * @param slotCount The number of slots in the `syncedSlots` array. * @param complete If true, then this is the final call of this response callback. If false, the callback will be invoked again with the next page of data. * @param callStatus A GameKit status code indicating the result of the API call. Status codes are defined in errors.h. * See the specific API's documentation for a list of possible status codes the API may return. */ typedef void(*GameSavingResponseCallback)(DISPATCH_RECEIVER_HANDLE dispatchReceiver, const Slot* syncedSlots, unsigned int slotCount, bool complete, unsigned int callStatus); /** * @brief A static callback function that will be invoked by a Game Saving API upon completion of the call (both for success or failure). * * @details This callback signature is used by Game Saving APIs which act on a single save slot. * * @param dispatchReceiver The `receiver` pointer that was passed into the Game Saving API. * @param syncedSlots An array containing a copy of the current set of cached slots. * @param slotCount The number of slots in the `syncedSlots` array. * @param slot A copy of the cached slot that was acted on by the API. If the call failed, this slot is empty and should not be used. * This slot might not be valid once this object leaves scope (i.e. once this callback function completes). * @param callStatus A GameKit status code indicating the result of the API call. Status codes are defined in errors.h. * See the specific API's documentation for a list of possible status codes the API may return. */ typedef void(*GameSavingSlotActionResponseCallback)(DISPATCH_RECEIVER_HANDLE dispatchReceiver, const Slot* syncedSlots, unsigned int slotCount, const Slot* slot, unsigned int callStatus); /** * @brief A static callback function that will be invoked by GameKitLoadSlot() upon completion of the call (both for success or failure). * * @param dispatchReceiver The `receiver` pointer that was passed into the Game Saving API. * @param syncedSlots An array containing a copy of the current set of cached slots. * @param slotCount The number of slots in the `syncedSlots` array. * @param slot A copy of the cached slot that was downloaded. If the call failed, this slot is empty and should not be used. * This slot might not be valid once this object leaves scope (i.e. once this callback function completes). * @param data An array of unsigned bytes containing the downloaded file, or a nullptr if the call failed. * @param dataSize The size of the `data` array in bytes. * @param callStatus A GameKit status code indicating the result of the API call. Status codes are defined in errors.h. * See the specific API's documentation for a list of possible status codes the API may return. */ typedef void(*GameSavingDataResponseCallback)(DISPATCH_RECEIVER_HANDLE dispatchReceiver, const Slot* syncedSlots, unsigned int slotCount, const Slot* slot, const uint8_t* data, unsigned int dataSize, unsigned int callStatus); /** * @brief Save a byte array to a file, overwriting the file if it already exists. * * @param dispatchReceiver The pointer stored in FileActions::fileWriteDispatchReceiver. The implementer of this function may use this pointer however they find suitable. * For example, to point to a class instance on which this callback function can invoke a file-writing instance method. * @param filePath The absolute or relative path of the file to write to. * @param data The data to write to the file. * @param size The length of the `data` array. * @return True if the data was successfully written to the file, false otherwise. */ typedef bool(*FileWriteCallback)(DISPATCH_RECEIVER_HANDLE dispatchReceiver, const char* filePath, const uint8_t* data, const unsigned int size); /** * @brief Load a file into a byte array. * * @param dispatchReceiver The pointer stored in FileActions::fileReadDispatchReceiver. The implementer of this function may use this pointer however they find suitable. * For example, to point to a class instance on which this callback function can invoke a file-reading instance method. * @param filePath The absolute or relative path of the file to read from. * @param data The array to store the loaded data in. Must be pre-allocated with enough space to store the entire contents of the file. The caller of * this function must call `delete[] data` when finished with the data to prevent a memory leak. * @param size The length of the `data` array. * @return True if the data was successfully read from the file, false otherwise. */ typedef bool(*FileReadCallback)(DISPATCH_RECEIVER_HANDLE dispatchReceiver, const char* filePath, uint8_t* data, unsigned int size); /** * @brief Return the size of the file in bytes, or 0 if the file does not exist. * * @param dispatchReceiver The pointer stored in FileActions::fileSizeDispatchReceiver. The implementer of this function may use this pointer however they find suitable. * For example, to point to a class instance on which this callback function can invoke a size-getting instance method. * @param filePath The absolute or relative path of the file to check. * @return The file size in bytes, or 0 if the file does not exist. */ typedef unsigned int(*FileGetSizeCallback)(DISPATCH_RECEIVER_HANDLE dispatchReceiver, const char* filePath); /** * @brief A bundle of callback functions that provide file I/O for the Game Saving library. */ struct FileActions { /** * @brief A callback function the Game Saving library will call when it needs to write to a file. */ FileWriteCallback fileWriteCallback; /** * @brief A callback function the Game Saving library will call when it needs to load a file. */ FileReadCallback fileReadCallback; /** * @brief A callback function the Game Saving library will call when it needs to get the size of a file. */ FileGetSizeCallback fileSizeCallback; /** * @brief This pointer will be passed into FileActions::fileWriteCallback() whenever it is invoked. * * @details The implementer of FileActions::fileWriteCallback() may use this pointer however they find suitable. * For example, to point to an instance of a class on which to invoke a file writing instance method. */ DISPATCH_RECEIVER_HANDLE fileWriteDispatchReceiver; /** * @brief This pointer will be passed into FileActions::fileReadCallback() whenever it is invoked. * * @details The implementer of FileActions::fileReadCallback() may use this pointer however they find suitable. * For example, to point to an instance of a class on which to invoke a file reading instance method. */ DISPATCH_RECEIVER_HANDLE fileReadDispatchReceiver; /** * @brief This pointer will be passed into FileActions::fileSizeCallback() whenever it is invoked. * * @details The implementer of FileActions::fileSizeCallback() may use this pointer however they find suitable. * For example, to point to an instance of a class on which to invoke a file size-getting instance method. */ DISPATCH_RECEIVER_HANDLE fileSizeDispatchReceiver; }; }