mirror of
https://github.com/libretro/RetroArch.git
synced 2024-06-02 11:47:24 -04:00
a6beba6376
* provide more information during achievement load process * update rcheevos version * do disconnected processing even when no game is loaded * make loading widget unique * only show loading indicator with verbose messages on
965 lines
29 KiB
C
965 lines
29 KiB
C
#include "rc_runtime.h"
|
|
#include "rc_internal.h"
|
|
|
|
#include "rc_util.h"
|
|
#include "../rhash/md5.h"
|
|
|
|
#include <assert.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#define RC_RUNTIME_MARKER 0x0A504152 /* RAP\n */
|
|
|
|
#define RC_RUNTIME_CHUNK_MEMREFS 0x4645524D /* MREF */
|
|
#define RC_RUNTIME_CHUNK_VARIABLES 0x53524156 /* VARS */
|
|
#define RC_RUNTIME_CHUNK_ACHIEVEMENT 0x56484341 /* ACHV */
|
|
#define RC_RUNTIME_CHUNK_LEADERBOARD 0x4452424C /* LBRD */
|
|
#define RC_RUNTIME_CHUNK_RICHPRESENCE 0x48434952 /* RICH */
|
|
|
|
#define RC_RUNTIME_CHUNK_DONE 0x454E4F44 /* DONE */
|
|
|
|
#define RC_RUNTIME_MIN_BUFFER_SIZE 4 + 8 + 16 /* RUNTIME_MARKER, CHUNK_DONE, MD5 */
|
|
|
|
typedef struct rc_runtime_progress_t {
|
|
const rc_runtime_t* runtime;
|
|
|
|
uint32_t offset;
|
|
uint8_t* buffer;
|
|
uint32_t buffer_size;
|
|
|
|
uint32_t chunk_size_offset;
|
|
|
|
lua_State* L;
|
|
} rc_runtime_progress_t;
|
|
|
|
#define assert_chunk_size(expected_size) assert((uint32_t)(progress->offset - progress->chunk_size_offset - 4) == (uint32_t)(expected_size))
|
|
|
|
#define RC_TRIGGER_STATE_UNUPDATED 0x7F
|
|
|
|
#define RC_MEMREF_FLAG_CHANGED_THIS_FRAME 0x00010000
|
|
|
|
#define RC_VAR_FLAG_HAS_COND_DATA 0x01000000
|
|
|
|
#define RC_COND_FLAG_IS_TRUE 0x00000001
|
|
#define RC_COND_FLAG_OPERAND1_IS_INDIRECT_MEMREF 0x00010000
|
|
#define RC_COND_FLAG_OPERAND1_MEMREF_CHANGED_THIS_FRAME 0x00020000
|
|
#define RC_COND_FLAG_OPERAND2_IS_INDIRECT_MEMREF 0x00100000
|
|
#define RC_COND_FLAG_OPERAND2_MEMREF_CHANGED_THIS_FRAME 0x00200000
|
|
|
|
static void rc_runtime_progress_write_uint(rc_runtime_progress_t* progress, uint32_t value)
|
|
{
|
|
if (progress->buffer) {
|
|
progress->buffer[progress->offset + 0] = value & 0xFF; value >>= 8;
|
|
progress->buffer[progress->offset + 1] = value & 0xFF; value >>= 8;
|
|
progress->buffer[progress->offset + 2] = value & 0xFF; value >>= 8;
|
|
progress->buffer[progress->offset + 3] = value & 0xFF;
|
|
}
|
|
|
|
progress->offset += 4;
|
|
}
|
|
|
|
static uint32_t rc_runtime_progress_read_uint(rc_runtime_progress_t* progress)
|
|
{
|
|
uint32_t value = progress->buffer[progress->offset + 0] |
|
|
(progress->buffer[progress->offset + 1] << 8) |
|
|
(progress->buffer[progress->offset + 2] << 16) |
|
|
(progress->buffer[progress->offset + 3] << 24);
|
|
|
|
progress->offset += 4;
|
|
return value;
|
|
}
|
|
|
|
static void rc_runtime_progress_write_md5(rc_runtime_progress_t* progress, uint8_t* md5)
|
|
{
|
|
if (progress->buffer)
|
|
memcpy(&progress->buffer[progress->offset], md5, 16);
|
|
|
|
progress->offset += 16;
|
|
}
|
|
|
|
static int rc_runtime_progress_match_md5(rc_runtime_progress_t* progress, uint8_t* md5)
|
|
{
|
|
int result = 0;
|
|
if (progress->buffer)
|
|
result = (memcmp(&progress->buffer[progress->offset], md5, 16) == 0);
|
|
|
|
progress->offset += 16;
|
|
|
|
return result;
|
|
}
|
|
|
|
static void rc_runtime_progress_start_chunk(rc_runtime_progress_t* progress, uint32_t chunk_id)
|
|
{
|
|
rc_runtime_progress_write_uint(progress, chunk_id);
|
|
|
|
progress->chunk_size_offset = progress->offset;
|
|
|
|
progress->offset += 4;
|
|
}
|
|
|
|
static void rc_runtime_progress_end_chunk(rc_runtime_progress_t* progress)
|
|
{
|
|
uint32_t length;
|
|
uint32_t offset;
|
|
|
|
progress->offset = (progress->offset + 3) & ~0x03; /* align to 4 byte boundary */
|
|
|
|
if (progress->buffer) {
|
|
/* ignore chunk size field when calculating chunk size */
|
|
length = (uint32_t)(progress->offset - progress->chunk_size_offset - 4);
|
|
|
|
/* temporarily update the write pointer to write the chunk size field */
|
|
offset = progress->offset;
|
|
progress->offset = progress->chunk_size_offset;
|
|
rc_runtime_progress_write_uint(progress, length);
|
|
progress->offset = offset;
|
|
}
|
|
}
|
|
|
|
static void rc_runtime_progress_init(rc_runtime_progress_t* progress, const rc_runtime_t* runtime, lua_State* L)
|
|
{
|
|
memset(progress, 0, sizeof(rc_runtime_progress_t));
|
|
progress->runtime = runtime;
|
|
progress->L = L;
|
|
}
|
|
|
|
#define RC_RUNTIME_SERIALIZED_MEMREF_SIZE 16 /* 4x uint: address, flags, value, prior */
|
|
|
|
static int rc_runtime_progress_write_memrefs(rc_runtime_progress_t* progress)
|
|
{
|
|
rc_memref_t* memref;
|
|
uint32_t count = 0;
|
|
|
|
for (memref = progress->runtime->memrefs; memref; memref = memref->next)
|
|
++count;
|
|
if (count == 0)
|
|
return RC_OK;
|
|
|
|
if (progress->offset + 8 + count * RC_RUNTIME_SERIALIZED_MEMREF_SIZE > progress->buffer_size)
|
|
return RC_INSUFFICIENT_BUFFER;
|
|
|
|
rc_runtime_progress_start_chunk(progress, RC_RUNTIME_CHUNK_MEMREFS);
|
|
|
|
if (!progress->buffer) {
|
|
progress->offset += count * RC_RUNTIME_SERIALIZED_MEMREF_SIZE;
|
|
}
|
|
else {
|
|
uint32_t flags = 0;
|
|
for (memref = progress->runtime->memrefs; memref; memref = memref->next) {
|
|
flags = memref->value.size;
|
|
if (memref->value.changed)
|
|
flags |= RC_MEMREF_FLAG_CHANGED_THIS_FRAME;
|
|
|
|
rc_runtime_progress_write_uint(progress, memref->address);
|
|
rc_runtime_progress_write_uint(progress, flags);
|
|
rc_runtime_progress_write_uint(progress, memref->value.value);
|
|
rc_runtime_progress_write_uint(progress, memref->value.prior);
|
|
}
|
|
}
|
|
|
|
assert_chunk_size(count * RC_RUNTIME_SERIALIZED_MEMREF_SIZE);
|
|
rc_runtime_progress_end_chunk(progress);
|
|
return RC_OK;
|
|
}
|
|
|
|
static int rc_runtime_progress_read_memrefs(rc_runtime_progress_t* progress)
|
|
{
|
|
uint32_t entries;
|
|
uint32_t address, flags, value, prior;
|
|
uint8_t size;
|
|
rc_memref_t* memref;
|
|
rc_memref_t* first_unmatched_memref = progress->runtime->memrefs;
|
|
|
|
/* re-read the chunk size to determine how many memrefs are present */
|
|
progress->offset -= 4;
|
|
entries = rc_runtime_progress_read_uint(progress) / RC_RUNTIME_SERIALIZED_MEMREF_SIZE;
|
|
|
|
while (entries != 0) {
|
|
address = rc_runtime_progress_read_uint(progress);
|
|
flags = rc_runtime_progress_read_uint(progress);
|
|
value = rc_runtime_progress_read_uint(progress);
|
|
prior = rc_runtime_progress_read_uint(progress);
|
|
|
|
size = flags & 0xFF;
|
|
|
|
memref = first_unmatched_memref;
|
|
while (memref) {
|
|
if (memref->address == address && memref->value.size == size) {
|
|
memref->value.value = value;
|
|
memref->value.changed = (flags & RC_MEMREF_FLAG_CHANGED_THIS_FRAME) ? 1 : 0;
|
|
memref->value.prior = prior;
|
|
|
|
if (memref == first_unmatched_memref)
|
|
first_unmatched_memref = memref->next;
|
|
|
|
break;
|
|
}
|
|
|
|
memref = memref->next;
|
|
}
|
|
|
|
--entries;
|
|
}
|
|
|
|
return RC_OK;
|
|
}
|
|
|
|
static int rc_runtime_progress_is_indirect_memref(rc_operand_t* oper)
|
|
{
|
|
switch (oper->type)
|
|
{
|
|
case RC_OPERAND_CONST:
|
|
case RC_OPERAND_FP:
|
|
case RC_OPERAND_LUA:
|
|
return 0;
|
|
|
|
default:
|
|
return oper->value.memref->value.is_indirect;
|
|
}
|
|
}
|
|
|
|
static int rc_runtime_progress_write_condset(rc_runtime_progress_t* progress, rc_condset_t* condset)
|
|
{
|
|
rc_condition_t* cond;
|
|
uint32_t flags;
|
|
|
|
if (progress->offset + 4 > progress->buffer_size)
|
|
return RC_INSUFFICIENT_BUFFER;
|
|
|
|
rc_runtime_progress_write_uint(progress, condset->is_paused);
|
|
|
|
cond = condset->conditions;
|
|
while (cond) {
|
|
flags = 0;
|
|
if (cond->is_true)
|
|
flags |= RC_COND_FLAG_IS_TRUE;
|
|
|
|
if (rc_runtime_progress_is_indirect_memref(&cond->operand1)) {
|
|
flags |= RC_COND_FLAG_OPERAND1_IS_INDIRECT_MEMREF;
|
|
if (cond->operand1.value.memref->value.changed)
|
|
flags |= RC_COND_FLAG_OPERAND1_MEMREF_CHANGED_THIS_FRAME;
|
|
}
|
|
|
|
if (rc_runtime_progress_is_indirect_memref(&cond->operand2)) {
|
|
flags |= RC_COND_FLAG_OPERAND2_IS_INDIRECT_MEMREF;
|
|
if (cond->operand2.value.memref->value.changed)
|
|
flags |= RC_COND_FLAG_OPERAND2_MEMREF_CHANGED_THIS_FRAME;
|
|
}
|
|
|
|
if (progress->offset + 8 > progress->buffer_size)
|
|
return RC_INSUFFICIENT_BUFFER;
|
|
|
|
rc_runtime_progress_write_uint(progress, cond->current_hits);
|
|
rc_runtime_progress_write_uint(progress, flags);
|
|
|
|
if (flags & RC_COND_FLAG_OPERAND1_IS_INDIRECT_MEMREF) {
|
|
if (progress->offset + 8 > progress->buffer_size)
|
|
return RC_INSUFFICIENT_BUFFER;
|
|
|
|
rc_runtime_progress_write_uint(progress, cond->operand1.value.memref->value.value);
|
|
rc_runtime_progress_write_uint(progress, cond->operand1.value.memref->value.prior);
|
|
}
|
|
|
|
if (flags & RC_COND_FLAG_OPERAND2_IS_INDIRECT_MEMREF) {
|
|
if (progress->offset + 8 > progress->buffer_size)
|
|
return RC_INSUFFICIENT_BUFFER;
|
|
|
|
rc_runtime_progress_write_uint(progress, cond->operand2.value.memref->value.value);
|
|
rc_runtime_progress_write_uint(progress, cond->operand2.value.memref->value.prior);
|
|
}
|
|
|
|
cond = cond->next;
|
|
}
|
|
|
|
return RC_OK;
|
|
}
|
|
|
|
static int rc_runtime_progress_read_condset(rc_runtime_progress_t* progress, rc_condset_t* condset)
|
|
{
|
|
rc_condition_t* cond;
|
|
uint32_t flags;
|
|
|
|
condset->is_paused = (char)rc_runtime_progress_read_uint(progress);
|
|
|
|
cond = condset->conditions;
|
|
while (cond) {
|
|
cond->current_hits = rc_runtime_progress_read_uint(progress);
|
|
flags = rc_runtime_progress_read_uint(progress);
|
|
|
|
cond->is_true = (flags & RC_COND_FLAG_IS_TRUE) ? 1 : 0;
|
|
|
|
if (flags & RC_COND_FLAG_OPERAND1_IS_INDIRECT_MEMREF) {
|
|
if (!rc_operand_is_memref(&cond->operand1)) /* this should never happen, but better safe than sorry */
|
|
return RC_INVALID_STATE;
|
|
|
|
cond->operand1.value.memref->value.value = rc_runtime_progress_read_uint(progress);
|
|
cond->operand1.value.memref->value.prior = rc_runtime_progress_read_uint(progress);
|
|
cond->operand1.value.memref->value.changed = (flags & RC_COND_FLAG_OPERAND1_MEMREF_CHANGED_THIS_FRAME) ? 1 : 0;
|
|
}
|
|
|
|
if (flags & RC_COND_FLAG_OPERAND2_IS_INDIRECT_MEMREF) {
|
|
if (!rc_operand_is_memref(&cond->operand2)) /* this should never happen, but better safe than sorry */
|
|
return RC_INVALID_STATE;
|
|
|
|
cond->operand2.value.memref->value.value = rc_runtime_progress_read_uint(progress);
|
|
cond->operand2.value.memref->value.prior = rc_runtime_progress_read_uint(progress);
|
|
cond->operand2.value.memref->value.changed = (flags & RC_COND_FLAG_OPERAND2_MEMREF_CHANGED_THIS_FRAME) ? 1 : 0;
|
|
}
|
|
|
|
cond = cond->next;
|
|
}
|
|
|
|
return RC_OK;
|
|
}
|
|
|
|
static uint32_t rc_runtime_progress_should_serialize_variable_condset(const rc_condset_t* conditions)
|
|
{
|
|
const rc_condition_t* condition;
|
|
|
|
/* predetermined presence of pause flag or indirect memrefs - must serialize */
|
|
if (conditions->has_pause || conditions->has_indirect_memrefs)
|
|
return RC_VAR_FLAG_HAS_COND_DATA;
|
|
|
|
/* if any conditions has required hits, must serialize */
|
|
/* ASSERT: Measured with comparison and no explicit target will set hit target to 0xFFFFFFFF */
|
|
for (condition = conditions->conditions; condition; condition = condition->next) {
|
|
if (condition->required_hits > 0)
|
|
return RC_VAR_FLAG_HAS_COND_DATA;
|
|
}
|
|
|
|
/* can safely be reset without affecting behavior */
|
|
return 0;
|
|
}
|
|
|
|
static int rc_runtime_progress_write_variable(rc_runtime_progress_t* progress, const rc_value_t* variable)
|
|
{
|
|
uint32_t flags;
|
|
|
|
if (progress->offset + 12 > progress->buffer_size)
|
|
return RC_INSUFFICIENT_BUFFER;
|
|
|
|
flags = rc_runtime_progress_should_serialize_variable_condset(variable->conditions);
|
|
if (variable->value.changed)
|
|
flags |= RC_MEMREF_FLAG_CHANGED_THIS_FRAME;
|
|
|
|
rc_runtime_progress_write_uint(progress, flags);
|
|
rc_runtime_progress_write_uint(progress, variable->value.value);
|
|
rc_runtime_progress_write_uint(progress, variable->value.prior);
|
|
|
|
if (flags & RC_VAR_FLAG_HAS_COND_DATA) {
|
|
int result = rc_runtime_progress_write_condset(progress, variable->conditions);
|
|
if (result != RC_OK)
|
|
return result;
|
|
}
|
|
|
|
return RC_OK;
|
|
}
|
|
|
|
static int rc_runtime_progress_write_variables(rc_runtime_progress_t* progress)
|
|
{
|
|
uint32_t count = 0;
|
|
const rc_value_t* variable;
|
|
int result;
|
|
|
|
for (variable = progress->runtime->variables; variable; variable = variable->next)
|
|
++count;
|
|
if (count == 0)
|
|
return RC_OK;
|
|
|
|
/* header + count + count(djb2,flags,value,prior,?cond) */
|
|
if (progress->offset + 8 + 4 + count * 16 > progress->buffer_size)
|
|
return RC_INSUFFICIENT_BUFFER;
|
|
|
|
rc_runtime_progress_start_chunk(progress, RC_RUNTIME_CHUNK_VARIABLES);
|
|
rc_runtime_progress_write_uint(progress, count);
|
|
|
|
for (variable = progress->runtime->variables; variable; variable = variable->next) {
|
|
uint32_t djb2 = rc_djb2(variable->name);
|
|
if (progress->offset + 16 > progress->buffer_size)
|
|
return RC_INSUFFICIENT_BUFFER;
|
|
|
|
rc_runtime_progress_write_uint(progress, djb2);
|
|
|
|
result = rc_runtime_progress_write_variable(progress, variable);
|
|
if (result != RC_OK)
|
|
return result;
|
|
}
|
|
|
|
rc_runtime_progress_end_chunk(progress);
|
|
return RC_OK;
|
|
}
|
|
|
|
static int rc_runtime_progress_read_variable(rc_runtime_progress_t* progress, rc_value_t* variable)
|
|
{
|
|
uint32_t flags = rc_runtime_progress_read_uint(progress);
|
|
variable->value.changed = (flags & RC_MEMREF_FLAG_CHANGED_THIS_FRAME) ? 1 : 0;
|
|
variable->value.value = rc_runtime_progress_read_uint(progress);
|
|
variable->value.prior = rc_runtime_progress_read_uint(progress);
|
|
|
|
if (flags & RC_VAR_FLAG_HAS_COND_DATA) {
|
|
int result = rc_runtime_progress_read_condset(progress, variable->conditions);
|
|
if (result != RC_OK)
|
|
return result;
|
|
}
|
|
else {
|
|
rc_reset_condset(variable->conditions);
|
|
}
|
|
|
|
return RC_OK;
|
|
}
|
|
|
|
static int rc_runtime_progress_read_variables(rc_runtime_progress_t* progress)
|
|
{
|
|
struct rc_pending_value_t
|
|
{
|
|
rc_value_t* variable;
|
|
uint32_t djb2;
|
|
};
|
|
struct rc_pending_value_t local_pending_variables[32];
|
|
struct rc_pending_value_t* pending_variables;
|
|
rc_value_t* variable;
|
|
uint32_t count, serialized_count;
|
|
int result;
|
|
uint32_t i;
|
|
|
|
serialized_count = rc_runtime_progress_read_uint(progress);
|
|
if (serialized_count == 0)
|
|
return RC_OK;
|
|
|
|
count = 0;
|
|
for (variable = progress->runtime->variables; variable; variable = variable->next)
|
|
++count;
|
|
|
|
if (count == 0)
|
|
return RC_OK;
|
|
|
|
if (count <= sizeof(local_pending_variables) / sizeof(local_pending_variables[0])) {
|
|
pending_variables = local_pending_variables;
|
|
}
|
|
else {
|
|
pending_variables = (struct rc_pending_value_t*)malloc(count * sizeof(struct rc_pending_value_t));
|
|
if (pending_variables == NULL)
|
|
return RC_OUT_OF_MEMORY;
|
|
}
|
|
|
|
count = 0;
|
|
for (variable = progress->runtime->variables; variable; variable = variable->next) {
|
|
pending_variables[count].variable = variable;
|
|
pending_variables[count].djb2 = rc_djb2(variable->name);
|
|
++count;
|
|
}
|
|
|
|
result = RC_OK;
|
|
for (; serialized_count > 0 && result == RC_OK; --serialized_count) {
|
|
uint32_t djb2 = rc_runtime_progress_read_uint(progress);
|
|
for (i = 0; i < count; ++i) {
|
|
if (pending_variables[i].djb2 == djb2) {
|
|
variable = pending_variables[i].variable;
|
|
result = rc_runtime_progress_read_variable(progress, variable);
|
|
if (result == RC_OK) {
|
|
if (i < count - 1)
|
|
memcpy(&pending_variables[i], &pending_variables[count - 1], sizeof(struct rc_pending_value_t));
|
|
count--;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
while (count > 0)
|
|
rc_reset_value(pending_variables[--count].variable);
|
|
|
|
if (pending_variables != local_pending_variables)
|
|
free(pending_variables);
|
|
|
|
return result;
|
|
}
|
|
|
|
static int rc_runtime_progress_write_trigger(rc_runtime_progress_t* progress, const rc_trigger_t* trigger)
|
|
{
|
|
rc_condset_t* condset;
|
|
int result;
|
|
|
|
rc_runtime_progress_write_uint(progress, trigger->state);
|
|
rc_runtime_progress_write_uint(progress, trigger->measured_value);
|
|
|
|
if (trigger->requirement) {
|
|
result = rc_runtime_progress_write_condset(progress, trigger->requirement);
|
|
if (result != RC_OK)
|
|
return result;
|
|
}
|
|
|
|
condset = trigger->alternative;
|
|
while (condset) {
|
|
result = rc_runtime_progress_write_condset(progress, condset);
|
|
if (result != RC_OK)
|
|
return result;
|
|
|
|
condset = condset->next;
|
|
}
|
|
|
|
return RC_OK;
|
|
}
|
|
|
|
static int rc_runtime_progress_read_trigger(rc_runtime_progress_t* progress, rc_trigger_t* trigger)
|
|
{
|
|
rc_condset_t* condset;
|
|
int result;
|
|
|
|
trigger->state = (char)rc_runtime_progress_read_uint(progress);
|
|
trigger->measured_value = rc_runtime_progress_read_uint(progress);
|
|
|
|
if (trigger->requirement) {
|
|
result = rc_runtime_progress_read_condset(progress, trigger->requirement);
|
|
if (result != RC_OK)
|
|
return result;
|
|
}
|
|
|
|
condset = trigger->alternative;
|
|
while (condset) {
|
|
result = rc_runtime_progress_read_condset(progress, condset);
|
|
if (result != RC_OK)
|
|
return result;
|
|
|
|
condset = condset->next;
|
|
}
|
|
|
|
return RC_OK;
|
|
}
|
|
|
|
static int rc_runtime_progress_write_achievements(rc_runtime_progress_t* progress)
|
|
{
|
|
uint32_t i;
|
|
int initial_offset = 0;
|
|
int result;
|
|
|
|
for (i = 0; i < progress->runtime->trigger_count; ++i) {
|
|
rc_runtime_trigger_t* runtime_trigger = &progress->runtime->triggers[i];
|
|
if (!runtime_trigger->trigger)
|
|
continue;
|
|
|
|
/* don't store state for inactive or triggered achievements */
|
|
if (!rc_trigger_state_active(runtime_trigger->trigger->state))
|
|
continue;
|
|
|
|
if (!progress->buffer) {
|
|
if (runtime_trigger->serialized_size) {
|
|
progress->offset += runtime_trigger->serialized_size;
|
|
continue;
|
|
}
|
|
|
|
initial_offset = progress->offset;
|
|
} else {
|
|
if (progress->offset + runtime_trigger->serialized_size > progress->buffer_size)
|
|
return RC_INSUFFICIENT_BUFFER;
|
|
}
|
|
|
|
rc_runtime_progress_start_chunk(progress, RC_RUNTIME_CHUNK_ACHIEVEMENT);
|
|
rc_runtime_progress_write_uint(progress, runtime_trigger->id);
|
|
rc_runtime_progress_write_md5(progress, runtime_trigger->md5);
|
|
|
|
result = rc_runtime_progress_write_trigger(progress, runtime_trigger->trigger);
|
|
if (result != RC_OK)
|
|
return result;
|
|
|
|
if (runtime_trigger->serialized_size) {
|
|
/* runtime_trigger->serialized_size includes the header */
|
|
assert_chunk_size(runtime_trigger->serialized_size - 8);
|
|
}
|
|
|
|
rc_runtime_progress_end_chunk(progress);
|
|
|
|
if (!progress->buffer)
|
|
runtime_trigger->serialized_size = progress->offset - initial_offset;
|
|
}
|
|
|
|
return RC_OK;
|
|
}
|
|
|
|
static int rc_runtime_progress_read_achievement(rc_runtime_progress_t* progress)
|
|
{
|
|
uint32_t id = rc_runtime_progress_read_uint(progress);
|
|
uint32_t i;
|
|
|
|
for (i = 0; i < progress->runtime->trigger_count; ++i) {
|
|
rc_runtime_trigger_t* runtime_trigger = &progress->runtime->triggers[i];
|
|
if (runtime_trigger->id == id && runtime_trigger->trigger != NULL) {
|
|
/* ignore triggered and waiting achievements */
|
|
if (runtime_trigger->trigger->state == RC_TRIGGER_STATE_UNUPDATED) {
|
|
/* only update state if definition hasn't changed (md5 matches) */
|
|
if (rc_runtime_progress_match_md5(progress, runtime_trigger->md5))
|
|
return rc_runtime_progress_read_trigger(progress, runtime_trigger->trigger);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return RC_OK;
|
|
}
|
|
|
|
static int rc_runtime_progress_write_leaderboards(rc_runtime_progress_t* progress)
|
|
{
|
|
uint32_t i;
|
|
uint32_t flags;
|
|
int initial_offset = 0;
|
|
int result;
|
|
|
|
for (i = 0; i < progress->runtime->lboard_count; ++i) {
|
|
rc_runtime_lboard_t* runtime_lboard = &progress->runtime->lboards[i];
|
|
if (!runtime_lboard->lboard)
|
|
continue;
|
|
|
|
/* don't store state for inactive leaderboards */
|
|
if (!rc_lboard_state_active(runtime_lboard->lboard->state))
|
|
continue;
|
|
|
|
if (!progress->buffer) {
|
|
if (runtime_lboard->serialized_size) {
|
|
progress->offset += runtime_lboard->serialized_size;
|
|
continue;
|
|
}
|
|
|
|
initial_offset = progress->offset;
|
|
} else {
|
|
if (progress->offset + runtime_lboard->serialized_size > progress->buffer_size)
|
|
return RC_INSUFFICIENT_BUFFER;
|
|
}
|
|
|
|
rc_runtime_progress_start_chunk(progress, RC_RUNTIME_CHUNK_LEADERBOARD);
|
|
rc_runtime_progress_write_uint(progress, runtime_lboard->id);
|
|
rc_runtime_progress_write_md5(progress, runtime_lboard->md5);
|
|
|
|
flags = runtime_lboard->lboard->state;
|
|
rc_runtime_progress_write_uint(progress, flags);
|
|
|
|
result = rc_runtime_progress_write_trigger(progress, &runtime_lboard->lboard->start);
|
|
if (result != RC_OK)
|
|
return result;
|
|
|
|
result = rc_runtime_progress_write_trigger(progress, &runtime_lboard->lboard->submit);
|
|
if (result != RC_OK)
|
|
return result;
|
|
|
|
result = rc_runtime_progress_write_trigger(progress, &runtime_lboard->lboard->cancel);
|
|
if (result != RC_OK)
|
|
return result;
|
|
|
|
result = rc_runtime_progress_write_variable(progress, &runtime_lboard->lboard->value);
|
|
if (result != RC_OK)
|
|
return result;
|
|
|
|
if (runtime_lboard->serialized_size) {
|
|
/* runtime_lboard->serialized_size includes the header */
|
|
assert_chunk_size(runtime_lboard->serialized_size - 8);
|
|
}
|
|
|
|
rc_runtime_progress_end_chunk(progress);
|
|
|
|
if (!progress->buffer)
|
|
runtime_lboard->serialized_size = progress->offset - initial_offset;
|
|
}
|
|
|
|
return RC_OK;
|
|
}
|
|
|
|
static int rc_runtime_progress_read_leaderboard(rc_runtime_progress_t* progress)
|
|
{
|
|
uint32_t id = rc_runtime_progress_read_uint(progress);
|
|
uint32_t i;
|
|
int result;
|
|
|
|
for (i = 0; i < progress->runtime->lboard_count; ++i) {
|
|
rc_runtime_lboard_t* runtime_lboard = &progress->runtime->lboards[i];
|
|
if (runtime_lboard->id == id && runtime_lboard->lboard != NULL) {
|
|
/* ignore triggered and waiting achievements */
|
|
if (runtime_lboard->lboard->state == RC_TRIGGER_STATE_UNUPDATED) {
|
|
/* only update state if definition hasn't changed (md5 matches) */
|
|
if (rc_runtime_progress_match_md5(progress, runtime_lboard->md5)) {
|
|
uint32_t flags = rc_runtime_progress_read_uint(progress);
|
|
|
|
result = rc_runtime_progress_read_trigger(progress, &runtime_lboard->lboard->start);
|
|
if (result != RC_OK)
|
|
return result;
|
|
|
|
result = rc_runtime_progress_read_trigger(progress, &runtime_lboard->lboard->submit);
|
|
if (result != RC_OK)
|
|
return result;
|
|
|
|
result = rc_runtime_progress_read_trigger(progress, &runtime_lboard->lboard->cancel);
|
|
if (result != RC_OK)
|
|
return result;
|
|
|
|
result = rc_runtime_progress_read_variable(progress, &runtime_lboard->lboard->value);
|
|
if (result != RC_OK)
|
|
return result;
|
|
|
|
runtime_lboard->lboard->state = (char)(flags & 0x7F);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return RC_OK;
|
|
}
|
|
|
|
static int rc_runtime_progress_write_rich_presence(rc_runtime_progress_t* progress)
|
|
{
|
|
const rc_richpresence_display_t* display;
|
|
int result;
|
|
|
|
if (!progress->runtime->richpresence || !progress->runtime->richpresence->richpresence)
|
|
return RC_OK;
|
|
|
|
/* if there are no conditional display strings, there's nothing to capture */
|
|
display = progress->runtime->richpresence->richpresence->first_display;
|
|
if (!display->next)
|
|
return RC_OK;
|
|
|
|
if (progress->offset + 8 + 16 > progress->buffer_size)
|
|
return RC_INSUFFICIENT_BUFFER;
|
|
|
|
rc_runtime_progress_start_chunk(progress, RC_RUNTIME_CHUNK_RICHPRESENCE);
|
|
rc_runtime_progress_write_md5(progress, progress->runtime->richpresence->md5);
|
|
|
|
for (; display->next; display = display->next) {
|
|
result = rc_runtime_progress_write_trigger(progress, &display->trigger);
|
|
if (result != RC_OK)
|
|
return result;
|
|
}
|
|
|
|
rc_runtime_progress_end_chunk(progress);
|
|
return RC_OK;
|
|
}
|
|
|
|
static int rc_runtime_progress_read_rich_presence(rc_runtime_progress_t* progress)
|
|
{
|
|
rc_richpresence_display_t* display;
|
|
int result;
|
|
|
|
if (!progress->runtime->richpresence || !progress->runtime->richpresence->richpresence)
|
|
return RC_OK;
|
|
|
|
if (!rc_runtime_progress_match_md5(progress, progress->runtime->richpresence->md5)) {
|
|
rc_reset_richpresence(progress->runtime->richpresence->richpresence);
|
|
return RC_OK;
|
|
}
|
|
|
|
display = progress->runtime->richpresence->richpresence->first_display;
|
|
for (; display->next; display = display->next) {
|
|
result = rc_runtime_progress_read_trigger(progress, &display->trigger);
|
|
if (result != RC_OK)
|
|
return result;
|
|
}
|
|
|
|
return RC_OK;
|
|
}
|
|
|
|
static int rc_runtime_progress_serialize_internal(rc_runtime_progress_t* progress)
|
|
{
|
|
md5_state_t state;
|
|
uint8_t md5[16];
|
|
int result;
|
|
|
|
if (progress->buffer_size < RC_RUNTIME_MIN_BUFFER_SIZE)
|
|
return RC_INSUFFICIENT_BUFFER;
|
|
|
|
rc_runtime_progress_write_uint(progress, RC_RUNTIME_MARKER);
|
|
|
|
if ((result = rc_runtime_progress_write_memrefs(progress)) != RC_OK)
|
|
return result;
|
|
|
|
if ((result = rc_runtime_progress_write_variables(progress)) != RC_OK)
|
|
return result;
|
|
|
|
if ((result = rc_runtime_progress_write_achievements(progress)) != RC_OK)
|
|
return result;
|
|
|
|
if ((result = rc_runtime_progress_write_leaderboards(progress)) != RC_OK)
|
|
return result;
|
|
|
|
if ((result = rc_runtime_progress_write_rich_presence(progress)) != RC_OK)
|
|
return result;
|
|
|
|
if (progress->offset + 8 + 16 > progress->buffer_size)
|
|
return RC_INSUFFICIENT_BUFFER;
|
|
|
|
rc_runtime_progress_write_uint(progress, RC_RUNTIME_CHUNK_DONE);
|
|
rc_runtime_progress_write_uint(progress, 16);
|
|
|
|
if (progress->buffer) {
|
|
md5_init(&state);
|
|
md5_append(&state, progress->buffer, progress->offset);
|
|
md5_finish(&state, md5);
|
|
}
|
|
|
|
rc_runtime_progress_write_md5(progress, md5);
|
|
|
|
return RC_OK;
|
|
}
|
|
|
|
uint32_t rc_runtime_progress_size(const rc_runtime_t* runtime, lua_State* L)
|
|
{
|
|
rc_runtime_progress_t progress;
|
|
int result;
|
|
|
|
rc_runtime_progress_init(&progress, runtime, L);
|
|
progress.buffer_size = 0xFFFFFFFF;
|
|
|
|
result = rc_runtime_progress_serialize_internal(&progress);
|
|
if (result != RC_OK)
|
|
return result;
|
|
|
|
return progress.offset;
|
|
}
|
|
|
|
int rc_runtime_serialize_progress(void* buffer, const rc_runtime_t* runtime, lua_State* L)
|
|
{
|
|
return rc_runtime_serialize_progress_sized(buffer, 0xFFFFFFFF, runtime, L);
|
|
}
|
|
|
|
int rc_runtime_serialize_progress_sized(uint8_t* buffer, uint32_t buffer_size, const rc_runtime_t* runtime, lua_State* L)
|
|
{
|
|
rc_runtime_progress_t progress;
|
|
|
|
if (!buffer)
|
|
return RC_INVALID_STATE;
|
|
|
|
rc_runtime_progress_init(&progress, runtime, L);
|
|
progress.buffer = (uint8_t*)buffer;
|
|
progress.buffer_size = buffer_size;
|
|
|
|
return rc_runtime_progress_serialize_internal(&progress);
|
|
}
|
|
|
|
int rc_runtime_deserialize_progress(rc_runtime_t* runtime, const uint8_t* serialized, lua_State* L)
|
|
{
|
|
return rc_runtime_deserialize_progress_sized(runtime, serialized, 0xFFFFFFFF, L);
|
|
}
|
|
|
|
int rc_runtime_deserialize_progress_sized(rc_runtime_t* runtime, const uint8_t* serialized, uint32_t serialized_size, lua_State* L)
|
|
{
|
|
rc_runtime_progress_t progress;
|
|
md5_state_t state;
|
|
uint8_t md5[16];
|
|
uint32_t chunk_id;
|
|
uint32_t chunk_size;
|
|
uint32_t next_chunk_offset;
|
|
uint32_t i;
|
|
int seen_rich_presence = 0;
|
|
int result = RC_OK;
|
|
|
|
if (!serialized || serialized_size < RC_RUNTIME_MIN_BUFFER_SIZE) {
|
|
rc_runtime_reset(runtime);
|
|
return RC_INSUFFICIENT_BUFFER;
|
|
}
|
|
|
|
rc_runtime_progress_init(&progress, runtime, L);
|
|
progress.buffer = (uint8_t*)serialized;
|
|
|
|
if (rc_runtime_progress_read_uint(&progress) != RC_RUNTIME_MARKER) {
|
|
rc_runtime_reset(runtime);
|
|
return RC_INVALID_STATE;
|
|
}
|
|
|
|
for (i = 0; i < runtime->trigger_count; ++i) {
|
|
rc_runtime_trigger_t* runtime_trigger = &runtime->triggers[i];
|
|
if (runtime_trigger->trigger) {
|
|
/* don't update state for inactive or triggered achievements */
|
|
if (rc_trigger_state_active(runtime_trigger->trigger->state)) {
|
|
/* mark active achievements as unupdated. anything that's still unupdated
|
|
* after deserializing the progress will be reset to waiting */
|
|
runtime_trigger->trigger->state = RC_TRIGGER_STATE_UNUPDATED;
|
|
}
|
|
}
|
|
}
|
|
|
|
for (i = 0; i < runtime->lboard_count; ++i) {
|
|
rc_runtime_lboard_t* runtime_lboard = &runtime->lboards[i];
|
|
if (runtime_lboard->lboard) {
|
|
/* don't update state for inactive or triggered achievements */
|
|
if (rc_lboard_state_active(runtime_lboard->lboard->state)) {
|
|
/* mark active achievements as unupdated. anything that's still unupdated
|
|
* after deserializing the progress will be reset to waiting */
|
|
runtime_lboard->lboard->state = RC_TRIGGER_STATE_UNUPDATED;
|
|
}
|
|
}
|
|
}
|
|
|
|
do {
|
|
if (progress.offset + 8 >= serialized_size) {
|
|
result = RC_INSUFFICIENT_BUFFER;
|
|
break;
|
|
}
|
|
|
|
chunk_id = rc_runtime_progress_read_uint(&progress);
|
|
chunk_size = rc_runtime_progress_read_uint(&progress);
|
|
next_chunk_offset = progress.offset + chunk_size;
|
|
|
|
if (next_chunk_offset > serialized_size) {
|
|
result = RC_INSUFFICIENT_BUFFER;
|
|
break;
|
|
}
|
|
|
|
switch (chunk_id) {
|
|
case RC_RUNTIME_CHUNK_MEMREFS:
|
|
result = rc_runtime_progress_read_memrefs(&progress);
|
|
break;
|
|
|
|
case RC_RUNTIME_CHUNK_VARIABLES:
|
|
result = rc_runtime_progress_read_variables(&progress);
|
|
break;
|
|
|
|
case RC_RUNTIME_CHUNK_ACHIEVEMENT:
|
|
result = rc_runtime_progress_read_achievement(&progress);
|
|
break;
|
|
|
|
case RC_RUNTIME_CHUNK_LEADERBOARD:
|
|
result = rc_runtime_progress_read_leaderboard(&progress);
|
|
break;
|
|
|
|
case RC_RUNTIME_CHUNK_RICHPRESENCE:
|
|
seen_rich_presence = 1;
|
|
result = rc_runtime_progress_read_rich_presence(&progress);
|
|
break;
|
|
|
|
case RC_RUNTIME_CHUNK_DONE:
|
|
md5_init(&state);
|
|
md5_append(&state, progress.buffer, progress.offset);
|
|
md5_finish(&state, md5);
|
|
if (!rc_runtime_progress_match_md5(&progress, md5))
|
|
result = RC_INVALID_STATE;
|
|
break;
|
|
|
|
default:
|
|
if (chunk_size & 0xFFFF0000)
|
|
result = RC_INVALID_STATE; /* assume unknown chunk > 64KB is invalid */
|
|
break;
|
|
}
|
|
|
|
progress.offset = next_chunk_offset;
|
|
} while (result == RC_OK && chunk_id != RC_RUNTIME_CHUNK_DONE);
|
|
|
|
if (result != RC_OK) {
|
|
rc_runtime_reset(runtime);
|
|
}
|
|
else {
|
|
for (i = 0; i < runtime->trigger_count; ++i) {
|
|
rc_trigger_t* trigger = runtime->triggers[i].trigger;
|
|
if (trigger && trigger->state == RC_TRIGGER_STATE_UNUPDATED)
|
|
rc_reset_trigger(trigger);
|
|
}
|
|
|
|
for (i = 0; i < runtime->lboard_count; ++i) {
|
|
rc_lboard_t* lboard = runtime->lboards[i].lboard;
|
|
if (lboard && lboard->state == RC_TRIGGER_STATE_UNUPDATED)
|
|
rc_reset_lboard(lboard);
|
|
}
|
|
|
|
if (!seen_rich_presence && runtime->richpresence && runtime->richpresence->richpresence)
|
|
rc_reset_richpresence(runtime->richpresence->richpresence);
|
|
}
|
|
|
|
return result;
|
|
}
|