commit
cf89fe4845
|
@ -1,6 +1,6 @@
|
|||
/*
|
||||
FLAC audio decoder. Choice of public domain or MIT-0. See license statements at the end of this file.
|
||||
dr_flac - v0.12.37 - 2022-02-12
|
||||
dr_flac - v0.12.39 - 2022-09-17
|
||||
|
||||
David Reid - mackron@gmail.com
|
||||
|
||||
|
@ -210,6 +210,9 @@ Build Options
|
|||
#define DR_FLAC_NO_SIMD
|
||||
Disables SIMD optimizations (SSE on x86/x64 architectures, NEON on ARM architectures). Use this if you are having compatibility issues with your compiler.
|
||||
|
||||
#define DR_FLAC_NO_WCHAR
|
||||
Disables all functions ending with `_w`. Use this if your compiler does not provide wchar.h. Not required if DR_FLAC_NO_STDIO is also defined.
|
||||
|
||||
|
||||
|
||||
Notes
|
||||
|
@ -232,7 +235,7 @@ extern "C" {
|
|||
|
||||
#define DRFLAC_VERSION_MAJOR 0
|
||||
#define DRFLAC_VERSION_MINOR 12
|
||||
#define DRFLAC_VERSION_REVISION 37
|
||||
#define DRFLAC_VERSION_REVISION 39
|
||||
#define DRFLAC_VERSION_STRING DRFLAC_XSTRINGIFY(DRFLAC_VERSION_MAJOR) "." DRFLAC_XSTRINGIFY(DRFLAC_VERSION_MINOR) "." DRFLAC_XSTRINGIFY(DRFLAC_VERSION_REVISION)
|
||||
|
||||
#include <stddef.h> /* For size_t. */
|
||||
|
@ -383,15 +386,13 @@ typedef enum
|
|||
drflac_seek_origin_current
|
||||
} drflac_seek_origin;
|
||||
|
||||
/* Packing is important on this structure because we map this directly to the raw data within the SEEKTABLE metadata block. */
|
||||
#pragma pack(2)
|
||||
/* The order of members in this structure is important because we map this directly to the raw data within the SEEKTABLE metadata block. */
|
||||
typedef struct
|
||||
{
|
||||
drflac_uint64 firstPCMFrame;
|
||||
drflac_uint64 flacFrameOffset; /* The offset from the first byte of the header of the first frame. */
|
||||
drflac_uint16 pcmFrameCount;
|
||||
} drflac_seekpoint;
|
||||
#pragma pack()
|
||||
|
||||
typedef struct
|
||||
{
|
||||
|
@ -1280,15 +1281,13 @@ typedef struct
|
|||
const char* pRunningData;
|
||||
} drflac_cuesheet_track_iterator;
|
||||
|
||||
/* Packing is important on this structure because we map this directly to the raw data within the CUESHEET metadata block. */
|
||||
#pragma pack(4)
|
||||
/* The order of members here is important because we map this directly to the raw data within the CUESHEET metadata block. */
|
||||
typedef struct
|
||||
{
|
||||
drflac_uint64 offset;
|
||||
drflac_uint8 index;
|
||||
drflac_uint8 reserved[3];
|
||||
} drflac_cuesheet_track_index;
|
||||
#pragma pack()
|
||||
|
||||
typedef struct
|
||||
{
|
||||
|
@ -1363,9 +1362,15 @@ DRFLAC_API drflac_bool32 drflac_next_cuesheet_track(drflac_cuesheet_track_iterat
|
|||
I am using "__inline__" only when we're compiling in strict ANSI mode.
|
||||
*/
|
||||
#if defined(__STRICT_ANSI__)
|
||||
#define DRFLAC_INLINE __inline__ __attribute__((always_inline))
|
||||
#define DRFLAC_GNUC_INLINE_HINT __inline__
|
||||
#else
|
||||
#define DRFLAC_INLINE inline __attribute__((always_inline))
|
||||
#define DRFLAC_GNUC_INLINE_HINT inline
|
||||
#endif
|
||||
|
||||
#if (__GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 2)) || defined(__clang__)
|
||||
#define DRFLAC_INLINE DRFLAC_GNUC_INLINE_HINT __attribute__((always_inline))
|
||||
#else
|
||||
#define DRFLAC_INLINE DRFLAC_GNUC_INLINE_HINT
|
||||
#endif
|
||||
#elif defined(__WATCOMC__)
|
||||
#define DRFLAC_INLINE __inline
|
||||
|
@ -1509,9 +1514,7 @@ static DRFLAC_INLINE drflac_bool32 drflac_has_sse41(void)
|
|||
{
|
||||
#if defined(DRFLAC_SUPPORT_SSE41)
|
||||
#if (defined(DRFLAC_X64) || defined(DRFLAC_X86)) && !defined(DRFLAC_NO_SSE41)
|
||||
#if defined(DRFLAC_X64)
|
||||
return DRFLAC_TRUE; /* 64-bit targets always support SSE4.1. */
|
||||
#elif (defined(_M_IX86_FP) && _M_IX86_FP == 2) || defined(__SSE4_1__)
|
||||
#if defined(__SSE4_1__) || defined(__AVX__)
|
||||
return DRFLAC_TRUE; /* If the compiler is allowed to freely generate SSE41 code we can assume support. */
|
||||
#else
|
||||
#if defined(DRFLAC_NO_CPUID)
|
||||
|
@ -1576,18 +1579,21 @@ static DRFLAC_INLINE drflac_bool32 drflac_has_sse41(void)
|
|||
extern __inline drflac_uint64 _watcom_bswap64(drflac_uint64);
|
||||
#pragma aux _watcom_bswap16 = \
|
||||
"xchg al, ah" \
|
||||
parm [ax] \
|
||||
modify [ax];
|
||||
parm [ax] \
|
||||
value [ax] \
|
||||
modify nomemory;
|
||||
#pragma aux _watcom_bswap32 = \
|
||||
"bswap eax" \
|
||||
parm [eax] \
|
||||
modify [eax];
|
||||
"bswap eax" \
|
||||
parm [eax] \
|
||||
value [eax] \
|
||||
modify nomemory;
|
||||
#pragma aux _watcom_bswap64 = \
|
||||
"bswap eax" \
|
||||
"bswap edx" \
|
||||
"xchg eax,edx" \
|
||||
parm [eax edx] \
|
||||
modify [eax edx];
|
||||
value [eax edx] \
|
||||
modify nomemory;
|
||||
#endif
|
||||
|
||||
|
||||
|
@ -1688,6 +1694,10 @@ typedef drflac_int32 drflac_result;
|
|||
#define DRFLAC_CHANNEL_ASSIGNMENT_RIGHT_SIDE 9
|
||||
#define DRFLAC_CHANNEL_ASSIGNMENT_MID_SIDE 10
|
||||
|
||||
#define DRFLAC_SEEKPOINT_SIZE_IN_BYTES 18
|
||||
#define DRFLAC_CUESHEET_TRACK_SIZE_IN_BYTES 36
|
||||
#define DRFLAC_CUESHEET_TRACK_INDEX_SIZE_IN_BYTES 12
|
||||
|
||||
#define drflac_align(x, a) ((((x) + (a) - 1) / (a)) * (a))
|
||||
|
||||
|
||||
|
@ -2690,6 +2700,10 @@ static drflac_bool32 drflac__find_and_seek_to_next_sync_code(drflac_bs* bs)
|
|||
#if defined(__WATCOMC__) && defined(__386__)
|
||||
#define DRFLAC_IMPLEMENT_CLZ_WATCOM
|
||||
#endif
|
||||
#ifdef __MRC__
|
||||
#include <intrinsics.h>
|
||||
#define DRFLAC_IMPLEMENT_CLZ_MRC
|
||||
#endif
|
||||
|
||||
static DRFLAC_INLINE drflac_uint32 drflac__clz_software(drflac_cache_t x)
|
||||
{
|
||||
|
@ -2730,6 +2744,8 @@ static DRFLAC_INLINE drflac_bool32 drflac__is_lzcnt_supported(void)
|
|||
/* Fast compile time check for ARM. */
|
||||
#if defined(DRFLAC_HAS_LZCNT_INTRINSIC) && defined(DRFLAC_ARM) && (defined(__ARM_ARCH) && __ARM_ARCH >= 5)
|
||||
return DRFLAC_TRUE;
|
||||
#elif defined(__MRC__)
|
||||
return DRFLAC_TRUE;
|
||||
#else
|
||||
/* If the compiler itself does not support the intrinsic then we'll need to return false. */
|
||||
#ifdef DRFLAC_HAS_LZCNT_INTRINSIC
|
||||
|
@ -2839,6 +2855,15 @@ static DRFLAC_INLINE drflac_uint32 drflac__clz_msvc(drflac_cache_t x)
|
|||
|
||||
#ifdef DRFLAC_IMPLEMENT_CLZ_WATCOM
|
||||
static __inline drflac_uint32 drflac__clz_watcom (drflac_uint32);
|
||||
#ifdef DRFLAC_IMPLEMENT_CLZ_WATCOM_LZCNT
|
||||
/* Use the LZCNT instruction (only available on some processors since the 2010s). */
|
||||
#pragma aux drflac__clz_watcom_lzcnt = \
|
||||
"db 0F3h, 0Fh, 0BDh, 0C0h" /* lzcnt eax, eax */ \
|
||||
parm [eax] \
|
||||
value [eax] \
|
||||
modify nomemory;
|
||||
#else
|
||||
/* Use the 386+-compatible implementation. */
|
||||
#pragma aux drflac__clz_watcom = \
|
||||
"bsr eax, eax" \
|
||||
"xor eax, 31" \
|
||||
|
@ -2846,6 +2871,7 @@ static __inline drflac_uint32 drflac__clz_watcom (drflac_uint32);
|
|||
value [eax] \
|
||||
modify exact [eax] nomemory;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
static DRFLAC_INLINE drflac_uint32 drflac__clz(drflac_cache_t x)
|
||||
{
|
||||
|
@ -2857,8 +2883,12 @@ static DRFLAC_INLINE drflac_uint32 drflac__clz(drflac_cache_t x)
|
|||
{
|
||||
#ifdef DRFLAC_IMPLEMENT_CLZ_MSVC
|
||||
return drflac__clz_msvc(x);
|
||||
#elif defined(DRFLAC_IMPLEMENT_CLZ_WATCOM_LZCNT)
|
||||
return drflac__clz_watcom_lzcnt(x);
|
||||
#elif defined(DRFLAC_IMPLEMENT_CLZ_WATCOM)
|
||||
return (x == 0) ? sizeof(x)*8 : drflac__clz_watcom(x);
|
||||
#elif defined(__MRC__)
|
||||
return __cntlzw(x);
|
||||
#else
|
||||
return drflac__clz_software(x);
|
||||
#endif
|
||||
|
@ -4420,7 +4450,7 @@ static drflac_bool32 drflac__decode_samples_with_residual__rice__neon_32(drflac_
|
|||
|
||||
const drflac_uint32 t[2] = {0x00000000, 0xFFFFFFFF};
|
||||
|
||||
riceParamMask = ~((~0UL) << riceParam);
|
||||
riceParamMask = (drflac_uint32)~((~0UL) << riceParam);
|
||||
riceParamMask128 = vdupq_n_u32(riceParamMask);
|
||||
|
||||
riceParam128 = vdupq_n_s32(riceParam);
|
||||
|
@ -4606,10 +4636,13 @@ static drflac_bool32 drflac__decode_samples_with_residual__rice__neon_64(drflac_
|
|||
int32x4_t riceParam128;
|
||||
int64x1_t shift64;
|
||||
uint32x4_t one128;
|
||||
int64x2_t prediction128 = { 0 };
|
||||
uint32x4_t zeroCountPart128;
|
||||
uint32x4_t riceParamPart128;
|
||||
|
||||
const drflac_uint32 t[2] = {0x00000000, 0xFFFFFFFF};
|
||||
|
||||
riceParamMask = ~((~0UL) << riceParam);
|
||||
riceParamMask = (drflac_uint32)~((~0UL) << riceParam);
|
||||
riceParamMask128 = vdupq_n_u32(riceParamMask);
|
||||
|
||||
riceParam128 = vdupq_n_s32(riceParam);
|
||||
|
@ -4686,10 +4719,6 @@ static drflac_bool32 drflac__decode_samples_with_residual__rice__neon_64(drflac_
|
|||
|
||||
/* For this version we are doing one sample at a time. */
|
||||
while (pDecodedSamples < pDecodedSamplesEnd) {
|
||||
int64x2_t prediction128 = vdupq_n_s64(0);
|
||||
uint32x4_t zeroCountPart128;
|
||||
uint32x4_t riceParamPart128;
|
||||
|
||||
if (!drflac__read_rice_parts_x1(bs, riceParam, &zeroCountParts[0], &riceParamParts[0]) ||
|
||||
!drflac__read_rice_parts_x1(bs, riceParam, &zeroCountParts[1], &riceParamParts[1]) ||
|
||||
!drflac__read_rice_parts_x1(bs, riceParam, &zeroCountParts[2], &riceParamParts[2]) ||
|
||||
|
@ -6437,7 +6466,7 @@ static void drflac__free_from_callbacks(void* p, const drflac_allocation_callbac
|
|||
}
|
||||
|
||||
|
||||
static drflac_bool32 drflac__read_and_decode_metadata(drflac_read_proc onRead, drflac_seek_proc onSeek, drflac_meta_proc onMeta, void* pUserData, void* pUserDataMD, drflac_uint64* pFirstFramePos, drflac_uint64* pSeektablePos, drflac_uint32* pSeektableSize, drflac_allocation_callbacks* pAllocationCallbacks)
|
||||
static drflac_bool32 drflac__read_and_decode_metadata(drflac_read_proc onRead, drflac_seek_proc onSeek, drflac_meta_proc onMeta, void* pUserData, void* pUserDataMD, drflac_uint64* pFirstFramePos, drflac_uint64* pSeektablePos, drflac_uint32* pSeekpointCount, drflac_allocation_callbacks* pAllocationCallbacks)
|
||||
{
|
||||
/*
|
||||
We want to keep track of the byte position in the stream of the seektable. At the time of calling this function we know that
|
||||
|
@ -6497,32 +6526,37 @@ static drflac_bool32 drflac__read_and_decode_metadata(drflac_read_proc onRead, d
|
|||
seektableSize = blockSize;
|
||||
|
||||
if (onMeta) {
|
||||
drflac_uint32 seekpointCount;
|
||||
drflac_uint32 iSeekpoint;
|
||||
void* pRawData;
|
||||
|
||||
pRawData = drflac__malloc_from_callbacks(blockSize, pAllocationCallbacks);
|
||||
seekpointCount = blockSize/DRFLAC_SEEKPOINT_SIZE_IN_BYTES;
|
||||
|
||||
pRawData = drflac__malloc_from_callbacks(seekpointCount * sizeof(drflac_seekpoint), pAllocationCallbacks);
|
||||
if (pRawData == NULL) {
|
||||
return DRFLAC_FALSE;
|
||||
}
|
||||
|
||||
if (onRead(pUserData, pRawData, blockSize) != blockSize) {
|
||||
drflac__free_from_callbacks(pRawData, pAllocationCallbacks);
|
||||
return DRFLAC_FALSE;
|
||||
}
|
||||
|
||||
metadata.pRawData = pRawData;
|
||||
metadata.rawDataSize = blockSize;
|
||||
metadata.data.seektable.seekpointCount = blockSize/sizeof(drflac_seekpoint);
|
||||
metadata.data.seektable.pSeekpoints = (const drflac_seekpoint*)pRawData;
|
||||
|
||||
/* Endian swap. */
|
||||
for (iSeekpoint = 0; iSeekpoint < metadata.data.seektable.seekpointCount; ++iSeekpoint) {
|
||||
/* We need to read seekpoint by seekpoint and do some processing. */
|
||||
for (iSeekpoint = 0; iSeekpoint < seekpointCount; ++iSeekpoint) {
|
||||
drflac_seekpoint* pSeekpoint = (drflac_seekpoint*)pRawData + iSeekpoint;
|
||||
|
||||
if (onRead(pUserData, pSeekpoint, DRFLAC_SEEKPOINT_SIZE_IN_BYTES) != DRFLAC_SEEKPOINT_SIZE_IN_BYTES) {
|
||||
drflac__free_from_callbacks(pRawData, pAllocationCallbacks);
|
||||
return DRFLAC_FALSE;
|
||||
}
|
||||
|
||||
/* Endian swap. */
|
||||
pSeekpoint->firstPCMFrame = drflac__be2host_64(pSeekpoint->firstPCMFrame);
|
||||
pSeekpoint->flacFrameOffset = drflac__be2host_64(pSeekpoint->flacFrameOffset);
|
||||
pSeekpoint->pcmFrameCount = drflac__be2host_16(pSeekpoint->pcmFrameCount);
|
||||
}
|
||||
|
||||
metadata.pRawData = pRawData;
|
||||
metadata.rawDataSize = blockSize;
|
||||
metadata.data.seektable.seekpointCount = seekpointCount;
|
||||
metadata.data.seektable.pSeekpoints = (const drflac_seekpoint*)pRawData;
|
||||
|
||||
onMeta(pUserDataMD, &metadata);
|
||||
|
||||
drflac__free_from_callbacks(pRawData, pAllocationCallbacks);
|
||||
|
@ -6607,9 +6641,15 @@ static drflac_bool32 drflac__read_and_decode_metadata(drflac_read_proc onRead, d
|
|||
void* pRawData;
|
||||
const char* pRunningData;
|
||||
const char* pRunningDataEnd;
|
||||
size_t bufferSize;
|
||||
drflac_uint8 iTrack;
|
||||
drflac_uint8 iIndex;
|
||||
void* pTrackData;
|
||||
|
||||
/*
|
||||
This needs to be loaded in two passes. The first pass is used to calculate the size of the memory allocation
|
||||
we need for storing the necessary data. The second pass will fill that buffer with usable data.
|
||||
*/
|
||||
pRawData = drflac__malloc_from_callbacks(blockSize, pAllocationCallbacks);
|
||||
if (pRawData == NULL) {
|
||||
return DRFLAC_FALSE;
|
||||
|
@ -6630,38 +6670,91 @@ static drflac_bool32 drflac__read_and_decode_metadata(drflac_read_proc onRead, d
|
|||
metadata.data.cuesheet.leadInSampleCount = drflac__be2host_64(*(const drflac_uint64*)pRunningData); pRunningData += 8;
|
||||
metadata.data.cuesheet.isCD = (pRunningData[0] & 0x80) != 0; pRunningData += 259;
|
||||
metadata.data.cuesheet.trackCount = pRunningData[0]; pRunningData += 1;
|
||||
metadata.data.cuesheet.pTrackData = pRunningData;
|
||||
metadata.data.cuesheet.pTrackData = NULL; /* Will be filled later. */
|
||||
|
||||
/* Check that the cuesheet tracks are valid before passing it to the callback */
|
||||
for (iTrack = 0; iTrack < metadata.data.cuesheet.trackCount; ++iTrack) {
|
||||
drflac_uint8 indexCount;
|
||||
drflac_uint32 indexPointSize;
|
||||
/* Pass 1: Calculate the size of the buffer for the track data. */
|
||||
{
|
||||
const char* pRunningDataSaved = pRunningData; /* Will be restored at the end in preparation for the second pass. */
|
||||
|
||||
if (pRunningDataEnd - pRunningData < 36) {
|
||||
drflac__free_from_callbacks(pRawData, pAllocationCallbacks);
|
||||
return DRFLAC_FALSE;
|
||||
bufferSize = metadata.data.cuesheet.trackCount * DRFLAC_CUESHEET_TRACK_SIZE_IN_BYTES;
|
||||
|
||||
for (iTrack = 0; iTrack < metadata.data.cuesheet.trackCount; ++iTrack) {
|
||||
drflac_uint8 indexCount;
|
||||
drflac_uint32 indexPointSize;
|
||||
|
||||
if (pRunningDataEnd - pRunningData < DRFLAC_CUESHEET_TRACK_SIZE_IN_BYTES) {
|
||||
drflac__free_from_callbacks(pRawData, pAllocationCallbacks);
|
||||
return DRFLAC_FALSE;
|
||||
}
|
||||
|
||||
/* Skip to the index point count */
|
||||
pRunningData += 35;
|
||||
|
||||
indexCount = pRunningData[0];
|
||||
pRunningData += 1;
|
||||
|
||||
bufferSize += indexCount * sizeof(drflac_cuesheet_track_index);
|
||||
|
||||
/* Quick validation check. */
|
||||
indexPointSize = indexCount * DRFLAC_CUESHEET_TRACK_INDEX_SIZE_IN_BYTES;
|
||||
if (pRunningDataEnd - pRunningData < (drflac_int64)indexPointSize) {
|
||||
drflac__free_from_callbacks(pRawData, pAllocationCallbacks);
|
||||
return DRFLAC_FALSE;
|
||||
}
|
||||
|
||||
pRunningData += indexPointSize;
|
||||
}
|
||||
|
||||
/* Skip to the index point count */
|
||||
pRunningData += 35;
|
||||
indexCount = pRunningData[0]; pRunningData += 1;
|
||||
indexPointSize = indexCount * sizeof(drflac_cuesheet_track_index);
|
||||
if (pRunningDataEnd - pRunningData < (drflac_int64)indexPointSize) {
|
||||
drflac__free_from_callbacks(pRawData, pAllocationCallbacks);
|
||||
return DRFLAC_FALSE;
|
||||
}
|
||||
|
||||
/* Endian swap. */
|
||||
for (iIndex = 0; iIndex < indexCount; ++iIndex) {
|
||||
drflac_cuesheet_track_index* pTrack = (drflac_cuesheet_track_index*)pRunningData;
|
||||
pRunningData += sizeof(drflac_cuesheet_track_index);
|
||||
pTrack->offset = drflac__be2host_64(pTrack->offset);
|
||||
}
|
||||
pRunningData = pRunningDataSaved;
|
||||
}
|
||||
|
||||
/* Pass 2: Allocate a buffer and fill the data. Validation was done in the step above so can be skipped. */
|
||||
{
|
||||
char* pRunningTrackData;
|
||||
|
||||
pTrackData = drflac__malloc_from_callbacks(bufferSize, pAllocationCallbacks);
|
||||
if (pTrackData == NULL) {
|
||||
drflac__free_from_callbacks(pRawData, pAllocationCallbacks);
|
||||
return DRFLAC_FALSE;
|
||||
}
|
||||
|
||||
pRunningTrackData = (char*)pTrackData;
|
||||
|
||||
for (iTrack = 0; iTrack < metadata.data.cuesheet.trackCount; ++iTrack) {
|
||||
drflac_uint8 indexCount;
|
||||
|
||||
DRFLAC_COPY_MEMORY(pRunningTrackData, pRunningData, DRFLAC_CUESHEET_TRACK_SIZE_IN_BYTES);
|
||||
pRunningData += DRFLAC_CUESHEET_TRACK_SIZE_IN_BYTES-1; /* Skip forward, but not beyond the last byte in the CUESHEET_TRACK block which is the index count. */
|
||||
pRunningTrackData += DRFLAC_CUESHEET_TRACK_SIZE_IN_BYTES-1;
|
||||
|
||||
/* Grab the index count for the next part. */
|
||||
indexCount = pRunningData[0];
|
||||
pRunningData += 1;
|
||||
pRunningTrackData += 1;
|
||||
|
||||
/* Extract each track index. */
|
||||
for (iIndex = 0; iIndex < indexCount; ++iIndex) {
|
||||
drflac_cuesheet_track_index* pTrackIndex = (drflac_cuesheet_track_index*)pRunningTrackData;
|
||||
|
||||
DRFLAC_COPY_MEMORY(pRunningTrackData, pRunningData, DRFLAC_CUESHEET_TRACK_INDEX_SIZE_IN_BYTES);
|
||||
pRunningData += DRFLAC_CUESHEET_TRACK_INDEX_SIZE_IN_BYTES;
|
||||
pRunningTrackData += sizeof(drflac_cuesheet_track_index);
|
||||
|
||||
pTrackIndex->offset = drflac__be2host_64(pTrackIndex->offset);
|
||||
}
|
||||
}
|
||||
|
||||
metadata.data.cuesheet.pTrackData = pTrackData;
|
||||
}
|
||||
|
||||
/* The original data is no longer needed. */
|
||||
drflac__free_from_callbacks(pRawData, pAllocationCallbacks);
|
||||
pRawData = NULL;
|
||||
|
||||
onMeta(pUserDataMD, &metadata);
|
||||
|
||||
drflac__free_from_callbacks(pRawData, pAllocationCallbacks);
|
||||
drflac__free_from_callbacks(pTrackData, pAllocationCallbacks);
|
||||
pTrackData = NULL;
|
||||
}
|
||||
} break;
|
||||
|
||||
|
@ -6700,7 +6793,7 @@ static drflac_bool32 drflac__read_and_decode_metadata(drflac_read_proc onRead, d
|
|||
drflac__free_from_callbacks(pRawData, pAllocationCallbacks);
|
||||
return DRFLAC_FALSE;
|
||||
}
|
||||
metadata.data.picture.mime = pRunningData; pRunningData += metadata.data.picture.mimeLength;
|
||||
metadata.data.picture.mime = pRunningData; pRunningData += metadata.data.picture.mimeLength;
|
||||
metadata.data.picture.descriptionLength = drflac__be2host_32_ptr_unaligned(pRunningData); pRunningData += 4;
|
||||
|
||||
/* Need space for the rest of the block */
|
||||
|
@ -6708,7 +6801,7 @@ static drflac_bool32 drflac__read_and_decode_metadata(drflac_read_proc onRead, d
|
|||
drflac__free_from_callbacks(pRawData, pAllocationCallbacks);
|
||||
return DRFLAC_FALSE;
|
||||
}
|
||||
metadata.data.picture.description = pRunningData; pRunningData += metadata.data.picture.descriptionLength;
|
||||
metadata.data.picture.description = pRunningData; pRunningData += metadata.data.picture.descriptionLength;
|
||||
metadata.data.picture.width = drflac__be2host_32_ptr_unaligned(pRunningData); pRunningData += 4;
|
||||
metadata.data.picture.height = drflac__be2host_32_ptr_unaligned(pRunningData); pRunningData += 4;
|
||||
metadata.data.picture.colorDepth = drflac__be2host_32_ptr_unaligned(pRunningData); pRunningData += 4;
|
||||
|
@ -6791,9 +6884,9 @@ static drflac_bool32 drflac__read_and_decode_metadata(drflac_read_proc onRead, d
|
|||
}
|
||||
}
|
||||
|
||||
*pSeektablePos = seektablePos;
|
||||
*pSeektableSize = seektableSize;
|
||||
*pFirstFramePos = runningFilePos;
|
||||
*pSeektablePos = seektablePos;
|
||||
*pSeekpointCount = seektableSize / DRFLAC_SEEKPOINT_SIZE_IN_BYTES;
|
||||
*pFirstFramePos = runningFilePos;
|
||||
|
||||
return DRFLAC_TRUE;
|
||||
}
|
||||
|
@ -7823,11 +7916,11 @@ static drflac* drflac_open_with_metadata_private(drflac_read_proc onRead, drflac
|
|||
drflac_uint32 wholeSIMDVectorCountPerChannel;
|
||||
drflac_uint32 decodedSamplesAllocationSize;
|
||||
#ifndef DR_FLAC_NO_OGG
|
||||
drflac_oggbs oggbs;
|
||||
drflac_oggbs* pOggbs = NULL;
|
||||
#endif
|
||||
drflac_uint64 firstFramePos;
|
||||
drflac_uint64 seektablePos;
|
||||
drflac_uint32 seektableSize;
|
||||
drflac_uint32 seekpointCount;
|
||||
drflac_allocation_callbacks allocationCallbacks;
|
||||
drflac* pFlac;
|
||||
|
||||
|
@ -7881,18 +7974,21 @@ static drflac* drflac_open_with_metadata_private(drflac_read_proc onRead, drflac
|
|||
/* There's additional data required for Ogg streams. */
|
||||
if (init.container == drflac_container_ogg) {
|
||||
allocationSize += sizeof(drflac_oggbs);
|
||||
}
|
||||
|
||||
DRFLAC_ZERO_MEMORY(&oggbs, sizeof(oggbs));
|
||||
if (init.container == drflac_container_ogg) {
|
||||
oggbs.onRead = onRead;
|
||||
oggbs.onSeek = onSeek;
|
||||
oggbs.pUserData = pUserData;
|
||||
oggbs.currentBytePos = init.oggFirstBytePos;
|
||||
oggbs.firstBytePos = init.oggFirstBytePos;
|
||||
oggbs.serialNumber = init.oggSerial;
|
||||
oggbs.bosPageHeader = init.oggBosHeader;
|
||||
oggbs.bytesRemainingInPage = 0;
|
||||
pOggbs = (drflac_oggbs*)drflac__malloc_from_callbacks(sizeof(*pOggbs), &allocationCallbacks);
|
||||
if (pOggbs == NULL) {
|
||||
return NULL; /*DRFLAC_OUT_OF_MEMORY;*/
|
||||
}
|
||||
|
||||
DRFLAC_ZERO_MEMORY(pOggbs, sizeof(*pOggbs));
|
||||
pOggbs->onRead = onRead;
|
||||
pOggbs->onSeek = onSeek;
|
||||
pOggbs->pUserData = pUserData;
|
||||
pOggbs->currentBytePos = init.oggFirstBytePos;
|
||||
pOggbs->firstBytePos = init.oggFirstBytePos;
|
||||
pOggbs->serialNumber = init.oggSerial;
|
||||
pOggbs->bosPageHeader = init.oggBosHeader;
|
||||
pOggbs->bytesRemainingInPage = 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
@ -7901,9 +7997,9 @@ static drflac* drflac_open_with_metadata_private(drflac_read_proc onRead, drflac
|
|||
consist of only a single heap allocation. To this, the size of the seek table needs to be known, which we determine when reading
|
||||
and decoding the metadata.
|
||||
*/
|
||||
firstFramePos = 42; /* <-- We know we are at byte 42 at this point. */
|
||||
seektablePos = 0;
|
||||
seektableSize = 0;
|
||||
firstFramePos = 42; /* <-- We know we are at byte 42 at this point. */
|
||||
seektablePos = 0;
|
||||
seekpointCount = 0;
|
||||
if (init.hasMetadataBlocks) {
|
||||
drflac_read_proc onReadOverride = onRead;
|
||||
drflac_seek_proc onSeekOverride = onSeek;
|
||||
|
@ -7913,20 +8009,26 @@ static drflac* drflac_open_with_metadata_private(drflac_read_proc onRead, drflac
|
|||
if (init.container == drflac_container_ogg) {
|
||||
onReadOverride = drflac__on_read_ogg;
|
||||
onSeekOverride = drflac__on_seek_ogg;
|
||||
pUserDataOverride = (void*)&oggbs;
|
||||
pUserDataOverride = (void*)pOggbs;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!drflac__read_and_decode_metadata(onReadOverride, onSeekOverride, onMeta, pUserDataOverride, pUserDataMD, &firstFramePos, &seektablePos, &seektableSize, &allocationCallbacks)) {
|
||||
if (!drflac__read_and_decode_metadata(onReadOverride, onSeekOverride, onMeta, pUserDataOverride, pUserDataMD, &firstFramePos, &seektablePos, &seekpointCount, &allocationCallbacks)) {
|
||||
#ifndef DR_FLAC_NO_OGG
|
||||
drflac__free_from_callbacks(pOggbs, &allocationCallbacks);
|
||||
#endif
|
||||
return NULL;
|
||||
}
|
||||
|
||||
allocationSize += seektableSize;
|
||||
allocationSize += seekpointCount * sizeof(drflac_seekpoint);
|
||||
}
|
||||
|
||||
|
||||
pFlac = (drflac*)drflac__malloc_from_callbacks(allocationSize, &allocationCallbacks);
|
||||
if (pFlac == NULL) {
|
||||
#ifndef DR_FLAC_NO_OGG
|
||||
drflac__free_from_callbacks(pOggbs, &allocationCallbacks);
|
||||
#endif
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
@ -7936,8 +8038,12 @@ static drflac* drflac_open_with_metadata_private(drflac_read_proc onRead, drflac
|
|||
|
||||
#ifndef DR_FLAC_NO_OGG
|
||||
if (init.container == drflac_container_ogg) {
|
||||
drflac_oggbs* pInternalOggbs = (drflac_oggbs*)((drflac_uint8*)pFlac->pDecodedSamples + decodedSamplesAllocationSize + seektableSize);
|
||||
*pInternalOggbs = oggbs;
|
||||
drflac_oggbs* pInternalOggbs = (drflac_oggbs*)((drflac_uint8*)pFlac->pDecodedSamples + decodedSamplesAllocationSize + (seekpointCount * sizeof(drflac_seekpoint)));
|
||||
DRFLAC_COPY_MEMORY(pInternalOggbs, pOggbs, sizeof(*pOggbs));
|
||||
|
||||
/* At this point the pOggbs object has been handed over to pInternalOggbs and can be freed. */
|
||||
drflac__free_from_callbacks(pOggbs, &allocationCallbacks);
|
||||
pOggbs = NULL;
|
||||
|
||||
/* The Ogg bistream needs to be layered on top of the original bitstream. */
|
||||
pFlac->bs.onRead = drflac__on_read_ogg;
|
||||
|
@ -7961,7 +8067,7 @@ static drflac* drflac_open_with_metadata_private(drflac_read_proc onRead, drflac
|
|||
{
|
||||
/* If we have a seektable we need to load it now, making sure we move back to where we were previously. */
|
||||
if (seektablePos != 0) {
|
||||
pFlac->seekpointCount = seektableSize / sizeof(*pFlac->pSeekpoints);
|
||||
pFlac->seekpointCount = seekpointCount;
|
||||
pFlac->pSeekpoints = (drflac_seekpoint*)((drflac_uint8*)pFlac->pDecodedSamples + decodedSamplesAllocationSize);
|
||||
|
||||
DRFLAC_ASSERT(pFlac->bs.onSeek != NULL);
|
||||
|
@ -7969,18 +8075,20 @@ static drflac* drflac_open_with_metadata_private(drflac_read_proc onRead, drflac
|
|||
|
||||
/* Seek to the seektable, then just read directly into our seektable buffer. */
|
||||
if (pFlac->bs.onSeek(pFlac->bs.pUserData, (int)seektablePos, drflac_seek_origin_start)) {
|
||||
if (pFlac->bs.onRead(pFlac->bs.pUserData, pFlac->pSeekpoints, seektableSize) == seektableSize) {
|
||||
/* Endian swap. */
|
||||
drflac_uint32 iSeekpoint;
|
||||
for (iSeekpoint = 0; iSeekpoint < pFlac->seekpointCount; ++iSeekpoint) {
|
||||
drflac_uint32 iSeekpoint;
|
||||
|
||||
for (iSeekpoint = 0; iSeekpoint < seekpointCount; iSeekpoint += 1) {
|
||||
if (pFlac->bs.onRead(pFlac->bs.pUserData, pFlac->pSeekpoints + iSeekpoint, DRFLAC_SEEKPOINT_SIZE_IN_BYTES) == DRFLAC_SEEKPOINT_SIZE_IN_BYTES) {
|
||||
/* Endian swap. */
|
||||
pFlac->pSeekpoints[iSeekpoint].firstPCMFrame = drflac__be2host_64(pFlac->pSeekpoints[iSeekpoint].firstPCMFrame);
|
||||
pFlac->pSeekpoints[iSeekpoint].flacFrameOffset = drflac__be2host_64(pFlac->pSeekpoints[iSeekpoint].flacFrameOffset);
|
||||
pFlac->pSeekpoints[iSeekpoint].pcmFrameCount = drflac__be2host_16(pFlac->pSeekpoints[iSeekpoint].pcmFrameCount);
|
||||
} else {
|
||||
/* Failed to read the seektable. Pretend we don't have one. */
|
||||
pFlac->pSeekpoints = NULL;
|
||||
pFlac->seekpointCount = 0;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
/* Failed to read the seektable. Pretend we don't have one. */
|
||||
pFlac->pSeekpoints = NULL;
|
||||
pFlac->seekpointCount = 0;
|
||||
}
|
||||
|
||||
/* We need to seek back to where we were. If this fails it's a critical error. */
|
||||
|
@ -8029,7 +8137,9 @@ static drflac* drflac_open_with_metadata_private(drflac_read_proc onRead, drflac
|
|||
|
||||
#ifndef DR_FLAC_NO_STDIO
|
||||
#include <stdio.h>
|
||||
#ifndef DR_FLAC_NO_WCHAR
|
||||
#include <wchar.h> /* For wcslen(), wcsrtombs() */
|
||||
#endif
|
||||
|
||||
/* drflac_result_from_errno() is only used for fopen() and wfopen() so putting it inside DR_WAV_NO_STDIO for now. If something else needs this later we can move it out. */
|
||||
#include <errno.h>
|
||||
|
@ -8495,6 +8605,7 @@ fallback, so if you notice your compiler not detecting this properly I'm happy t
|
|||
#endif
|
||||
#endif
|
||||
|
||||
#ifndef DR_FLAC_NO_WCHAR
|
||||
static drflac_result drflac_wfopen(FILE** ppFile, const wchar_t* pFilePath, const wchar_t* pOpenMode, const drflac_allocation_callbacks* pAllocationCallbacks)
|
||||
{
|
||||
if (ppFile != NULL) {
|
||||
|
@ -8523,10 +8634,23 @@ static drflac_result drflac_wfopen(FILE** ppFile, const wchar_t* pFilePath, cons
|
|||
}
|
||||
#else
|
||||
/*
|
||||
Use fopen() on anything other than Windows. Requires a conversion. This is annoying because fopen() is locale specific. The only real way I can
|
||||
think of to do this is with wcsrtombs(). Note that wcstombs() is apparently not thread-safe because it uses a static global mbstate_t object for
|
||||
maintaining state. I've checked this with -std=c89 and it works, but if somebody get's a compiler error I'll look into improving compatibility.
|
||||
Use fopen() on anything other than Windows. Requires a conversion. This is annoying because
|
||||
fopen() is locale specific. The only real way I can think of to do this is with wcsrtombs(). Note
|
||||
that wcstombs() is apparently not thread-safe because it uses a static global mbstate_t object for
|
||||
maintaining state. I've checked this with -std=c89 and it works, but if somebody get's a compiler
|
||||
error I'll look into improving compatibility.
|
||||
*/
|
||||
|
||||
/*
|
||||
Some compilers don't support wchar_t or wcsrtombs() which we're using below. In this case we just
|
||||
need to abort with an error. If you encounter a compiler lacking such support, add it to this list
|
||||
and submit a bug report and it'll be added to the library upstream.
|
||||
*/
|
||||
#if defined(__DJGPP__)
|
||||
{
|
||||
/* Nothing to do here. This will fall through to the error check below. */
|
||||
}
|
||||
#else
|
||||
{
|
||||
mbstate_t mbs;
|
||||
size_t lenMB;
|
||||
|
@ -8568,6 +8692,7 @@ static drflac_result drflac_wfopen(FILE** ppFile, const wchar_t* pFilePath, cons
|
|||
|
||||
drflac__free_from_callbacks(pFilePathMB, pAllocationCallbacks);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (*ppFile == NULL) {
|
||||
return DRFLAC_ERROR;
|
||||
|
@ -8576,6 +8701,7 @@ static drflac_result drflac_wfopen(FILE** ppFile, const wchar_t* pFilePath, cons
|
|||
|
||||
return DRFLAC_SUCCESS;
|
||||
}
|
||||
#endif
|
||||
|
||||
static size_t drflac__on_read_stdio(void* pUserData, void* bufferOut, size_t bytesToRead)
|
||||
{
|
||||
|
@ -8608,6 +8734,7 @@ DRFLAC_API drflac* drflac_open_file(const char* pFileName, const drflac_allocati
|
|||
return pFlac;
|
||||
}
|
||||
|
||||
#ifndef DR_FLAC_NO_WCHAR
|
||||
DRFLAC_API drflac* drflac_open_file_w(const wchar_t* pFileName, const drflac_allocation_callbacks* pAllocationCallbacks)
|
||||
{
|
||||
drflac* pFlac;
|
||||
|
@ -8625,6 +8752,7 @@ DRFLAC_API drflac* drflac_open_file_w(const wchar_t* pFileName, const drflac_all
|
|||
|
||||
return pFlac;
|
||||
}
|
||||
#endif
|
||||
|
||||
DRFLAC_API drflac* drflac_open_file_with_metadata(const char* pFileName, drflac_meta_proc onMeta, void* pUserData, const drflac_allocation_callbacks* pAllocationCallbacks)
|
||||
{
|
||||
|
@ -8644,6 +8772,7 @@ DRFLAC_API drflac* drflac_open_file_with_metadata(const char* pFileName, drflac_
|
|||
return pFlac;
|
||||
}
|
||||
|
||||
#ifndef DR_FLAC_NO_WCHAR
|
||||
DRFLAC_API drflac* drflac_open_file_with_metadata_w(const wchar_t* pFileName, drflac_meta_proc onMeta, void* pUserData, const drflac_allocation_callbacks* pAllocationCallbacks)
|
||||
{
|
||||
drflac* pFlac;
|
||||
|
@ -8661,6 +8790,7 @@ DRFLAC_API drflac* drflac_open_file_with_metadata_w(const wchar_t* pFileName, dr
|
|||
|
||||
return pFlac;
|
||||
}
|
||||
#endif
|
||||
#endif /* DR_FLAC_NO_STDIO */
|
||||
|
||||
static size_t drflac__on_read_memory(void* pUserData, void* bufferOut, size_t bytesToRead)
|
||||
|
@ -11928,6 +12058,18 @@ DRFLAC_API drflac_bool32 drflac_next_cuesheet_track(drflac_cuesheet_track_iterat
|
|||
/*
|
||||
REVISION HISTORY
|
||||
================
|
||||
v0.12.39 - 2022-09-17
|
||||
- Fix compilation with DJGPP.
|
||||
- Fix compilation error with Visual Studio 2019 and the ARM build.
|
||||
- Fix an error with SSE 4.1 detection.
|
||||
- Add support for disabling wchar_t with DR_WAV_NO_WCHAR.
|
||||
- Improve compatibility with compilers which lack support for explicit struct packing.
|
||||
- Improve compatibility with low-end and embedded hardware by reducing the amount of stack
|
||||
allocation when loading an Ogg encapsulated file.
|
||||
|
||||
v0.12.38 - 2022-04-10
|
||||
- Fix compilation error on older versions of GCC.
|
||||
|
||||
v0.12.37 - 2022-02-12
|
||||
- Improve ARM detection.
|
||||
|
||||
|
|
|
@ -48,6 +48,7 @@ extern "C" {
|
|||
|
||||
#include <libchdr/coretypes.h>
|
||||
#include <libchdr/chdconfig.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
/***************************************************************************
|
||||
|
||||
|
@ -204,6 +205,9 @@ extern "C" {
|
|||
|
||||
#define CHD_CODEC_NONE 0
|
||||
#define CHD_CODEC_ZLIB CHD_MAKE_TAG('z','l','i','b')
|
||||
#define CHD_CODEC_LZMA CHD_MAKE_TAG('l','z','m','a')
|
||||
#define CHD_CODEC_HUFFMAN CHD_MAKE_TAG('h','u','f','f')
|
||||
#define CHD_CODEC_FLAC CHD_MAKE_TAG('f','l','a','c')
|
||||
/* general codecs with CD frontend */
|
||||
#define CHD_CODEC_CD_ZLIB CHD_MAKE_TAG('c','d','z','l')
|
||||
#define CHD_CODEC_CD_LZMA CHD_MAKE_TAG('c','d','l','z')
|
||||
|
@ -253,6 +257,7 @@ extern "C" {
|
|||
/* CHD open values */
|
||||
#define CHD_OPEN_READ 1
|
||||
#define CHD_OPEN_READWRITE 2
|
||||
#define CHD_OPEN_TRANSFER_FILE 4 /* Freeing of the FILE* is now libchdr's responsibility if open was successful */
|
||||
|
||||
/* error types */
|
||||
enum _chd_error
|
||||
|
@ -370,7 +375,8 @@ struct _chd_verify_result
|
|||
/* chd_error chd_create_file(core_file *file, UINT64 logicalbytes, UINT32 hunkbytes, UINT32 compression, chd_file *parent); */
|
||||
|
||||
/* open an existing CHD file */
|
||||
CHD_EXPORT chd_error chd_open_file(core_file *file, int mode, chd_file *parent, chd_file **chd);
|
||||
CHD_EXPORT chd_error chd_open_core_file(core_file *file, int mode, chd_file *parent, chd_file **chd);
|
||||
CHD_EXPORT chd_error chd_open_file(FILE *file, int mode, chd_file *parent, chd_file **chd);
|
||||
CHD_EXPORT chd_error chd_open(const char *filename, int mode, chd_file *parent, chd_file **chd);
|
||||
|
||||
/* precache underlying file */
|
||||
|
@ -394,7 +400,10 @@ CHD_EXPORT const char *chd_error_string(chd_error err);
|
|||
CHD_EXPORT const chd_header *chd_get_header(chd_file *chd);
|
||||
|
||||
/* read CHD header data from file into the pointed struct */
|
||||
CHD_EXPORT chd_error chd_read_header_core_file(core_file *file, chd_header *header);
|
||||
CHD_EXPORT chd_error chd_read_header_file(FILE *file, chd_header *header);
|
||||
CHD_EXPORT chd_error chd_read_header(const char *filename, chd_header *header);
|
||||
CHD_EXPORT bool chd_is_matching_parent(const chd_header* header, const chd_header* parent_header);
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -4,8 +4,21 @@
|
|||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#ifdef USE_LIBRETRO_VFS
|
||||
#include <streams/file_stream_transforms.h>
|
||||
#endif
|
||||
|
||||
#define ARRAY_LENGTH(x) (sizeof(x)/sizeof(x[0]))
|
||||
|
||||
#if defined(__PS3__) || defined(__PSL1GHT__)
|
||||
#undef UINT32
|
||||
#undef UINT16
|
||||
#undef UINT8
|
||||
#undef INT32
|
||||
#undef INT16
|
||||
#undef INT8
|
||||
#endif
|
||||
|
||||
typedef uint64_t UINT64;
|
||||
typedef uint32_t UINT32;
|
||||
typedef uint16_t UINT16;
|
||||
|
@ -16,33 +29,50 @@ typedef int32_t INT32;
|
|||
typedef int16_t INT16;
|
||||
typedef int8_t INT8;
|
||||
|
||||
#define core_file FILE
|
||||
#define core_fopen(file) fopen(file, "rb")
|
||||
typedef struct chd_core_file {
|
||||
/*
|
||||
* arbitrary pointer to data the implementation uses to implement the below functions
|
||||
*/
|
||||
void *argp;
|
||||
|
||||
#if defined(__WIN32__) || defined(_WIN32) || defined(WIN32) || defined(__WIN64__)
|
||||
#define core_fseek _fseeki64
|
||||
#define core_ftell _ftelli64
|
||||
#elif defined(_LARGEFILE_SOURCE) && defined(_FILE_OFFSET_BITS) && _FILE_OFFSET_BITS == 64
|
||||
#define core_fseek fseeko64
|
||||
#define core_ftell ftello64
|
||||
#elif defined(__PS3__) && !defined(__PSL1GHT__) || defined(__SWITCH__)
|
||||
#define core_fseek(x,y,z) fseek(x,(off_t)y,z)
|
||||
#define core_ftell(x) (off_t)ftell(x)
|
||||
#else
|
||||
#define core_fseek fseeko
|
||||
#define core_ftell ftello
|
||||
#endif
|
||||
#define core_fread(fc, buff, len) fread(buff, 1, len, fc)
|
||||
#define core_fclose fclose
|
||||
/*
|
||||
* return the size of a given file as a 64-bit unsigned integer.
|
||||
* the position of the file pointer after calling this function is
|
||||
* undefined because many implementations will seek to the end of the
|
||||
* file and call ftell.
|
||||
*
|
||||
* on error, (UINT64)-1 is returned.
|
||||
*/
|
||||
UINT64(*fsize)(struct chd_core_file*);
|
||||
|
||||
static inline UINT64 core_fsize(core_file *f)
|
||||
/*
|
||||
* should match the behavior of fread, except the FILE* argument at the end
|
||||
* will be replaced with a struct chd_core_file*.
|
||||
*/
|
||||
size_t(*fread)(void*,size_t,size_t,struct chd_core_file*);
|
||||
|
||||
// closes the given file.
|
||||
int (*fclose)(struct chd_core_file*);
|
||||
|
||||
// fseek clone
|
||||
int (*fseek)(struct chd_core_file*, INT64, int);
|
||||
} core_file;
|
||||
|
||||
static inline int core_fclose(core_file *fp) {
|
||||
return fp->fclose(fp);
|
||||
}
|
||||
|
||||
static inline size_t core_fread(core_file *fp, void *ptr, size_t len) {
|
||||
return fp->fread(ptr, 1, len, fp);
|
||||
}
|
||||
|
||||
static inline int core_fseek(core_file* fp, INT64 offset, int whence) {
|
||||
return fp->fseek(fp, offset, whence);
|
||||
}
|
||||
|
||||
static inline UINT64 core_fsize(core_file *fp)
|
||||
{
|
||||
UINT64 rv;
|
||||
UINT64 p = core_ftell(f);
|
||||
core_fseek(f, 0, SEEK_END);
|
||||
rv = core_ftell(f);
|
||||
core_fseek(f, p, SEEK_SET);
|
||||
return rv;
|
||||
return fp->fsize(fp);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
|
@ -42,6 +42,7 @@
|
|||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include <limits.h>
|
||||
|
||||
#include <libchdr/chd.h>
|
||||
#include <libchdr/cdrom.h>
|
||||
|
@ -227,6 +228,12 @@ struct _lzma_codec_data
|
|||
lzma_allocator allocator;
|
||||
};
|
||||
|
||||
typedef struct _huff_codec_data huff_codec_data;
|
||||
struct _huff_codec_data
|
||||
{
|
||||
struct huffman_decoder* decoder;
|
||||
};
|
||||
|
||||
/* codec-private data for the CDZL codec */
|
||||
typedef struct _cdzl_codec_data cdzl_codec_data;
|
||||
struct _cdzl_codec_data {
|
||||
|
@ -249,6 +256,14 @@ struct _cdlz_codec_data {
|
|||
uint8_t* buffer;
|
||||
};
|
||||
|
||||
/* codec-private data for the FLAC codec */
|
||||
typedef struct _flac_codec_data flac_codec_data;
|
||||
struct _flac_codec_data {
|
||||
/* internal state */
|
||||
int native_endian;
|
||||
flac_decoder decoder;
|
||||
};
|
||||
|
||||
/* codec-private data for the CDFL codec */
|
||||
typedef struct _cdfl_codec_data cdfl_codec_data;
|
||||
struct _cdfl_codec_data {
|
||||
|
@ -267,7 +282,6 @@ struct _chd_file
|
|||
UINT32 cookie; /* cookie, should equal COOKIE_VALUE */
|
||||
|
||||
core_file * file; /* handle to the open core file */
|
||||
UINT8 owns_file; /* flag indicating if this file should be closed on chd_close() */
|
||||
chd_header header; /* header, extracted from file */
|
||||
|
||||
chd_file * parent; /* pointer to parent file, or NULL */
|
||||
|
@ -286,6 +300,9 @@ struct _chd_file
|
|||
const codec_interface * codecintf[4]; /* interface to the codec */
|
||||
|
||||
zlib_codec_data zlib_codec_data; /* zlib codec data */
|
||||
lzma_codec_data lzma_codec_data; /* lzma codec data */
|
||||
huff_codec_data huff_codec_data; /* huff codec data */
|
||||
flac_codec_data flac_codec_data; /* flac codec data */
|
||||
cdzl_codec_data cdzl_codec_data; /* cdzl codec data */
|
||||
cdlz_codec_data cdlz_codec_data; /* cdlz codec data */
|
||||
cdfl_codec_data cdfl_codec_data; /* cdfl codec data */
|
||||
|
@ -309,6 +326,14 @@ static const UINT8 nullsha1[CHD_SHA1_BYTES] = { 0 };
|
|||
PROTOTYPES
|
||||
***************************************************************************/
|
||||
|
||||
/* core_file wrappers over stdio */
|
||||
static core_file *core_stdio_fopen(char const *path);
|
||||
static UINT64 core_stdio_fsize(core_file *file);
|
||||
static size_t core_stdio_fread(void *ptr, size_t size, size_t nmemb, core_file *file);
|
||||
static int core_stdio_fclose(core_file *file);
|
||||
static int core_stdio_fclose_nonowner(core_file *file); // alternate fclose used by chd_open_file
|
||||
static int core_stdio_fseek(core_file* file, INT64 offset, int whence);
|
||||
|
||||
/* internal header operations */
|
||||
static chd_error header_validate(const chd_header *header);
|
||||
static chd_error header_read(chd_file *chd, chd_header *header);
|
||||
|
@ -338,6 +363,16 @@ static chd_error lzma_codec_init(void *codec, uint32_t hunkbytes);
|
|||
static void lzma_codec_free(void *codec);
|
||||
static chd_error lzma_codec_decompress(void *codec, const uint8_t *src, uint32_t complen, uint8_t *dest, uint32_t destlen);
|
||||
|
||||
/* huff compression codec */
|
||||
static chd_error huff_codec_init(void *codec, uint32_t hunkbytes);
|
||||
static void huff_codec_free(void *codec);
|
||||
static chd_error huff_codec_decompress(void *codec, const uint8_t *src, uint32_t complen, uint8_t *dest, uint32_t destlen);
|
||||
|
||||
/* flac compression codec */
|
||||
static chd_error flac_codec_init(void *codec, uint32_t hunkbytes);
|
||||
static void flac_codec_free(void *codec);
|
||||
static chd_error flac_codec_decompress(void *codec, const uint8_t *src, uint32_t complen, uint8_t *dest, uint32_t destlen);
|
||||
|
||||
/* cdzl compression codec */
|
||||
static chd_error cdzl_codec_init(void* codec, uint32_t hunkbytes);
|
||||
static void cdzl_codec_free(void* codec);
|
||||
|
@ -749,24 +784,121 @@ static chd_error cdzl_codec_decompress(void *codec, const uint8_t *src, uint32_t
|
|||
return CHDERR_NONE;
|
||||
}
|
||||
|
||||
/***************************************************************************
|
||||
* HUFFMAN DECOMPRESSOR
|
||||
***************************************************************************
|
||||
*/
|
||||
|
||||
static chd_error huff_codec_init(void* codec, uint32_t hunkbytes)
|
||||
{
|
||||
huff_codec_data* huff_codec = (huff_codec_data*) codec;
|
||||
huff_codec->decoder = create_huffman_decoder(256, 16);
|
||||
return CHDERR_NONE;
|
||||
}
|
||||
|
||||
static void huff_codec_free(void *codec)
|
||||
{
|
||||
huff_codec_data* huff_codec = (huff_codec_data*) codec;
|
||||
delete_huffman_decoder(huff_codec->decoder);
|
||||
}
|
||||
|
||||
static chd_error huff_codec_decompress(void *codec, const uint8_t *src, uint32_t complen, uint8_t *dest, uint32_t destlen)
|
||||
{
|
||||
huff_codec_data* huff_codec = (huff_codec_data*) codec;
|
||||
struct bitstream* bitbuf = create_bitstream(src, complen);
|
||||
|
||||
// first import the tree
|
||||
enum huffman_error err = huffman_import_tree_huffman(huff_codec->decoder, bitbuf);
|
||||
if (err != HUFFERR_NONE)
|
||||
{
|
||||
free(bitbuf);
|
||||
return err;
|
||||
}
|
||||
|
||||
// then decode the data
|
||||
for (uint32_t cur = 0; cur < destlen; cur++)
|
||||
dest[cur] = huffman_decode_one(huff_codec->decoder, bitbuf);
|
||||
bitstream_flush(bitbuf);
|
||||
chd_error result = bitstream_overflow(bitbuf) ? CHDERR_DECOMPRESSION_ERROR : CHDERR_NONE;
|
||||
|
||||
free(bitbuf);
|
||||
return result;
|
||||
}
|
||||
|
||||
/***************************************************************************
|
||||
* CD FLAC DECOMPRESSOR
|
||||
***************************************************************************
|
||||
*/
|
||||
|
||||
/*------------------------------------------------------
|
||||
* cdfl_codec_blocksize - return the optimal block size
|
||||
* flac_codec_blocksize - return the optimal block size
|
||||
*------------------------------------------------------
|
||||
*/
|
||||
|
||||
static uint32_t cdfl_codec_blocksize(uint32_t bytes)
|
||||
static uint32_t flac_codec_blocksize(uint32_t bytes)
|
||||
{
|
||||
/* determine FLAC block size, which must be 16-65535
|
||||
* clamp to 2k since that's supposed to be the sweet spot */
|
||||
uint32_t hunkbytes = bytes / 4;
|
||||
while (hunkbytes > 2048)
|
||||
hunkbytes /= 2;
|
||||
return hunkbytes;
|
||||
uint32_t blocksize = bytes / 4;
|
||||
while (blocksize > 2048)
|
||||
blocksize /= 2;
|
||||
return blocksize;
|
||||
}
|
||||
|
||||
static chd_error flac_codec_init(void *codec, uint32_t hunkbytes)
|
||||
{
|
||||
uint16_t native_endian = 0;
|
||||
flac_codec_data *flac = (flac_codec_data*)codec;
|
||||
|
||||
/* make sure the CHD's hunk size is an even multiple of the sample size */
|
||||
if (hunkbytes % 4 != 0)
|
||||
return CHDERR_CODEC_ERROR;
|
||||
|
||||
/* determine whether we want native or swapped samples */
|
||||
*(uint8_t *)(&native_endian) = 1;
|
||||
flac->native_endian = (native_endian & 1);
|
||||
|
||||
/* flac decoder init */
|
||||
if (flac_decoder_init(&flac->decoder))
|
||||
return CHDERR_OUT_OF_MEMORY;
|
||||
|
||||
return CHDERR_NONE;
|
||||
}
|
||||
|
||||
static void flac_codec_free(void *codec)
|
||||
{
|
||||
flac_codec_data *flac = (flac_codec_data*)codec;
|
||||
flac_decoder_free(&flac->decoder);
|
||||
}
|
||||
|
||||
static chd_error flac_codec_decompress(void *codec, const uint8_t *src, uint32_t complen, uint8_t *dest, uint32_t destlen)
|
||||
{
|
||||
flac_codec_data *flac = (flac_codec_data*)codec;
|
||||
int swap_endian;
|
||||
|
||||
if (src[0] == 'L')
|
||||
swap_endian = !flac->native_endian;
|
||||
else if (src[0] == 'B')
|
||||
swap_endian = flac->native_endian;
|
||||
else
|
||||
return CHDERR_DECOMPRESSION_ERROR;
|
||||
|
||||
if (!flac_decoder_reset(&flac->decoder, 44100, 2, flac_codec_blocksize(destlen), src + 1, complen - 1))
|
||||
return CHDERR_DECOMPRESSION_ERROR;
|
||||
if (!flac_decoder_decode_interleaved(&flac->decoder, (int16_t *)(dest), destlen/4, swap_endian))
|
||||
return CHDERR_DECOMPRESSION_ERROR;
|
||||
flac_decoder_finish(&flac->decoder);
|
||||
|
||||
return CHDERR_NONE;
|
||||
}
|
||||
|
||||
static uint32_t cdfl_codec_blocksize(uint32_t bytes)
|
||||
{
|
||||
// for CDs it seems that CD_MAX_SECTOR_DATA is the right target
|
||||
uint32_t blocksize = bytes / 4;
|
||||
while (blocksize > CD_MAX_SECTOR_DATA)
|
||||
blocksize /= 2;
|
||||
return blocksize;
|
||||
}
|
||||
|
||||
static chd_error cdfl_codec_init(void *codec, uint32_t hunkbytes)
|
||||
|
@ -904,6 +1036,39 @@ static const codec_interface codec_interfaces[] =
|
|||
NULL
|
||||
},
|
||||
|
||||
/* V5 lzma compression */
|
||||
{
|
||||
CHD_CODEC_LZMA,
|
||||
"lzma (LZMA)",
|
||||
FALSE,
|
||||
lzma_codec_init,
|
||||
lzma_codec_free,
|
||||
lzma_codec_decompress,
|
||||
NULL
|
||||
},
|
||||
|
||||
/* V5 huffman compression */
|
||||
{
|
||||
CHD_CODEC_HUFFMAN,
|
||||
"Huffman",
|
||||
FALSE,
|
||||
huff_codec_init,
|
||||
huff_codec_free,
|
||||
huff_codec_decompress,
|
||||
NULL
|
||||
},
|
||||
|
||||
/* V5 flac compression */
|
||||
{
|
||||
CHD_CODEC_FLAC,
|
||||
"flac (FLAC)",
|
||||
FALSE,
|
||||
flac_codec_init,
|
||||
flac_codec_free,
|
||||
flac_codec_decompress,
|
||||
NULL
|
||||
},
|
||||
|
||||
/* V5 CD zlib compression */
|
||||
{
|
||||
CHD_CODEC_CD_ZLIB,
|
||||
|
@ -1185,6 +1350,8 @@ static chd_error decompress_v5_map(chd_file* chd, chd_header* header)
|
|||
if (!chd_compressed(header))
|
||||
{
|
||||
header->rawmap = (uint8_t*)malloc(rawmapsize);
|
||||
if (header->rawmap == NULL)
|
||||
return CHDERR_OUT_OF_MEMORY;
|
||||
core_fseek(chd->file, header->mapoffset, SEEK_SET);
|
||||
result = core_fread(chd->file, header->rawmap, rawmapsize);
|
||||
return CHDERR_NONE;
|
||||
|
@ -1202,10 +1369,18 @@ static chd_error decompress_v5_map(chd_file* chd, chd_header* header)
|
|||
|
||||
/* now read the map */
|
||||
compressed_ptr = (uint8_t*)malloc(sizeof(uint8_t) * mapbytes);
|
||||
if (compressed_ptr == NULL)
|
||||
return CHDERR_OUT_OF_MEMORY;
|
||||
core_fseek(chd->file, header->mapoffset + 16, SEEK_SET);
|
||||
result = core_fread(chd->file, compressed_ptr, mapbytes);
|
||||
bitbuf = create_bitstream(compressed_ptr, sizeof(uint8_t) * mapbytes);
|
||||
header->rawmap = (uint8_t*)malloc(rawmapsize);
|
||||
if (header->rawmap == NULL)
|
||||
{
|
||||
free(compressed_ptr);
|
||||
free(bitbuf);
|
||||
return CHDERR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
/* first decode the compression types */
|
||||
decoder = create_huffman_decoder(16, 8);
|
||||
|
@ -1343,7 +1518,30 @@ static inline void map_extract_old(const UINT8 *base, map_entry *entry, UINT32 h
|
|||
chd_open_file - open a CHD file for access
|
||||
-------------------------------------------------*/
|
||||
|
||||
CHD_EXPORT chd_error chd_open_file(core_file *file, int mode, chd_file *parent, chd_file **chd)
|
||||
CHD_EXPORT chd_error chd_open_file(FILE *file, int mode, chd_file *parent, chd_file **chd) {
|
||||
core_file *stream = malloc(sizeof(core_file));
|
||||
if (!stream)
|
||||
return CHDERR_OUT_OF_MEMORY;
|
||||
stream->argp = file;
|
||||
stream->fsize = core_stdio_fsize;
|
||||
stream->fread = core_stdio_fread;
|
||||
stream->fclose = core_stdio_fclose_nonowner;
|
||||
stream->fseek = core_stdio_fseek;
|
||||
|
||||
chd_error err = chd_open_core_file(stream, mode, parent, chd);
|
||||
if (err != CHDERR_NONE)
|
||||
return err;
|
||||
|
||||
// swap out the fclose so that we close it on chd clost
|
||||
stream->fclose = core_stdio_fclose;
|
||||
return CHDERR_NONE;
|
||||
}
|
||||
|
||||
/*-------------------------------------------------
|
||||
chd_open_core_file - open a CHD file for access
|
||||
-------------------------------------------------*/
|
||||
|
||||
CHD_EXPORT chd_error chd_open_core_file(core_file *file, int mode, chd_file *parent, chd_file **chd)
|
||||
{
|
||||
chd_file *newchd = NULL;
|
||||
chd_error err;
|
||||
|
@ -1492,6 +1690,18 @@ CHD_EXPORT chd_error chd_open_file(core_file *file, int mode, chd_file *parent,
|
|||
codec = &newchd->zlib_codec_data;
|
||||
break;
|
||||
|
||||
case CHD_CODEC_LZMA:
|
||||
codec = &newchd->lzma_codec_data;
|
||||
break;
|
||||
|
||||
case CHD_CODEC_HUFFMAN:
|
||||
codec = &newchd->huff_codec_data;
|
||||
break;
|
||||
|
||||
case CHD_CODEC_FLAC:
|
||||
codec = &newchd->flac_codec_data;
|
||||
break;
|
||||
|
||||
case CHD_CODEC_CD_ZLIB:
|
||||
codec = &newchd->cdzl_codec_data;
|
||||
break;
|
||||
|
@ -1532,33 +1742,33 @@ cleanup:
|
|||
|
||||
CHD_EXPORT chd_error chd_precache(chd_file* chd)
|
||||
{
|
||||
return chd_precache_progress(chd, NULL, NULL);
|
||||
return chd_precache_progress(chd, NULL, NULL);
|
||||
}
|
||||
|
||||
CHD_EXPORT chd_error chd_precache_progress(chd_file *chd, void(*progress)(size_t pos, size_t total, void* param), void* param)
|
||||
CHD_EXPORT chd_error chd_precache_progress(chd_file* chd, void(*progress)(size_t pos, size_t total, void* param), void* param)
|
||||
{
|
||||
#define PRECACHE_CHUNK_SIZE 16 * 1024 * 1024
|
||||
|
||||
#ifdef _MSC_VER
|
||||
size_t size, done, req_count, count, last_update_done, update_interval;
|
||||
#else
|
||||
ssize_t size, done, req_count, count, last_update_done, update_interval;
|
||||
#endif
|
||||
size_t count;
|
||||
UINT64 size, done, req_count, last_update_done, update_interval;
|
||||
|
||||
if (chd->file_cache == NULL)
|
||||
{
|
||||
core_fseek(chd->file, 0, SEEK_END);
|
||||
size = core_ftell(chd->file);
|
||||
if (size <= 0)
|
||||
return CHDERR_INVALID_DATA;
|
||||
chd->file_cache = malloc(size);
|
||||
if (chd->file_cache == NULL)
|
||||
if (chd->file_cache == NULL)
|
||||
{
|
||||
size = core_fsize(chd->file);
|
||||
if ((INT64)size <= 0)
|
||||
return CHDERR_INVALID_DATA;
|
||||
|
||||
if (size > SIZE_MAX)
|
||||
return CHDERR_OUT_OF_MEMORY;
|
||||
core_fseek(chd->file, 0, SEEK_SET);
|
||||
|
||||
chd->file_cache = malloc(size);
|
||||
if (chd->file_cache == NULL)
|
||||
return CHDERR_OUT_OF_MEMORY;
|
||||
core_fseek(chd->file, 0, SEEK_SET);
|
||||
|
||||
done = 0;
|
||||
last_update_done = 0;
|
||||
update_interval = ((size + 99) / 100);
|
||||
last_update_done = 0;
|
||||
update_interval = ((size + 99) / 100);
|
||||
|
||||
while (done < size)
|
||||
{
|
||||
|
@ -1566,8 +1776,8 @@ CHD_EXPORT chd_error chd_precache_progress(chd_file *chd, void(*progress)(size_t
|
|||
if (req_count > PRECACHE_CHUNK_SIZE)
|
||||
req_count = PRECACHE_CHUNK_SIZE;
|
||||
|
||||
count = core_fread(chd->file, chd->file_cache + done, req_count);
|
||||
if (count != req_count)
|
||||
count = core_fread(chd->file, chd->file_cache + (size_t)done, (size_t)req_count);
|
||||
if (count != (size_t)req_count)
|
||||
{
|
||||
free(chd->file_cache);
|
||||
chd->file_cache = NULL;
|
||||
|
@ -1581,9 +1791,9 @@ CHD_EXPORT chd_error chd_precache_progress(chd_file *chd, void(*progress)(size_t
|
|||
progress(done, size, param);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return CHDERR_NONE;
|
||||
return CHDERR_NONE;
|
||||
}
|
||||
|
||||
/*-------------------------------------------------
|
||||
|
@ -1614,7 +1824,7 @@ CHD_EXPORT chd_error chd_open(const char *filename, int mode, chd_file *parent,
|
|||
}
|
||||
|
||||
/* open the file */
|
||||
file = core_fopen(filename);
|
||||
file = core_stdio_fopen(filename);
|
||||
if (file == 0)
|
||||
{
|
||||
err = CHDERR_FILE_NOT_FOUND;
|
||||
|
@ -1622,12 +1832,7 @@ CHD_EXPORT chd_error chd_open(const char *filename, int mode, chd_file *parent,
|
|||
}
|
||||
|
||||
/* now open the CHD */
|
||||
err = chd_open_file(file, mode, parent, chd);
|
||||
if (err != CHDERR_NONE)
|
||||
goto cleanup;
|
||||
|
||||
/* we now own this file */
|
||||
(*chd)->owns_file = TRUE;
|
||||
return chd_open_core_file(file, mode, parent, chd);
|
||||
|
||||
cleanup:
|
||||
if ((err != CHDERR_NONE) && (file != NULL))
|
||||
|
@ -1664,18 +1869,30 @@ CHD_EXPORT void chd_close(chd_file *chd)
|
|||
|
||||
switch (chd->codecintf[i]->compression)
|
||||
{
|
||||
case CHD_CODEC_CD_LZMA:
|
||||
codec = &chd->cdlz_codec_data;
|
||||
break;
|
||||
|
||||
case CHD_CODEC_ZLIB:
|
||||
codec = &chd->zlib_codec_data;
|
||||
break;
|
||||
|
||||
case CHD_CODEC_LZMA:
|
||||
codec = &chd->lzma_codec_data;
|
||||
break;
|
||||
|
||||
case CHD_CODEC_HUFFMAN:
|
||||
codec = &chd->huff_codec_data;
|
||||
break;
|
||||
|
||||
case CHD_CODEC_FLAC:
|
||||
codec = &chd->flac_codec_data;
|
||||
break;
|
||||
|
||||
case CHD_CODEC_CD_ZLIB:
|
||||
codec = &chd->cdzl_codec_data;
|
||||
break;
|
||||
|
||||
case CHD_CODEC_CD_LZMA:
|
||||
codec = &chd->cdlz_codec_data;
|
||||
break;
|
||||
|
||||
case CHD_CODEC_CD_FLAC:
|
||||
codec = &chd->cdfl_codec_data;
|
||||
break;
|
||||
|
@ -1709,7 +1926,7 @@ CHD_EXPORT void chd_close(chd_file *chd)
|
|||
free(chd->map);
|
||||
|
||||
/* close the file */
|
||||
if (chd->owns_file && chd->file != NULL)
|
||||
if (chd->file != NULL)
|
||||
core_fclose(chd->file);
|
||||
|
||||
#ifdef NEED_CACHE_HUNK
|
||||
|
@ -1798,37 +2015,68 @@ CHD_EXPORT const chd_header *chd_get_header(chd_file *chd)
|
|||
chd_read_header - read CHD header data
|
||||
from file into the pointed struct
|
||||
-------------------------------------------------*/
|
||||
CHD_EXPORT chd_error chd_read_header(const char *filename, chd_header *header)
|
||||
CHD_EXPORT chd_error chd_read_header_core_file(core_file* file, chd_header* header)
|
||||
{
|
||||
chd_error err = CHDERR_NONE;
|
||||
chd_file chd;
|
||||
|
||||
/* punt if NULL */
|
||||
if (filename == NULL || header == NULL)
|
||||
EARLY_EXIT(err = CHDERR_INVALID_PARAMETER);
|
||||
|
||||
/* open the file */
|
||||
chd.file = core_fopen(filename);
|
||||
if (chd.file == NULL)
|
||||
EARLY_EXIT(err = CHDERR_FILE_NOT_FOUND);
|
||||
chd.file = file;
|
||||
|
||||
/* attempt to read the header */
|
||||
err = header_read(&chd, header);
|
||||
const chd_error err = header_read(&chd, header);
|
||||
if (err != CHDERR_NONE)
|
||||
EARLY_EXIT(err);
|
||||
return err;
|
||||
|
||||
/* validate the header */
|
||||
err = header_validate(header);
|
||||
if (err != CHDERR_NONE)
|
||||
EARLY_EXIT(err);
|
||||
return header_validate(header);
|
||||
}
|
||||
|
||||
cleanup:
|
||||
if (chd.file != NULL)
|
||||
core_fclose(chd.file);
|
||||
CHD_EXPORT chd_error chd_read_header_file(FILE *file, chd_header *header)
|
||||
{
|
||||
core_file stream;
|
||||
stream.argp = file;
|
||||
stream.fsize = core_stdio_fsize;
|
||||
stream.fread = core_stdio_fread;
|
||||
stream.fclose = core_stdio_fclose_nonowner;
|
||||
stream.fseek = core_stdio_fseek;
|
||||
|
||||
return chd_read_header_core_file(&stream, header);
|
||||
}
|
||||
|
||||
CHD_EXPORT chd_error chd_read_header(const char *filename, chd_header *header)
|
||||
{
|
||||
if (filename == NULL)
|
||||
return CHDERR_INVALID_PARAMETER;
|
||||
|
||||
core_file* file = core_stdio_fopen(filename);
|
||||
if (file == NULL)
|
||||
return CHDERR_FILE_NOT_FOUND;
|
||||
|
||||
chd_error err = chd_read_header_core_file(file, header);
|
||||
|
||||
core_fclose(file);
|
||||
return err;
|
||||
}
|
||||
|
||||
CHD_EXPORT bool chd_is_matching_parent(const chd_header* header, const chd_header* parent_header)
|
||||
{
|
||||
/* check MD5 if it isn't empty */
|
||||
if (memcmp(nullmd5, header->parentmd5, sizeof(header->parentmd5)) != 0 &&
|
||||
memcmp(nullmd5, parent_header->md5, sizeof(parent_header->md5)) != 0 &&
|
||||
memcmp(parent_header->md5, header->parentmd5, sizeof(header->parentmd5)) != 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/* check SHA1 if it isn't empty */
|
||||
if (memcmp(nullsha1, header->parentsha1, sizeof(header->parentsha1)) != 0 &&
|
||||
memcmp(nullsha1, parent_header->sha1, sizeof(parent_header->sha1)) != 0 &&
|
||||
memcmp(parent_header->sha1, header->parentsha1, sizeof(header->parentsha1)) != 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/***************************************************************************
|
||||
CORE DATA READ/WRITE
|
||||
***************************************************************************/
|
||||
|
@ -2096,6 +2344,8 @@ static chd_error header_read(chd_file *chd, chd_header *header)
|
|||
header->logicalbytes = (UINT64)header->obsolete_cylinders * (UINT64)header->obsolete_heads * (UINT64)header->obsolete_sectors * (UINT64)seclen;
|
||||
header->hunkbytes = seclen * header->obsolete_hunksize;
|
||||
header->unitbytes = header_guess_unitbytes(chd);
|
||||
if (header->unitbytes == 0)
|
||||
return CHDERR_INVALID_DATA;
|
||||
header->unitcount = (header->logicalbytes + header->unitbytes - 1) / header->unitbytes;
|
||||
header->metaoffset = 0;
|
||||
}
|
||||
|
@ -2110,6 +2360,8 @@ static chd_error header_read(chd_file *chd, chd_header *header)
|
|||
memcpy(header->parentmd5, &rawheader[60], CHD_MD5_BYTES);
|
||||
header->hunkbytes = get_bigendian_uint32(&rawheader[76]);
|
||||
header->unitbytes = header_guess_unitbytes(chd);
|
||||
if (header->unitbytes == 0)
|
||||
return CHDERR_INVALID_DATA;
|
||||
header->unitcount = (header->logicalbytes + header->unitbytes - 1) / header->unitbytes;
|
||||
memcpy(header->sha1, &rawheader[80], CHD_SHA1_BYTES);
|
||||
memcpy(header->parentsha1, &rawheader[100], CHD_SHA1_BYTES);
|
||||
|
@ -2123,6 +2375,8 @@ static chd_error header_read(chd_file *chd, chd_header *header)
|
|||
header->metaoffset = get_bigendian_uint64(&rawheader[36]);
|
||||
header->hunkbytes = get_bigendian_uint32(&rawheader[44]);
|
||||
header->unitbytes = header_guess_unitbytes(chd);
|
||||
if (header->unitbytes == 0)
|
||||
return CHDERR_INVALID_DATA;
|
||||
header->unitcount = (header->logicalbytes + header->unitbytes - 1) / header->unitbytes;
|
||||
memcpy(header->sha1, &rawheader[48], CHD_SHA1_BYTES);
|
||||
memcpy(header->parentsha1, &rawheader[68], CHD_SHA1_BYTES);
|
||||
|
@ -2141,8 +2395,12 @@ static chd_error header_read(chd_file *chd, chd_header *header)
|
|||
header->mapoffset = get_bigendian_uint64(&rawheader[40]);
|
||||
header->metaoffset = get_bigendian_uint64(&rawheader[48]);
|
||||
header->hunkbytes = get_bigendian_uint32(&rawheader[56]);
|
||||
if (header->hunkbytes == 0)
|
||||
return CHDERR_INVALID_DATA;
|
||||
header->hunkcount = (header->logicalbytes + header->hunkbytes - 1) / header->hunkbytes;
|
||||
header->unitbytes = get_bigendian_uint32(&rawheader[60]);
|
||||
if (header->unitbytes == 0)
|
||||
return CHDERR_INVALID_DATA;
|
||||
header->unitcount = (header->logicalbytes + header->unitbytes - 1) / header->unitbytes;
|
||||
memcpy(header->sha1, &rawheader[84], CHD_SHA1_BYTES);
|
||||
memcpy(header->parentsha1, &rawheader[104], CHD_SHA1_BYTES);
|
||||
|
@ -2382,18 +2640,30 @@ static chd_error hunk_read_into_memory(chd_file *chd, UINT32 hunknum, UINT8 *des
|
|||
return CHDERR_READ_ERROR;
|
||||
switch (chd->codecintf[rawmap[0]]->compression)
|
||||
{
|
||||
case CHD_CODEC_CD_LZMA:
|
||||
codec = &chd->cdlz_codec_data;
|
||||
break;
|
||||
|
||||
case CHD_CODEC_ZLIB:
|
||||
codec = &chd->zlib_codec_data;
|
||||
break;
|
||||
|
||||
case CHD_CODEC_LZMA:
|
||||
codec = &chd->lzma_codec_data;
|
||||
break;
|
||||
|
||||
case CHD_CODEC_HUFFMAN:
|
||||
codec = &chd->huff_codec_data;
|
||||
break;
|
||||
|
||||
case CHD_CODEC_FLAC:
|
||||
codec = &chd->flac_codec_data;
|
||||
break;
|
||||
|
||||
case CHD_CODEC_CD_ZLIB:
|
||||
codec = &chd->cdzl_codec_data;
|
||||
break;
|
||||
|
||||
case CHD_CODEC_CD_LZMA:
|
||||
codec = &chd->cdlz_codec_data;
|
||||
break;
|
||||
|
||||
case CHD_CODEC_CD_FLAC:
|
||||
codec = &chd->cdfl_codec_data;
|
||||
break;
|
||||
|
@ -2625,10 +2895,6 @@ static chd_error zlib_codec_init(void *codec, uint32_t hunkbytes)
|
|||
else
|
||||
err = CHDERR_NONE;
|
||||
|
||||
/* handle an error */
|
||||
if (err != CHDERR_NONE)
|
||||
free(data);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
|
@ -2768,3 +3034,87 @@ static void zlib_allocator_free(voidpf opaque)
|
|||
if (alloc->allocptr[i])
|
||||
free(alloc->allocptr[i]);
|
||||
}
|
||||
|
||||
/*-------------------------------------------------
|
||||
core_stdio_fopen - core_file wrapper over fopen
|
||||
-------------------------------------------------*/
|
||||
static core_file *core_stdio_fopen(char const *path) {
|
||||
core_file *file = malloc(sizeof(core_file));
|
||||
if (!file)
|
||||
return NULL;
|
||||
if (!(file->argp = fopen(path, "rb"))) {
|
||||
free(file);
|
||||
return NULL;
|
||||
}
|
||||
file->fsize = core_stdio_fsize;
|
||||
file->fread = core_stdio_fread;
|
||||
file->fclose = core_stdio_fclose;
|
||||
file->fseek = core_stdio_fseek;
|
||||
return file;
|
||||
}
|
||||
|
||||
/*-------------------------------------------------
|
||||
core_stdio_fsize - core_file function for
|
||||
getting file size with stdio
|
||||
-------------------------------------------------*/
|
||||
static UINT64 core_stdio_fsize(core_file *file) {
|
||||
#if defined USE_LIBRETRO_VFS
|
||||
#define core_stdio_fseek_impl fseek
|
||||
#define core_stdio_ftell_impl ftell
|
||||
#elif defined(__WIN32__) || defined(_WIN32) || defined(WIN32) || defined(__WIN64__)
|
||||
#define core_stdio_fseek_impl _fseeki64
|
||||
#define core_stdio_ftell_impl _ftelli64
|
||||
#elif defined(_LARGEFILE_SOURCE) && defined(_FILE_OFFSET_BITS) && _FILE_OFFSET_BITS == 64
|
||||
#define core_stdio_fseek_impl fseeko64
|
||||
#define core_stdio_ftell_impl ftello64
|
||||
#elif defined(__PS3__) && !defined(__PSL1GHT__) || defined(__SWITCH__) || defined(__vita__)
|
||||
#define core_stdio_fseek_impl(x,y,z) fseek(x,(off_t)y,z)
|
||||
#define core_stdio_ftell_impl(x) (off_t)ftell(x)
|
||||
#else
|
||||
#define core_stdio_fseek_impl fseeko
|
||||
#define core_stdio_ftell_impl ftello
|
||||
#endif
|
||||
FILE *fp;
|
||||
UINT64 p, rv;
|
||||
fp = (FILE*)file->argp;
|
||||
|
||||
p = core_stdio_ftell_impl(fp);
|
||||
core_stdio_fseek_impl(fp, 0, SEEK_END);
|
||||
rv = core_stdio_ftell_impl(fp);
|
||||
core_stdio_fseek_impl(fp, p, SEEK_SET);
|
||||
return rv;
|
||||
}
|
||||
|
||||
/*-------------------------------------------------
|
||||
core_stdio_fread - core_file wrapper over fread
|
||||
-------------------------------------------------*/
|
||||
static size_t core_stdio_fread(void *ptr, size_t size, size_t nmemb, core_file *file) {
|
||||
return fread(ptr, size, nmemb, (FILE*)file->argp);
|
||||
}
|
||||
|
||||
/*-------------------------------------------------
|
||||
core_stdio_fclose - core_file wrapper over fclose
|
||||
-------------------------------------------------*/
|
||||
static int core_stdio_fclose(core_file *file) {
|
||||
int err = fclose((FILE*)file->argp);
|
||||
if (err == 0)
|
||||
free(file);
|
||||
return err;
|
||||
}
|
||||
|
||||
/*-------------------------------------------------
|
||||
core_stdio_fclose_nonowner - don't call fclose because
|
||||
we don't own the underlying file, but do free the
|
||||
core_file because libchdr did allocate that itself.
|
||||
-------------------------------------------------*/
|
||||
static int core_stdio_fclose_nonowner(core_file *file) {
|
||||
free(file);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*-------------------------------------------------
|
||||
core_stdio_fseek - core_file wrapper over fclose
|
||||
-------------------------------------------------*/
|
||||
static int core_stdio_fseek(core_file* file, INT64 offset, int whence) {
|
||||
return core_stdio_fseek_impl((FILE*)file->argp, offset, whence);
|
||||
}
|
||||
|
|
|
@ -60,9 +60,10 @@ int flac_decoder_init(flac_decoder *decoder)
|
|||
|
||||
void flac_decoder_free(flac_decoder* decoder)
|
||||
{
|
||||
if ((decoder != NULL) && (decoder->decoder != NULL))
|
||||
if ((decoder != NULL) && (decoder->decoder != NULL)) {
|
||||
drflac_close(decoder->decoder);
|
||||
decoder->decoder = NULL;
|
||||
decoder->decoder = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/*-------------------------------------------------
|
||||
|
|
|
@ -212,6 +212,8 @@ enum huffman_error huffman_import_tree_rle(struct huffman_decoder* decoder, stru
|
|||
else
|
||||
{
|
||||
int repcount = bitstream_read(bitbuf, numbits) + 3;
|
||||
if (repcount + curnode > decoder->numcodes)
|
||||
return HUFFERR_INVALID_DATA;
|
||||
while (repcount--)
|
||||
decoder->huffnode[curnode++].numbits = nodebits;
|
||||
}
|
||||
|
|
|
@ -44,6 +44,7 @@ rc_api_fetch_code_notes_response_t;
|
|||
|
||||
int rc_api_init_fetch_code_notes_request(rc_api_request_t* request, const rc_api_fetch_code_notes_request_t* api_params);
|
||||
int rc_api_process_fetch_code_notes_response(rc_api_fetch_code_notes_response_t* response, const char* server_response);
|
||||
int rc_api_process_fetch_code_notes_server_response(rc_api_fetch_code_notes_response_t* response, const rc_api_server_response_t* server_response);
|
||||
void rc_api_destroy_fetch_code_notes_response(rc_api_fetch_code_notes_response_t* response);
|
||||
|
||||
/* --- Update Code Note --- */
|
||||
|
@ -76,6 +77,7 @@ rc_api_update_code_note_response_t;
|
|||
|
||||
int rc_api_init_update_code_note_request(rc_api_request_t* request, const rc_api_update_code_note_request_t* api_params);
|
||||
int rc_api_process_update_code_note_response(rc_api_update_code_note_response_t* response, const char* server_response);
|
||||
int rc_api_process_update_code_note_server_response(rc_api_update_code_note_response_t* response, const rc_api_server_response_t* server_response);
|
||||
void rc_api_destroy_update_code_note_response(rc_api_update_code_note_response_t* response);
|
||||
|
||||
/* --- Update Achievement --- */
|
||||
|
@ -121,6 +123,7 @@ rc_api_update_achievement_response_t;
|
|||
|
||||
int rc_api_init_update_achievement_request(rc_api_request_t* request, const rc_api_update_achievement_request_t* api_params);
|
||||
int rc_api_process_update_achievement_response(rc_api_update_achievement_response_t* response, const char* server_response);
|
||||
int rc_api_process_update_achievement_server_response(rc_api_update_achievement_response_t* response, const rc_api_server_response_t* server_response);
|
||||
void rc_api_destroy_update_achievement_response(rc_api_update_achievement_response_t* response);
|
||||
|
||||
/* --- Update Leaderboard --- */
|
||||
|
@ -170,6 +173,7 @@ rc_api_update_leaderboard_response_t;
|
|||
|
||||
int rc_api_init_update_leaderboard_request(rc_api_request_t* request, const rc_api_update_leaderboard_request_t* api_params);
|
||||
int rc_api_process_update_leaderboard_response(rc_api_update_leaderboard_response_t* response, const char* server_response);
|
||||
int rc_api_process_update_leaderboard_server_response(rc_api_update_leaderboard_response_t* response, const rc_api_server_response_t* server_response);
|
||||
void rc_api_destroy_update_leaderboard_response(rc_api_update_leaderboard_response_t* response);
|
||||
|
||||
/* --- Fetch Badge Range --- */
|
||||
|
@ -199,6 +203,7 @@ rc_api_fetch_badge_range_response_t;
|
|||
|
||||
int rc_api_init_fetch_badge_range_request(rc_api_request_t* request, const rc_api_fetch_badge_range_request_t* api_params);
|
||||
int rc_api_process_fetch_badge_range_response(rc_api_fetch_badge_range_response_t* response, const char* server_response);
|
||||
int rc_api_process_fetch_badge_range_server_response(rc_api_fetch_badge_range_response_t* response, const rc_api_server_response_t* server_response);
|
||||
void rc_api_destroy_fetch_badge_range_response(rc_api_fetch_badge_range_response_t* response);
|
||||
|
||||
/* --- Add Game Hash --- */
|
||||
|
@ -238,6 +243,7 @@ rc_api_add_game_hash_response_t;
|
|||
|
||||
int rc_api_init_add_game_hash_request(rc_api_request_t* request, const rc_api_add_game_hash_request_t* api_params);
|
||||
int rc_api_process_add_game_hash_response(rc_api_add_game_hash_response_t* response, const char* server_response);
|
||||
int rc_api_process_add_game_hash_server_response(rc_api_add_game_hash_response_t* response, const rc_api_server_response_t* server_response);
|
||||
void rc_api_destroy_add_game_hash_response(rc_api_add_game_hash_response_t* response);
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
|
|
@ -64,6 +64,7 @@ rc_api_fetch_achievement_info_response_t;
|
|||
|
||||
int rc_api_init_fetch_achievement_info_request(rc_api_request_t* request, const rc_api_fetch_achievement_info_request_t* api_params);
|
||||
int rc_api_process_fetch_achievement_info_response(rc_api_fetch_achievement_info_response_t* response, const char* server_response);
|
||||
int rc_api_process_fetch_achievement_info_server_response(rc_api_fetch_achievement_info_response_t* response, const rc_api_server_response_t* server_response);
|
||||
void rc_api_destroy_fetch_achievement_info_response(rc_api_fetch_achievement_info_response_t* response);
|
||||
|
||||
/* --- Fetch Leaderboard Info --- */
|
||||
|
@ -135,6 +136,7 @@ rc_api_fetch_leaderboard_info_response_t;
|
|||
|
||||
int rc_api_init_fetch_leaderboard_info_request(rc_api_request_t* request, const rc_api_fetch_leaderboard_info_request_t* api_params);
|
||||
int rc_api_process_fetch_leaderboard_info_response(rc_api_fetch_leaderboard_info_response_t* response, const char* server_response);
|
||||
int rc_api_process_fetch_leaderboard_info_server_response(rc_api_fetch_leaderboard_info_response_t* response, const rc_api_server_response_t* server_response);
|
||||
void rc_api_destroy_fetch_leaderboard_info_response(rc_api_fetch_leaderboard_info_response_t* response);
|
||||
|
||||
/* --- Fetch Games List --- */
|
||||
|
@ -173,6 +175,7 @@ rc_api_fetch_games_list_response_t;
|
|||
|
||||
int rc_api_init_fetch_games_list_request(rc_api_request_t* request, const rc_api_fetch_games_list_request_t* api_params);
|
||||
int rc_api_process_fetch_games_list_response(rc_api_fetch_games_list_response_t* response, const char* server_response);
|
||||
int rc_api_process_fetch_games_list_server_response(rc_api_fetch_games_list_response_t* response, const rc_api_server_response_t* server_response);
|
||||
void rc_api_destroy_fetch_games_list_response(rc_api_fetch_games_list_response_t* response);
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
|
|
@ -3,6 +3,8 @@
|
|||
|
||||
#include "rc_error.h"
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
@ -10,15 +12,25 @@ extern "C" {
|
|||
/**
|
||||
* A block of memory for variable length data (like strings and arrays).
|
||||
*/
|
||||
typedef struct rc_api_buffer_t {
|
||||
typedef struct rc_api_buffer_chunk_t {
|
||||
/* The current location where data is being written */
|
||||
char* write;
|
||||
/* The first byte past the end of data where writing cannot occur */
|
||||
char* end;
|
||||
/* The first byte of the data */
|
||||
char* start;
|
||||
/* The next block in the allocated memory chain */
|
||||
struct rc_api_buffer_t* next;
|
||||
/* The buffer containing the data. The actual size may be larger than 256 bytes for buffers allocated in
|
||||
* the next chain. The 256 byte size specified is for the initial allocation within the container object. */
|
||||
struct rc_api_buffer_chunk_t* next;
|
||||
}
|
||||
rc_api_buffer_chunk_t;
|
||||
|
||||
/**
|
||||
* A preallocated block of memory for variable length data (like strings and arrays).
|
||||
*/
|
||||
typedef struct rc_api_buffer_t {
|
||||
/* The chunk data (will point at the local data member) */
|
||||
struct rc_api_buffer_chunk_t chunk;
|
||||
/* Small chunk of memory pre-allocated for the chunk */
|
||||
char data[256];
|
||||
}
|
||||
rc_api_buffer_t;
|
||||
|
@ -31,6 +43,8 @@ typedef struct rc_api_request_t {
|
|||
const char* url;
|
||||
/* Additional query args that should be sent via a POST command. If null, GET may be used */
|
||||
const char* post_data;
|
||||
/* The HTTP Content-Type of the POST data. */
|
||||
const char* content_type;
|
||||
|
||||
/* Storage for the url and post_data */
|
||||
rc_api_buffer_t buffer;
|
||||
|
@ -41,7 +55,7 @@ rc_api_request_t;
|
|||
* Common attributes for all server responses.
|
||||
*/
|
||||
typedef struct rc_api_response_t {
|
||||
/* Server-provided success indicator (non-zero on failure) */
|
||||
/* Server-provided success indicator (non-zero on success, zero on failure) */
|
||||
int succeeded;
|
||||
/* Server-provided message associated to the failure */
|
||||
const char* error_message;
|
||||
|
@ -56,6 +70,15 @@ void rc_api_destroy_request(rc_api_request_t* request);
|
|||
void rc_api_set_host(const char* hostname);
|
||||
void rc_api_set_image_host(const char* hostname);
|
||||
|
||||
typedef struct rc_api_server_response_t {
|
||||
/* Pointer to the data returned from the server */
|
||||
const char* body;
|
||||
/* Length of data returned from the server (Content-Length) */
|
||||
size_t body_length;
|
||||
/* HTTP status code returned from the server */
|
||||
int http_status_code;
|
||||
} rc_api_server_response_t;
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -59,6 +59,7 @@ rc_api_resolve_hash_response_t;
|
|||
|
||||
int rc_api_init_resolve_hash_request(rc_api_request_t* request, const rc_api_resolve_hash_request_t* api_params);
|
||||
int rc_api_process_resolve_hash_response(rc_api_resolve_hash_response_t* response, const char* server_response);
|
||||
int rc_api_process_resolve_hash_server_response(rc_api_resolve_hash_response_t* response, const rc_api_server_response_t* server_response);
|
||||
void rc_api_destroy_resolve_hash_response(rc_api_resolve_hash_response_t* response);
|
||||
|
||||
/* --- Fetch Game Data --- */
|
||||
|
@ -155,6 +156,7 @@ rc_api_fetch_game_data_response_t;
|
|||
|
||||
int rc_api_init_fetch_game_data_request(rc_api_request_t* request, const rc_api_fetch_game_data_request_t* api_params);
|
||||
int rc_api_process_fetch_game_data_response(rc_api_fetch_game_data_response_t* response, const char* server_response);
|
||||
int rc_api_process_fetch_game_data_server_response(rc_api_fetch_game_data_response_t* response, const rc_api_server_response_t* server_response);
|
||||
void rc_api_destroy_fetch_game_data_response(rc_api_fetch_game_data_response_t* response);
|
||||
|
||||
/* --- Ping --- */
|
||||
|
@ -185,6 +187,7 @@ rc_api_ping_response_t;
|
|||
|
||||
int rc_api_init_ping_request(rc_api_request_t* request, const rc_api_ping_request_t* api_params);
|
||||
int rc_api_process_ping_response(rc_api_ping_response_t* response, const char* server_response);
|
||||
int rc_api_process_ping_server_response(rc_api_ping_response_t* response, const rc_api_server_response_t* server_response);
|
||||
void rc_api_destroy_ping_response(rc_api_ping_response_t* response);
|
||||
|
||||
/* --- Award Achievement --- */
|
||||
|
@ -214,6 +217,8 @@ typedef struct rc_api_award_achievement_response_t {
|
|||
unsigned awarded_achievement_id;
|
||||
/* The updated player score */
|
||||
unsigned new_player_score;
|
||||
/* The updated player softcore score */
|
||||
unsigned new_player_score_softcore;
|
||||
/* The number of achievements the user has not yet unlocked for this game
|
||||
* (in hardcore/non-hardcore per hardcore flag in request) */
|
||||
unsigned achievements_remaining;
|
||||
|
@ -225,6 +230,7 @@ rc_api_award_achievement_response_t;
|
|||
|
||||
int rc_api_init_award_achievement_request(rc_api_request_t* request, const rc_api_award_achievement_request_t* api_params);
|
||||
int rc_api_process_award_achievement_response(rc_api_award_achievement_response_t* response, const char* server_response);
|
||||
int rc_api_process_award_achievement_server_response(rc_api_award_achievement_response_t* response, const rc_api_server_response_t* server_response);
|
||||
void rc_api_destroy_award_achievement_response(rc_api_award_achievement_response_t* response);
|
||||
|
||||
/* --- Submit Leaderboard Entry --- */
|
||||
|
@ -282,6 +288,7 @@ rc_api_submit_lboard_entry_response_t;
|
|||
|
||||
int rc_api_init_submit_lboard_entry_request(rc_api_request_t* request, const rc_api_submit_lboard_entry_request_t* api_params);
|
||||
int rc_api_process_submit_lboard_entry_response(rc_api_submit_lboard_entry_response_t* response, const char* server_response);
|
||||
int rc_api_process_submit_lboard_entry_server_response(rc_api_submit_lboard_entry_response_t* response, const rc_api_server_response_t* server_response);
|
||||
void rc_api_destroy_submit_lboard_entry_response(rc_api_submit_lboard_entry_response_t* response);
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
|
|
@ -33,6 +33,8 @@ typedef struct rc_api_login_response_t {
|
|||
const char* api_token;
|
||||
/* The current score of the player */
|
||||
unsigned score;
|
||||
/* The current softcore score of the player */
|
||||
unsigned score_softcore;
|
||||
/* The number of unread messages waiting for the player on the web site */
|
||||
unsigned num_unread_messages;
|
||||
/* The preferred name to display for the player */
|
||||
|
@ -45,6 +47,7 @@ rc_api_login_response_t;
|
|||
|
||||
int rc_api_init_login_request(rc_api_request_t* request, const rc_api_login_request_t* api_params);
|
||||
int rc_api_process_login_response(rc_api_login_response_t* response, const char* server_response);
|
||||
int rc_api_process_login_server_response(rc_api_login_response_t* response, const rc_api_server_response_t* server_response);
|
||||
void rc_api_destroy_login_response(rc_api_login_response_t* response);
|
||||
|
||||
/* --- Start Session --- */
|
||||
|
@ -73,6 +76,7 @@ rc_api_start_session_response_t;
|
|||
|
||||
int rc_api_init_start_session_request(rc_api_request_t* request, const rc_api_start_session_request_t* api_params);
|
||||
int rc_api_process_start_session_response(rc_api_start_session_response_t* response, const char* server_response);
|
||||
int rc_api_process_start_session_server_response(rc_api_start_session_response_t* response, const rc_api_server_response_t* server_response);
|
||||
void rc_api_destroy_start_session_response(rc_api_start_session_response_t* response);
|
||||
|
||||
/* --- Fetch User Unlocks --- */
|
||||
|
@ -108,6 +112,7 @@ rc_api_fetch_user_unlocks_response_t;
|
|||
|
||||
int rc_api_init_fetch_user_unlocks_request(rc_api_request_t* request, const rc_api_fetch_user_unlocks_request_t* api_params);
|
||||
int rc_api_process_fetch_user_unlocks_response(rc_api_fetch_user_unlocks_response_t* response, const char* server_response);
|
||||
int rc_api_process_fetch_user_unlocks_server_response(rc_api_fetch_user_unlocks_response_t* response, const rc_api_server_response_t* server_response);
|
||||
void rc_api_destroy_fetch_user_unlocks_response(rc_api_fetch_user_unlocks_response_t* response);
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
|
|
@ -0,0 +1,584 @@
|
|||
#ifndef RC_CLIENT_H
|
||||
#define RC_CLIENT_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include "rc_api_request.h"
|
||||
#include "rc_error.h"
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <time.h>
|
||||
|
||||
/* implementation abstracted in rc_client_internal.h */
|
||||
typedef struct rc_client_t rc_client_t;
|
||||
typedef struct rc_client_async_handle_t rc_client_async_handle_t;
|
||||
|
||||
/*****************************************************************************\
|
||||
| Callbacks |
|
||||
\*****************************************************************************/
|
||||
|
||||
/**
|
||||
* Callback used to read num_bytes bytes from memory starting at address into buffer.
|
||||
* Returns the number of bytes read. A return value of 0 indicates the address was invalid.
|
||||
*/
|
||||
typedef uint32_t (*rc_client_read_memory_func_t)(uint32_t address, uint8_t* buffer, uint32_t num_bytes, rc_client_t* client);
|
||||
|
||||
/**
|
||||
* Internal method passed to rc_client_server_call_t to process the server response.
|
||||
*/
|
||||
typedef void (*rc_client_server_callback_t)(const rc_api_server_response_t* server_response, void* callback_data);
|
||||
|
||||
/**
|
||||
* Callback used to issue a request to the server.
|
||||
*/
|
||||
typedef void (*rc_client_server_call_t)(const rc_api_request_t* request, rc_client_server_callback_t callback, void* callback_data, rc_client_t* client);
|
||||
|
||||
/**
|
||||
* Generic callback for asynchronous eventing.
|
||||
*/
|
||||
typedef void (*rc_client_callback_t)(int result, const char* error_message, rc_client_t* client, void* userdata);
|
||||
|
||||
/**
|
||||
* Callback for logging or displaying a message.
|
||||
*/
|
||||
typedef void (*rc_client_message_callback_t)(const char* message, const rc_client_t* client);
|
||||
|
||||
/**
|
||||
* Marks an async process as aborted. The associated callback will not be called.
|
||||
*/
|
||||
void rc_client_abort_async(rc_client_t* client, rc_client_async_handle_t* async_handle);
|
||||
|
||||
/*****************************************************************************\
|
||||
| Runtime |
|
||||
\*****************************************************************************/
|
||||
|
||||
/**
|
||||
* Creates a new rc_client_t object.
|
||||
*/
|
||||
rc_client_t* rc_client_create(rc_client_read_memory_func_t read_memory_function, rc_client_server_call_t server_call_function);
|
||||
|
||||
/**
|
||||
* Releases resources associated to a rc_client_t object.
|
||||
* Pointer will no longer be valid after making this call.
|
||||
*/
|
||||
void rc_client_destroy(rc_client_t* client);
|
||||
|
||||
/**
|
||||
* Sets whether hardcore is enabled (on by default).
|
||||
* Can be called with a game loaded.
|
||||
* Enabling hardcore with a game loaded will raise an RC_CLIENT_EVENT_RESET
|
||||
* event. Processing will be disabled until rc_client_reset is called.
|
||||
*/
|
||||
void rc_client_set_hardcore_enabled(rc_client_t* client, int enabled);
|
||||
|
||||
/**
|
||||
* Gets whether hardcore is enabled (on by default).
|
||||
*/
|
||||
int rc_client_get_hardcore_enabled(const rc_client_t* client);
|
||||
|
||||
/**
|
||||
* Sets whether encore mode is enabled (off by default).
|
||||
* Evaluated when loading a game. Has no effect while a game is loaded.
|
||||
*/
|
||||
void rc_client_set_encore_mode_enabled(rc_client_t* client, int enabled);
|
||||
|
||||
/**
|
||||
* Gets whether encore mode is enabled (off by default).
|
||||
*/
|
||||
int rc_client_get_encore_mode_enabled(const rc_client_t* client);
|
||||
|
||||
/**
|
||||
* Sets whether unofficial achievements should be loaded.
|
||||
* Evaluated when loading a game. Has no effect while a game is loaded.
|
||||
*/
|
||||
void rc_client_set_unofficial_enabled(rc_client_t* client, int enabled);
|
||||
|
||||
/**
|
||||
* Gets whether unofficial achievements should be loaded.
|
||||
*/
|
||||
int rc_client_get_unofficial_enabled(const rc_client_t* client);
|
||||
|
||||
/**
|
||||
* Sets whether spectator mode is enabled (off by default).
|
||||
* If enabled, events for achievement unlocks and leaderboard submissions will be
|
||||
* raised, but server calls to actually perform the unlock/submit will not occur.
|
||||
* Can be modified while a game is loaded. Evaluated at unlock/submit time.
|
||||
* Cannot be modified if disabled before a game is loaded.
|
||||
*/
|
||||
void rc_client_set_spectator_mode_enabled(rc_client_t* client, int enabled);
|
||||
|
||||
/**
|
||||
* Gets whether spectator mode is enabled (off by default).
|
||||
*/
|
||||
int rc_client_get_spectator_mode_enabled(const rc_client_t* client);
|
||||
|
||||
/**
|
||||
* Attaches client-specific data to the runtime.
|
||||
*/
|
||||
void rc_client_set_userdata(rc_client_t* client, void* userdata);
|
||||
|
||||
/**
|
||||
* Gets the client-specific data attached to the runtime.
|
||||
*/
|
||||
void* rc_client_get_userdata(const rc_client_t* client);
|
||||
|
||||
/**
|
||||
* Sets the name of the server to use.
|
||||
*/
|
||||
void rc_client_set_host(const rc_client_t* client, const char* hostname);
|
||||
|
||||
/*****************************************************************************\
|
||||
| Logging |
|
||||
\*****************************************************************************/
|
||||
|
||||
/**
|
||||
* Sets the logging level and provides a callback to be called to do the logging.
|
||||
*/
|
||||
void rc_client_enable_logging(rc_client_t* client, int level, rc_client_message_callback_t callback);
|
||||
enum
|
||||
{
|
||||
RC_CLIENT_LOG_LEVEL_NONE = 0,
|
||||
RC_CLIENT_LOG_LEVEL_ERROR = 1,
|
||||
RC_CLIENT_LOG_LEVEL_WARN = 2,
|
||||
RC_CLIENT_LOG_LEVEL_INFO = 3,
|
||||
RC_CLIENT_LOG_LEVEL_VERBOSE = 4
|
||||
};
|
||||
|
||||
/*****************************************************************************\
|
||||
| User |
|
||||
\*****************************************************************************/
|
||||
|
||||
/**
|
||||
* Attempt to login a user.
|
||||
*/
|
||||
rc_client_async_handle_t* rc_client_begin_login_with_password(rc_client_t* client,
|
||||
const char* username, const char* password,
|
||||
rc_client_callback_t callback, void* callback_userdata);
|
||||
|
||||
/**
|
||||
* Attempt to login a user.
|
||||
*/
|
||||
rc_client_async_handle_t* rc_client_begin_login_with_token(rc_client_t* client,
|
||||
const char* username, const char* token,
|
||||
rc_client_callback_t callback, void* callback_userdata);
|
||||
|
||||
/**
|
||||
* Logout the user.
|
||||
*/
|
||||
void rc_client_logout(rc_client_t* client);
|
||||
|
||||
typedef struct rc_client_user_t {
|
||||
const char* display_name;
|
||||
const char* username;
|
||||
const char* token;
|
||||
uint32_t score;
|
||||
uint32_t score_softcore;
|
||||
uint32_t num_unread_messages;
|
||||
} rc_client_user_t;
|
||||
|
||||
/**
|
||||
* Gets information about the logged in user. Will return NULL if the user is not logged in.
|
||||
*/
|
||||
const rc_client_user_t* rc_client_get_user_info(const rc_client_t* client);
|
||||
|
||||
/**
|
||||
* Gets the URL for the user's profile picture.
|
||||
* Returns RC_OK on success.
|
||||
*/
|
||||
int rc_client_user_get_image_url(const rc_client_user_t* user, char buffer[], size_t buffer_size);
|
||||
|
||||
typedef struct rc_client_user_game_summary_t
|
||||
{
|
||||
uint32_t num_core_achievements;
|
||||
uint32_t num_unofficial_achievements;
|
||||
uint32_t num_unlocked_achievements;
|
||||
uint32_t num_unsupported_achievements;
|
||||
|
||||
uint32_t points_core;
|
||||
uint32_t points_unlocked;
|
||||
} rc_client_user_game_summary_t;
|
||||
|
||||
/**
|
||||
* Gets a breakdown of the number of achievements in the game, and how many the user has unlocked.
|
||||
* Used for the "You have unlocked X of Y achievements" message shown when the game starts.
|
||||
*/
|
||||
void rc_client_get_user_game_summary(const rc_client_t* client, rc_client_user_game_summary_t* summary);
|
||||
|
||||
/*****************************************************************************\
|
||||
| Game |
|
||||
\*****************************************************************************/
|
||||
|
||||
/**
|
||||
* Start loading an unidentified game.
|
||||
*/
|
||||
rc_client_async_handle_t* rc_client_begin_identify_and_load_game(rc_client_t* client,
|
||||
uint32_t console_id, const char* file_path,
|
||||
const uint8_t* data, size_t data_size,
|
||||
rc_client_callback_t callback, void* callback_userdata);
|
||||
|
||||
/**
|
||||
* Start loading a game.
|
||||
*/
|
||||
rc_client_async_handle_t* rc_client_begin_load_game(rc_client_t* client, const char* hash,
|
||||
rc_client_callback_t callback, void* callback_userdata);
|
||||
|
||||
/**
|
||||
* Unloads the current game.
|
||||
*/
|
||||
void rc_client_unload_game(rc_client_t* client);
|
||||
|
||||
typedef struct rc_client_game_t {
|
||||
uint32_t id;
|
||||
uint32_t console_id;
|
||||
const char* title;
|
||||
const char* hash;
|
||||
const char* badge_name;
|
||||
} rc_client_game_t;
|
||||
|
||||
/**
|
||||
* Get information about the current game. Returns NULL if no game is loaded.
|
||||
*/
|
||||
const rc_client_game_t* rc_client_get_game_info(const rc_client_t* client);
|
||||
|
||||
/**
|
||||
* Gets the URL for the game image.
|
||||
* Returns RC_OK on success.
|
||||
*/
|
||||
int rc_client_game_get_image_url(const rc_client_game_t* game, char buffer[], size_t buffer_size);
|
||||
|
||||
/**
|
||||
* Changes the active disc in a multi-disc game.
|
||||
*/
|
||||
rc_client_async_handle_t* rc_client_begin_change_media(rc_client_t* client, const char* file_path,
|
||||
const uint8_t* data, size_t data_size, rc_client_callback_t callback, void* callback_userdata);
|
||||
|
||||
/*****************************************************************************\
|
||||
| Subsets |
|
||||
\*****************************************************************************/
|
||||
|
||||
typedef struct rc_client_subset_t {
|
||||
uint32_t id;
|
||||
const char* title;
|
||||
char badge_name[16];
|
||||
|
||||
uint32_t num_achievements;
|
||||
uint32_t num_leaderboards;
|
||||
} rc_client_subset_t;
|
||||
|
||||
const rc_client_subset_t* rc_client_get_subset_info(rc_client_t* client, uint32_t subset_id);
|
||||
|
||||
/*****************************************************************************\
|
||||
| Achievements |
|
||||
\*****************************************************************************/
|
||||
|
||||
enum {
|
||||
RC_CLIENT_ACHIEVEMENT_STATE_INACTIVE = 0, /* unprocessed */
|
||||
RC_CLIENT_ACHIEVEMENT_STATE_ACTIVE = 1, /* eligible to trigger */
|
||||
RC_CLIENT_ACHIEVEMENT_STATE_UNLOCKED = 2, /* earned by user */
|
||||
RC_CLIENT_ACHIEVEMENT_STATE_DISABLED = 3 /* not supported by this version of the runtime */
|
||||
};
|
||||
|
||||
enum {
|
||||
RC_CLIENT_ACHIEVEMENT_CATEGORY_NONE = 0,
|
||||
RC_CLIENT_ACHIEVEMENT_CATEGORY_CORE = (1 << 0),
|
||||
RC_CLIENT_ACHIEVEMENT_CATEGORY_UNOFFICIAL = (1 << 1),
|
||||
RC_CLIENT_ACHIEVEMENT_CATEGORY_CORE_AND_UNOFFICIAL = RC_CLIENT_ACHIEVEMENT_CATEGORY_CORE | RC_CLIENT_ACHIEVEMENT_CATEGORY_UNOFFICIAL
|
||||
};
|
||||
|
||||
enum {
|
||||
RC_CLIENT_ACHIEVEMENT_BUCKET_UNKNOWN = 0,
|
||||
RC_CLIENT_ACHIEVEMENT_BUCKET_LOCKED = 1,
|
||||
RC_CLIENT_ACHIEVEMENT_BUCKET_UNLOCKED = 2,
|
||||
RC_CLIENT_ACHIEVEMENT_BUCKET_UNSUPPORTED = 3,
|
||||
RC_CLIENT_ACHIEVEMENT_BUCKET_UNOFFICIAL = 4,
|
||||
RC_CLIENT_ACHIEVEMENT_BUCKET_RECENTLY_UNLOCKED = 5,
|
||||
RC_CLIENT_ACHIEVEMENT_BUCKET_ACTIVE_CHALLENGE = 6,
|
||||
RC_CLIENT_ACHIEVEMENT_BUCKET_ALMOST_THERE = 7
|
||||
};
|
||||
|
||||
enum {
|
||||
RC_CLIENT_ACHIEVEMENT_UNLOCKED_NONE = 0,
|
||||
RC_CLIENT_ACHIEVEMENT_UNLOCKED_SOFTCORE = (1 << 0),
|
||||
RC_CLIENT_ACHIEVEMENT_UNLOCKED_HARDCORE = (1 << 1),
|
||||
RC_CLIENT_ACHIEVEMENT_UNLOCKED_BOTH = RC_CLIENT_ACHIEVEMENT_UNLOCKED_SOFTCORE | RC_CLIENT_ACHIEVEMENT_UNLOCKED_HARDCORE
|
||||
};
|
||||
|
||||
typedef struct rc_client_achievement_t {
|
||||
const char* title;
|
||||
const char* description;
|
||||
char badge_name[8];
|
||||
char measured_progress[24];
|
||||
float measured_percent;
|
||||
uint32_t id;
|
||||
uint32_t points;
|
||||
time_t unlock_time;
|
||||
uint8_t state;
|
||||
uint8_t category;
|
||||
uint8_t bucket;
|
||||
uint8_t unlocked;
|
||||
} rc_client_achievement_t;
|
||||
|
||||
/**
|
||||
* Get information about an achievement. Returns NULL if not found.
|
||||
*/
|
||||
const rc_client_achievement_t* rc_client_get_achievement_info(rc_client_t* client, uint32_t id);
|
||||
|
||||
/**
|
||||
* Gets the URL for the achievement image.
|
||||
* Returns RC_OK on success.
|
||||
*/
|
||||
int rc_client_achievement_get_image_url(const rc_client_achievement_t* achievement, int state, char buffer[], size_t buffer_size);
|
||||
|
||||
typedef struct rc_client_achievement_bucket_t {
|
||||
rc_client_achievement_t** achievements;
|
||||
uint32_t num_achievements;
|
||||
|
||||
const char* label;
|
||||
uint32_t subset_id;
|
||||
uint8_t bucket_type;
|
||||
} rc_client_achievement_bucket_t;
|
||||
|
||||
typedef struct rc_client_achievement_list_t {
|
||||
rc_client_achievement_bucket_t* buckets;
|
||||
uint32_t num_buckets;
|
||||
} rc_client_achievement_list_t;
|
||||
|
||||
enum {
|
||||
RC_CLIENT_ACHIEVEMENT_LIST_GROUPING_LOCK_STATE = 0,
|
||||
RC_CLIENT_ACHIEVEMENT_LIST_GROUPING_PROGRESS = 1
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a list of achievements matching the specified category and grouping.
|
||||
* Returns an allocated list that must be free'd by calling rc_client_destroy_achievement_list.
|
||||
*/
|
||||
rc_client_achievement_list_t* rc_client_create_achievement_list(rc_client_t* client, int category, int grouping);
|
||||
|
||||
/**
|
||||
* Destroys a list allocated by rc_client_get_achievement_list.
|
||||
*/
|
||||
void rc_client_destroy_achievement_list(rc_client_achievement_list_t* list);
|
||||
|
||||
/*****************************************************************************\
|
||||
| Leaderboards |
|
||||
\*****************************************************************************/
|
||||
|
||||
enum {
|
||||
RC_CLIENT_LEADERBOARD_STATE_INACTIVE = 0,
|
||||
RC_CLIENT_LEADERBOARD_STATE_ACTIVE = 1,
|
||||
RC_CLIENT_LEADERBOARD_STATE_TRACKING = 2,
|
||||
RC_CLIENT_LEADERBOARD_STATE_DISABLED = 3
|
||||
};
|
||||
|
||||
typedef struct rc_client_leaderboard_t {
|
||||
const char* title;
|
||||
const char* description;
|
||||
const char* tracker_value;
|
||||
uint32_t id;
|
||||
uint8_t state;
|
||||
uint8_t lower_is_better;
|
||||
} rc_client_leaderboard_t;
|
||||
|
||||
/**
|
||||
* Get information about a leaderboard. Returns NULL if not found.
|
||||
*/
|
||||
const rc_client_leaderboard_t* rc_client_get_leaderboard_info(const rc_client_t* client, uint32_t id);
|
||||
|
||||
typedef struct rc_client_leaderboard_tracker_t {
|
||||
char display[24];
|
||||
uint32_t id;
|
||||
} rc_client_leaderboard_tracker_t;
|
||||
|
||||
typedef struct rc_client_leaderboard_bucket_t {
|
||||
rc_client_leaderboard_t** leaderboards;
|
||||
uint32_t num_leaderboards;
|
||||
|
||||
const char* label;
|
||||
uint32_t subset_id;
|
||||
uint8_t bucket_type;
|
||||
} rc_client_leaderboard_bucket_t;
|
||||
|
||||
typedef struct rc_client_leaderboard_list_t {
|
||||
rc_client_leaderboard_bucket_t* buckets;
|
||||
uint32_t num_buckets;
|
||||
} rc_client_leaderboard_list_t;
|
||||
|
||||
enum {
|
||||
RC_CLIENT_LEADERBOARD_BUCKET_UNKNOWN = 0,
|
||||
RC_CLIENT_LEADERBOARD_BUCKET_INACTIVE = 1,
|
||||
RC_CLIENT_LEADERBOARD_BUCKET_ACTIVE = 2,
|
||||
RC_CLIENT_LEADERBOARD_BUCKET_UNSUPPORTED = 3,
|
||||
RC_CLIENT_LEADERBOARD_BUCKET_ALL = 4
|
||||
};
|
||||
|
||||
enum {
|
||||
RC_CLIENT_LEADERBOARD_LIST_GROUPING_NONE = 0,
|
||||
RC_CLIENT_LEADERBOARD_LIST_GROUPING_TRACKING = 1
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a list of leaderboards matching the specified grouping.
|
||||
* Returns an allocated list that must be free'd by calling rc_client_destroy_leaderboard_list.
|
||||
*/
|
||||
rc_client_leaderboard_list_t* rc_client_create_leaderboard_list(rc_client_t* client, int grouping);
|
||||
|
||||
/**
|
||||
* Destroys a list allocated by rc_client_get_leaderboard_list.
|
||||
*/
|
||||
void rc_client_destroy_leaderboard_list(rc_client_leaderboard_list_t* list);
|
||||
|
||||
typedef struct rc_client_leaderboard_entry_t {
|
||||
const char* user;
|
||||
char display[24];
|
||||
time_t submitted;
|
||||
uint32_t rank;
|
||||
uint32_t index;
|
||||
} rc_client_leaderboard_entry_t;
|
||||
|
||||
typedef struct rc_client_leaderboard_entry_list_t {
|
||||
rc_client_leaderboard_entry_t* entries;
|
||||
uint32_t num_entries;
|
||||
int32_t user_index;
|
||||
} rc_client_leaderboard_entry_list_t;
|
||||
|
||||
typedef void (*rc_client_fetch_leaderboard_entries_callback_t)(int result, const char* error_message,
|
||||
rc_client_leaderboard_entry_list_t* list, rc_client_t* client, void* callback_userdata);
|
||||
|
||||
/**
|
||||
* Fetches a list of leaderboard entries from the server.
|
||||
* Callback receives an allocated list that must be free'd by calling rc_client_destroy_leaderboard_entry_list.
|
||||
*/
|
||||
rc_client_async_handle_t* rc_client_begin_fetch_leaderboard_entries(rc_client_t* client, uint32_t leaderboard_id,
|
||||
uint32_t first_entry, uint32_t count, rc_client_fetch_leaderboard_entries_callback_t callback, void* callback_userdata);
|
||||
|
||||
/**
|
||||
* Fetches a list of leaderboard entries from the server containing the logged-in user.
|
||||
* Callback receives an allocated list that must be free'd by calling rc_client_destroy_leaderboard_entry_list.
|
||||
*/
|
||||
rc_client_async_handle_t* rc_client_begin_fetch_leaderboard_entries_around_user(rc_client_t* client, uint32_t leaderboard_id,
|
||||
uint32_t count, rc_client_fetch_leaderboard_entries_callback_t callback, void* callback_userdata);
|
||||
|
||||
/**
|
||||
* Gets the URL for the profile picture of the user associated to a leaderboard entry.
|
||||
* Returns RC_OK on success.
|
||||
*/
|
||||
int rc_client_leaderboard_entry_get_user_image_url(const rc_client_leaderboard_entry_t* entry, char buffer[], size_t buffer_size);
|
||||
|
||||
/**
|
||||
* Destroys a list allocated by rc_client_begin_fetch_leaderboard_entries or rc_client_begin_fetch_leaderboard_entries_around_user.
|
||||
*/
|
||||
void rc_client_destroy_leaderboard_entry_list(rc_client_leaderboard_entry_list_t* list);
|
||||
|
||||
/*****************************************************************************\
|
||||
| Rich Presence |
|
||||
\*****************************************************************************/
|
||||
|
||||
/**
|
||||
* Gets the current rich presence message.
|
||||
* Returns the number of characters written to buffer.
|
||||
*/
|
||||
size_t rc_client_get_rich_presence_message(rc_client_t* client, char buffer[], size_t buffer_size);
|
||||
|
||||
/*****************************************************************************\
|
||||
| Processing |
|
||||
\*****************************************************************************/
|
||||
|
||||
enum {
|
||||
RC_CLIENT_EVENT_TYPE_NONE = 0,
|
||||
RC_CLIENT_EVENT_ACHIEVEMENT_TRIGGERED = 1, /* [achievement] was earned by the player */
|
||||
RC_CLIENT_EVENT_LEADERBOARD_STARTED = 2, /* [leaderboard] attempt has started */
|
||||
RC_CLIENT_EVENT_LEADERBOARD_FAILED = 3, /* [leaderboard] attempt failed */
|
||||
RC_CLIENT_EVENT_LEADERBOARD_SUBMITTED = 4, /* [leaderboard] attempt submitted */
|
||||
RC_CLIENT_EVENT_ACHIEVEMENT_CHALLENGE_INDICATOR_SHOW = 5, /* [achievement] challenge indicator should be shown */
|
||||
RC_CLIENT_EVENT_ACHIEVEMENT_CHALLENGE_INDICATOR_HIDE = 6, /* [achievement] challenge indicator should be hidden */
|
||||
RC_CLIENT_EVENT_ACHIEVEMENT_PROGRESS_INDICATOR_SHOW = 7, /* progress indicator should be shown for [achievement] */
|
||||
RC_CLIENT_EVENT_ACHIEVEMENT_PROGRESS_INDICATOR_HIDE = 8, /* progress indicator should be hidden */
|
||||
RC_CLIENT_EVENT_ACHIEVEMENT_PROGRESS_INDICATOR_UPDATE = 9, /* progress indicator should be updated to reflect new badge/progress for [achievement] */
|
||||
RC_CLIENT_EVENT_LEADERBOARD_TRACKER_SHOW = 10, /* [leaderboard_tracker] should be shown */
|
||||
RC_CLIENT_EVENT_LEADERBOARD_TRACKER_HIDE = 11, /* [leaderboard_tracker] should be hidden */
|
||||
RC_CLIENT_EVENT_LEADERBOARD_TRACKER_UPDATE = 12, /* [leaderboard_tracker] updated */
|
||||
RC_CLIENT_EVENT_RESET = 13, /* emulated system should be reset (as the result of enabling hardcore) */
|
||||
RC_CLIENT_EVENT_GAME_COMPLETED = 14, /* all achievements for the game have been earned */
|
||||
RC_CLIENT_EVENT_SERVER_ERROR = 15 /* an API response returned a [server_error] and will not be retried */
|
||||
};
|
||||
|
||||
typedef struct rc_client_server_error_t
|
||||
{
|
||||
const char* error_message;
|
||||
const char* api;
|
||||
} rc_client_server_error_t;
|
||||
|
||||
typedef struct rc_client_event_t
|
||||
{
|
||||
uint32_t type;
|
||||
|
||||
rc_client_achievement_t* achievement;
|
||||
rc_client_leaderboard_t* leaderboard;
|
||||
rc_client_leaderboard_tracker_t* leaderboard_tracker;
|
||||
rc_client_server_error_t* server_error;
|
||||
|
||||
} rc_client_event_t;
|
||||
|
||||
/**
|
||||
* Callback used to notify the client when certain events occur.
|
||||
*/
|
||||
typedef void (*rc_client_event_handler_t)(const rc_client_event_t* event, rc_client_t* client);
|
||||
|
||||
/**
|
||||
* Provides a callback for event handling.
|
||||
*/
|
||||
void rc_client_set_event_handler(rc_client_t* client, rc_client_event_handler_t handler);
|
||||
|
||||
/**
|
||||
* Provides a callback for reading memory.
|
||||
*/
|
||||
void rc_client_set_read_memory_function(rc_client_t* client, rc_client_read_memory_func_t handler);
|
||||
|
||||
/**
|
||||
* Determines if there are any active achievements/leaderboards/rich presence that need processing.
|
||||
*/
|
||||
int rc_client_is_processing_required(rc_client_t* client);
|
||||
|
||||
/**
|
||||
* Processes achievements for the current frame.
|
||||
*/
|
||||
void rc_client_do_frame(rc_client_t* client);
|
||||
|
||||
/**
|
||||
* Processes the periodic queue.
|
||||
* Called internally by rc_client_do_frame.
|
||||
* Should be explicitly called if rc_client_do_frame is not being called because emulation is paused.
|
||||
*/
|
||||
void rc_client_idle(rc_client_t* client);
|
||||
|
||||
/**
|
||||
* Informs the runtime that the emulator has been reset. Will reset all achievements and leaderboards
|
||||
* to their initial state (includes hiding indicators/trackers).
|
||||
*/
|
||||
void rc_client_reset(rc_client_t* client);
|
||||
|
||||
/**
|
||||
* Gets the number of bytes needed to serialized the runtime state.
|
||||
*/
|
||||
size_t rc_client_progress_size(rc_client_t* client);
|
||||
|
||||
/**
|
||||
* Serializes the runtime state into a buffer.
|
||||
* Returns RC_OK on success, or an error indicator.
|
||||
*/
|
||||
int rc_client_serialize_progress(rc_client_t* client, uint8_t* buffer);
|
||||
|
||||
/**
|
||||
* Deserializes the runtime state from a buffer.
|
||||
* Returns RC_OK on success, or an error indicator.
|
||||
*/
|
||||
int rc_client_deserialize_progress(rc_client_t* client, const uint8_t* serialized);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* RC_RUNTIME_H */
|
|
@ -10,6 +10,7 @@ extern "C" {
|
|||
\*****************************************************************************/
|
||||
|
||||
enum {
|
||||
RC_CONSOLE_UNKNOWN = 0,
|
||||
RC_CONSOLE_MEGA_DRIVE = 1,
|
||||
RC_CONSOLE_NINTENDO_64 = 2,
|
||||
RC_CONSOLE_SUPER_NINTENDO = 3,
|
||||
|
@ -85,6 +86,11 @@ enum {
|
|||
RC_CONSOLE_ARCADIA_2001 = 73,
|
||||
RC_CONSOLE_INTERTON_VC_4000 = 74,
|
||||
RC_CONSOLE_ELEKTOR_TV_GAMES_COMPUTER = 75,
|
||||
RC_CONSOLE_PC_ENGINE_CD = 76,
|
||||
RC_CONSOLE_ATARI_JAGUAR_CD = 77,
|
||||
RC_CONSOLE_NINTENDO_DSI = 78,
|
||||
RC_CONSOLE_TI83 = 79,
|
||||
RC_CONSOLE_UZEBOX = 80,
|
||||
|
||||
RC_CONSOLE_HUBS = 100,
|
||||
RC_CONSOLE_EVENTS = 101
|
||||
|
|
|
@ -36,7 +36,13 @@ enum {
|
|||
RC_INVALID_MEASURED_TARGET = -23,
|
||||
RC_INVALID_COMPARISON = -24,
|
||||
RC_INVALID_STATE = -25,
|
||||
RC_INVALID_JSON = -26
|
||||
RC_INVALID_JSON = -26,
|
||||
RC_API_FAILURE = -27,
|
||||
RC_LOGIN_REQUIRED = -28,
|
||||
RC_NO_GAME_LOADED = -29,
|
||||
RC_HARDCORE_DISABLED = -30,
|
||||
RC_ABORTED = -31,
|
||||
RC_NO_RESPONSE = -32
|
||||
};
|
||||
|
||||
const char* rc_error_str(int ret);
|
||||
|
|
|
@ -27,20 +27,20 @@ extern "C" {
|
|||
|
||||
/* data for rc_hash_iterate
|
||||
*/
|
||||
struct rc_hash_iterator
|
||||
typedef struct rc_hash_iterator
|
||||
{
|
||||
uint8_t* buffer;
|
||||
const uint8_t* buffer;
|
||||
size_t buffer_size;
|
||||
uint8_t consoles[12];
|
||||
int index;
|
||||
const char* path;
|
||||
};
|
||||
} rc_hash_iterator_t;
|
||||
|
||||
/* initializes a rc_hash_iterator
|
||||
* - path must be provided
|
||||
* - if buffer and buffer_size are provided, path may be a filename (i.e. for something extracted from a zip file)
|
||||
*/
|
||||
void rc_hash_initialize_iterator(struct rc_hash_iterator* iterator, const char* path, uint8_t* buffer, size_t buffer_size);
|
||||
void rc_hash_initialize_iterator(struct rc_hash_iterator* iterator, const char* path, const uint8_t* buffer, size_t buffer_size);
|
||||
|
||||
/* releases resources associated to a rc_hash_iterator
|
||||
*/
|
||||
|
@ -92,11 +92,12 @@ extern "C" {
|
|||
|
||||
/* ===================================================== */
|
||||
|
||||
#define RC_HASH_CDTRACK_FIRST_DATA ((uint32_t)-1)
|
||||
#define RC_HASH_CDTRACK_LAST ((uint32_t)-2)
|
||||
#define RC_HASH_CDTRACK_LARGEST ((uint32_t)-3)
|
||||
#define RC_HASH_CDTRACK_FIRST_DATA ((uint32_t)-1) /* the first data track (skip audio tracks) */
|
||||
#define RC_HASH_CDTRACK_LAST ((uint32_t)-2) /* the last data/audio track */
|
||||
#define RC_HASH_CDTRACK_LARGEST ((uint32_t)-3) /* the largest data/audio track */
|
||||
#define RC_HASH_CDTRACK_FIRST_OF_SECOND_SESSION ((uint32_t)-4) /* the first data/audio track of the second session */
|
||||
|
||||
/* opens a track from the specified file. track 0 indicates the largest data track should be opened.
|
||||
/* opens a track from the specified file. see the RC_HASH_CDTRACK_ defines for special tracks.
|
||||
* returns a handle to be passed to the other functions, or NULL if the track could not be opened.
|
||||
*/
|
||||
typedef void* (*rc_hash_cdreader_open_track_handler)(const char* path, uint32_t track);
|
||||
|
|
|
@ -88,9 +88,12 @@ typedef struct rc_runtime_t {
|
|||
|
||||
rc_value_t* variables;
|
||||
rc_value_t** next_variable;
|
||||
|
||||
char owns_self;
|
||||
}
|
||||
rc_runtime_t;
|
||||
|
||||
rc_runtime_t* rc_runtime_alloc(void);
|
||||
void rc_runtime_init(rc_runtime_t* runtime);
|
||||
void rc_runtime_destroy(rc_runtime_t* runtime);
|
||||
|
||||
|
@ -121,7 +124,8 @@ enum {
|
|||
RC_RUNTIME_EVENT_LBOARD_TRIGGERED,
|
||||
RC_RUNTIME_EVENT_ACHIEVEMENT_DISABLED,
|
||||
RC_RUNTIME_EVENT_LBOARD_DISABLED,
|
||||
RC_RUNTIME_EVENT_ACHIEVEMENT_UNPRIMED
|
||||
RC_RUNTIME_EVENT_ACHIEVEMENT_UNPRIMED,
|
||||
RC_RUNTIME_EVENT_ACHIEVEMENT_PROGRESS_UPDATED
|
||||
};
|
||||
|
||||
typedef struct rc_runtime_event_t {
|
||||
|
|
|
@ -56,6 +56,7 @@ enum {
|
|||
RC_MEMSIZE_32_BITS_BE,
|
||||
RC_MEMSIZE_FLOAT,
|
||||
RC_MEMSIZE_MBF32,
|
||||
RC_MEMSIZE_MBF32_LE,
|
||||
RC_MEMSIZE_VARIABLE
|
||||
};
|
||||
|
||||
|
@ -169,7 +170,8 @@ enum {
|
|||
RC_OPERATOR_NONE,
|
||||
RC_OPERATOR_MULT,
|
||||
RC_OPERATOR_DIV,
|
||||
RC_OPERATOR_AND
|
||||
RC_OPERATOR_AND,
|
||||
RC_OPERATOR_XOR
|
||||
};
|
||||
|
||||
typedef struct rc_condition_t rc_condition_t;
|
||||
|
@ -198,6 +200,9 @@ struct rc_condition_t {
|
|||
|
||||
/* Whether or not the condition evaluated true on the last check */
|
||||
char is_true;
|
||||
|
||||
/* Unique identifier of optimized comparator to use */
|
||||
char optimized_comparator;
|
||||
};
|
||||
|
||||
/*****************************************************************************\
|
||||
|
|
|
@ -10,7 +10,9 @@
|
|||
#include <string.h>
|
||||
|
||||
#define RETROACHIEVEMENTS_HOST "https://retroachievements.org"
|
||||
#define RETROACHIEVEMENTS_IMAGE_HOST "http://i.retroachievements.org"
|
||||
#define RETROACHIEVEMENTS_IMAGE_HOST "https://media.retroachievements.org"
|
||||
#define RETROACHIEVEMENTS_HOST_NONSSL "http://retroachievements.org"
|
||||
#define RETROACHIEVEMENTS_IMAGE_HOST_NONSSL "http://media.retroachievements.org"
|
||||
static char* g_host = NULL;
|
||||
static char* g_imagehost = NULL;
|
||||
|
||||
|
@ -18,157 +20,174 @@ static char* g_imagehost = NULL;
|
|||
|
||||
/* --- rc_json --- */
|
||||
|
||||
static int rc_json_parse_object(const char** json_ptr, rc_json_field_t* fields, size_t field_count, unsigned* fields_seen);
|
||||
static int rc_json_parse_array(const char** json_ptr, rc_json_field_t* field);
|
||||
static int rc_json_parse_object(rc_json_iterator_t* iterator, rc_json_field_t* fields, size_t field_count, unsigned* fields_seen);
|
||||
static int rc_json_parse_array(rc_json_iterator_t* iterator, rc_json_field_t* field);
|
||||
|
||||
static int rc_json_parse_field(const char** json_ptr, rc_json_field_t* field) {
|
||||
static int rc_json_match_char(rc_json_iterator_t* iterator, char c)
|
||||
{
|
||||
if (iterator->json < iterator->end && *iterator->json == c) {
|
||||
++iterator->json;
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void rc_json_skip_whitespace(rc_json_iterator_t* iterator)
|
||||
{
|
||||
while (iterator->json < iterator->end && isspace((unsigned char)*iterator->json))
|
||||
++iterator->json;
|
||||
}
|
||||
|
||||
static int rc_json_find_closing_quote(rc_json_iterator_t* iterator)
|
||||
{
|
||||
while (iterator->json < iterator->end) {
|
||||
if (*iterator->json == '"')
|
||||
return 1;
|
||||
|
||||
if (*iterator->json == '\\') {
|
||||
++iterator->json;
|
||||
if (iterator->json == iterator->end)
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (*iterator->json == '\0')
|
||||
return 0;
|
||||
|
||||
++iterator->json;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rc_json_parse_field(rc_json_iterator_t* iterator, rc_json_field_t* field) {
|
||||
int result;
|
||||
|
||||
field->value_start = *json_ptr;
|
||||
if (iterator->json >= iterator->end)
|
||||
return RC_INVALID_JSON;
|
||||
|
||||
switch (**json_ptr)
|
||||
field->value_start = iterator->json;
|
||||
|
||||
switch (*iterator->json)
|
||||
{
|
||||
case '"': /* quoted string */
|
||||
++(*json_ptr);
|
||||
while (**json_ptr != '"') {
|
||||
if (**json_ptr == '\\')
|
||||
++(*json_ptr);
|
||||
|
||||
if (**json_ptr == '\0')
|
||||
return RC_INVALID_JSON;
|
||||
|
||||
++(*json_ptr);
|
||||
}
|
||||
++(*json_ptr);
|
||||
++iterator->json;
|
||||
if (!rc_json_find_closing_quote(iterator))
|
||||
return RC_INVALID_JSON;
|
||||
++iterator->json;
|
||||
break;
|
||||
|
||||
case '-':
|
||||
case '+': /* signed number */
|
||||
++(*json_ptr);
|
||||
++iterator->json;
|
||||
/* fallthrough to number */
|
||||
case '0': case '1': case '2': case '3': case '4':
|
||||
case '5': case '6': case '7': case '8': case '9': /* number */
|
||||
do {
|
||||
++(*json_ptr);
|
||||
} while (**json_ptr >= '0' && **json_ptr <= '9');
|
||||
if (**json_ptr == '.') {
|
||||
do {
|
||||
++(*json_ptr);
|
||||
} while (**json_ptr >= '0' && **json_ptr <= '9');
|
||||
while (iterator->json < iterator->end && *iterator->json >= '0' && *iterator->json <= '9')
|
||||
++iterator->json;
|
||||
|
||||
if (rc_json_match_char(iterator, '.')) {
|
||||
while (iterator->json < iterator->end && *iterator->json >= '0' && *iterator->json <= '9')
|
||||
++iterator->json;
|
||||
}
|
||||
break;
|
||||
|
||||
case '[': /* array */
|
||||
result = rc_json_parse_array(json_ptr, field);
|
||||
result = rc_json_parse_array(iterator, field);
|
||||
if (result != RC_OK)
|
||||
return result;
|
||||
return result;
|
||||
|
||||
break;
|
||||
|
||||
case '{': /* object */
|
||||
result = rc_json_parse_object(json_ptr, NULL, 0, &field->array_size);
|
||||
result = rc_json_parse_object(iterator, NULL, 0, &field->array_size);
|
||||
if (result != RC_OK)
|
||||
return result;
|
||||
|
||||
break;
|
||||
|
||||
default: /* non-quoted text [true,false,null] */
|
||||
if (!isalpha((unsigned char)**json_ptr))
|
||||
if (!isalpha((unsigned char)*iterator->json))
|
||||
return RC_INVALID_JSON;
|
||||
|
||||
do {
|
||||
++(*json_ptr);
|
||||
} while (isalnum((unsigned char)**json_ptr));
|
||||
while (iterator->json < iterator->end && isalnum((unsigned char)*iterator->json))
|
||||
++iterator->json;
|
||||
break;
|
||||
}
|
||||
|
||||
field->value_end = *json_ptr;
|
||||
field->value_end = iterator->json;
|
||||
return RC_OK;
|
||||
}
|
||||
|
||||
static int rc_json_parse_array(const char** json_ptr, rc_json_field_t* field) {
|
||||
static int rc_json_parse_array(rc_json_iterator_t* iterator, rc_json_field_t* field) {
|
||||
rc_json_field_t unused_field;
|
||||
const char* json = *json_ptr;
|
||||
int result;
|
||||
|
||||
if (*json != '[')
|
||||
if (!rc_json_match_char(iterator, '['))
|
||||
return RC_INVALID_JSON;
|
||||
++json;
|
||||
|
||||
field->array_size = 0;
|
||||
if (*json != ']') {
|
||||
do
|
||||
{
|
||||
while (isspace((unsigned char)*json))
|
||||
++json;
|
||||
|
||||
result = rc_json_parse_field(&json, &unused_field);
|
||||
if (result != RC_OK)
|
||||
return result;
|
||||
if (rc_json_match_char(iterator, ']')) /* empty array */
|
||||
return RC_OK;
|
||||
|
||||
++field->array_size;
|
||||
do
|
||||
{
|
||||
rc_json_skip_whitespace(iterator);
|
||||
|
||||
while (isspace((unsigned char)*json))
|
||||
++json;
|
||||
result = rc_json_parse_field(iterator, &unused_field);
|
||||
if (result != RC_OK)
|
||||
return result;
|
||||
|
||||
if (*json != ',')
|
||||
break;
|
||||
++field->array_size;
|
||||
|
||||
++json;
|
||||
} while (1);
|
||||
rc_json_skip_whitespace(iterator);
|
||||
} while (rc_json_match_char(iterator, ','));
|
||||
|
||||
if (*json != ']')
|
||||
return RC_INVALID_JSON;
|
||||
}
|
||||
if (!rc_json_match_char(iterator, ']'))
|
||||
return RC_INVALID_JSON;
|
||||
|
||||
*json_ptr = ++json;
|
||||
return RC_OK;
|
||||
}
|
||||
|
||||
static int rc_json_get_next_field(rc_json_object_field_iterator_t* iterator) {
|
||||
const char* json = iterator->json;
|
||||
static int rc_json_get_next_field(rc_json_iterator_t* iterator, rc_json_field_t* field) {
|
||||
rc_json_skip_whitespace(iterator);
|
||||
|
||||
while (isspace((unsigned char)*json))
|
||||
++json;
|
||||
|
||||
if (*json != '"')
|
||||
if (!rc_json_match_char(iterator, '"'))
|
||||
return RC_INVALID_JSON;
|
||||
|
||||
iterator->field.name = ++json;
|
||||
while (*json != '"') {
|
||||
if (!*json)
|
||||
field->name = iterator->json;
|
||||
while (iterator->json < iterator->end && *iterator->json != '"') {
|
||||
if (!*iterator->json)
|
||||
return RC_INVALID_JSON;
|
||||
++json;
|
||||
++iterator->json;
|
||||
}
|
||||
iterator->name_len = json - iterator->field.name;
|
||||
++json;
|
||||
|
||||
while (isspace((unsigned char)*json))
|
||||
++json;
|
||||
|
||||
if (*json != ':')
|
||||
if (iterator->json == iterator->end)
|
||||
return RC_INVALID_JSON;
|
||||
|
||||
++json;
|
||||
field->name_len = iterator->json - field->name;
|
||||
++iterator->json;
|
||||
|
||||
while (isspace((unsigned char)*json))
|
||||
++json;
|
||||
rc_json_skip_whitespace(iterator);
|
||||
|
||||
if (rc_json_parse_field(&json, &iterator->field) < 0)
|
||||
if (!rc_json_match_char(iterator, ':'))
|
||||
return RC_INVALID_JSON;
|
||||
|
||||
while (isspace((unsigned char)*json))
|
||||
++json;
|
||||
rc_json_skip_whitespace(iterator);
|
||||
|
||||
if (rc_json_parse_field(iterator, field) < 0)
|
||||
return RC_INVALID_JSON;
|
||||
|
||||
rc_json_skip_whitespace(iterator);
|
||||
|
||||
iterator->json = json;
|
||||
return RC_OK;
|
||||
}
|
||||
|
||||
static int rc_json_parse_object(const char** json_ptr, rc_json_field_t* fields, size_t field_count, unsigned* fields_seen) {
|
||||
rc_json_object_field_iterator_t iterator;
|
||||
const char* json = *json_ptr;
|
||||
static int rc_json_parse_object(rc_json_iterator_t* iterator, rc_json_field_t* fields, size_t field_count, unsigned* fields_seen) {
|
||||
size_t i;
|
||||
unsigned num_fields = 0;
|
||||
rc_json_field_t field;
|
||||
int result;
|
||||
|
||||
if (fields_seen)
|
||||
|
@ -177,60 +196,105 @@ static int rc_json_parse_object(const char** json_ptr, rc_json_field_t* fields,
|
|||
for (i = 0; i < field_count; ++i)
|
||||
fields[i].value_start = fields[i].value_end = NULL;
|
||||
|
||||
if (*json != '{')
|
||||
if (!rc_json_match_char(iterator, '{'))
|
||||
return RC_INVALID_JSON;
|
||||
++json;
|
||||
|
||||
if (*json == '}') {
|
||||
*json_ptr = ++json;
|
||||
if (rc_json_match_char(iterator, '}')) /* empty object */
|
||||
return RC_OK;
|
||||
}
|
||||
|
||||
memset(&iterator, 0, sizeof(iterator));
|
||||
iterator.json = json;
|
||||
|
||||
do
|
||||
{
|
||||
result = rc_json_get_next_field(&iterator);
|
||||
result = rc_json_get_next_field(iterator, &field);
|
||||
if (result != RC_OK)
|
||||
return result;
|
||||
|
||||
for (i = 0; i < field_count; ++i) {
|
||||
if (!fields[i].value_start && strncmp(fields[i].name, iterator.field.name, iterator.name_len) == 0 &&
|
||||
fields[i].name[iterator.name_len] == '\0') {
|
||||
fields[i].value_start = iterator.field.value_start;
|
||||
fields[i].value_end = iterator.field.value_end;
|
||||
fields[i].array_size = iterator.field.array_size;
|
||||
if (!fields[i].value_start && fields[i].name_len == field.name_len &&
|
||||
memcmp(fields[i].name, field.name, field.name_len) == 0) {
|
||||
fields[i].value_start = field.value_start;
|
||||
fields[i].value_end = field.value_end;
|
||||
fields[i].array_size = field.array_size;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
++num_fields;
|
||||
if (*iterator.json != ',')
|
||||
break;
|
||||
|
||||
++iterator.json;
|
||||
} while (1);
|
||||
} while (rc_json_match_char(iterator, ','));
|
||||
|
||||
if (*iterator.json != '}')
|
||||
if (!rc_json_match_char(iterator, '}'))
|
||||
return RC_INVALID_JSON;
|
||||
|
||||
if (fields_seen)
|
||||
*fields_seen = num_fields;
|
||||
|
||||
*json_ptr = ++iterator.json;
|
||||
return RC_OK;
|
||||
}
|
||||
|
||||
int rc_json_get_next_object_field(rc_json_object_field_iterator_t* iterator) {
|
||||
if (*iterator->json != ',' && *iterator->json != '{')
|
||||
int rc_json_get_next_object_field(rc_json_iterator_t* iterator, rc_json_field_t* field) {
|
||||
if (!rc_json_match_char(iterator, ',') && !rc_json_match_char(iterator, '{'))
|
||||
return 0;
|
||||
|
||||
++iterator->json;
|
||||
return (rc_json_get_next_field(iterator) == RC_OK);
|
||||
return (rc_json_get_next_field(iterator, field) == RC_OK);
|
||||
}
|
||||
|
||||
int rc_json_parse_response(rc_api_response_t* response, const char* json, rc_json_field_t* fields, size_t field_count) {
|
||||
int rc_json_get_object_string_length(const char* json) {
|
||||
const char* json_start = json;
|
||||
|
||||
rc_json_iterator_t iterator;
|
||||
memset(&iterator, 0, sizeof(iterator));
|
||||
iterator.json = json;
|
||||
iterator.end = json + (1024 * 1024 * 1024); /* arbitrary 1GB limit on JSON response */
|
||||
|
||||
rc_json_parse_object(&iterator, NULL, 0, NULL);
|
||||
|
||||
return (int)(iterator.json - json_start);
|
||||
}
|
||||
|
||||
static int rc_json_extract_html_error(rc_api_response_t* response, const rc_api_server_response_t* server_response) {
|
||||
const char* json = server_response->body;
|
||||
const char* end = json;
|
||||
|
||||
const char* title_start = strstr(json, "<title>");
|
||||
if (title_start) {
|
||||
title_start += 7;
|
||||
if (isdigit((int)*title_start)) {
|
||||
const char* title_end = strstr(title_start + 7, "</title>");
|
||||
if (title_end) {
|
||||
char* dst = rc_buf_reserve(&response->buffer, (title_end - title_start) + 1);
|
||||
response->error_message = dst;
|
||||
memcpy(dst, title_start, title_end - title_start);
|
||||
dst += (title_end - title_start);
|
||||
*dst++ = '\0';
|
||||
rc_buf_consume(&response->buffer, response->error_message, dst);
|
||||
response->succeeded = 0;
|
||||
return RC_INVALID_JSON;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
while (*end && *end != '\n' && end - json < 200)
|
||||
++end;
|
||||
|
||||
if (end > json && end[-1] == '\r')
|
||||
--end;
|
||||
|
||||
if (end > json) {
|
||||
char* dst = rc_buf_reserve(&response->buffer, (end - json) + 1);
|
||||
response->error_message = dst;
|
||||
memcpy(dst, json, end - json);
|
||||
dst += (end - json);
|
||||
*dst++ = '\0';
|
||||
rc_buf_consume(&response->buffer, response->error_message, dst);
|
||||
}
|
||||
|
||||
response->succeeded = 0;
|
||||
return RC_INVALID_JSON;
|
||||
}
|
||||
|
||||
int rc_json_parse_server_response(rc_api_response_t* response, const rc_api_server_response_t* server_response, rc_json_field_t* fields, size_t field_count) {
|
||||
int result;
|
||||
|
||||
#ifndef NDEBUG
|
||||
if (field_count < 2)
|
||||
return RC_INVALID_STATE;
|
||||
|
@ -240,37 +304,28 @@ int rc_json_parse_response(rc_api_response_t* response, const char* json, rc_jso
|
|||
return RC_INVALID_STATE;
|
||||
#endif
|
||||
|
||||
if (*json == '{') {
|
||||
int result = rc_json_parse_object(&json, fields, field_count, NULL);
|
||||
response->error_message = NULL;
|
||||
|
||||
if (!server_response || !server_response->body || !*server_response->body) {
|
||||
response->succeeded = 0;
|
||||
return RC_NO_RESPONSE;
|
||||
}
|
||||
|
||||
if (*server_response->body != '{') {
|
||||
result = rc_json_extract_html_error(response, server_response);
|
||||
}
|
||||
else {
|
||||
rc_json_iterator_t iterator;
|
||||
memset(&iterator, 0, sizeof(iterator));
|
||||
iterator.json = server_response->body;
|
||||
iterator.end = server_response->body + server_response->body_length;
|
||||
result = rc_json_parse_object(&iterator, fields, field_count, NULL);
|
||||
|
||||
rc_json_get_optional_string(&response->error_message, response, &fields[1], "Error", NULL);
|
||||
rc_json_get_optional_bool(&response->succeeded, &fields[0], "Success", 1);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
response->error_message = NULL;
|
||||
|
||||
if (*json) {
|
||||
const char* end = json;
|
||||
while (*end && *end != '\n' && end - json < 200)
|
||||
++end;
|
||||
|
||||
if (end > json && end[-1] == '\r')
|
||||
--end;
|
||||
|
||||
if (end > json) {
|
||||
char* dst = rc_buf_reserve(&response->buffer, (end - json) + 1);
|
||||
response->error_message = dst;
|
||||
memcpy(dst, json, end - json);
|
||||
dst += (end - json);
|
||||
*dst++ = '\0';
|
||||
rc_buf_consume(&response->buffer, response->error_message, dst);
|
||||
}
|
||||
}
|
||||
|
||||
response->succeeded = 0;
|
||||
return RC_INVALID_JSON;
|
||||
return result;
|
||||
}
|
||||
|
||||
static int rc_json_missing_field(rc_api_response_t* response, const rc_json_field_t* field) {
|
||||
|
@ -293,42 +348,48 @@ static int rc_json_missing_field(rc_api_response_t* response, const rc_json_fiel
|
|||
}
|
||||
|
||||
int rc_json_get_required_object(rc_json_field_t* fields, size_t field_count, rc_api_response_t* response, rc_json_field_t* field, const char* field_name) {
|
||||
const char* json = field->value_start;
|
||||
rc_json_iterator_t iterator;
|
||||
|
||||
#ifndef NDEBUG
|
||||
if (strcmp(field->name, field_name) != 0)
|
||||
return 0;
|
||||
#else
|
||||
(void)field_name;
|
||||
#endif
|
||||
|
||||
if (!json)
|
||||
if (!field->value_start)
|
||||
return rc_json_missing_field(response, field);
|
||||
|
||||
return (rc_json_parse_object(&json, fields, field_count, &field->array_size) == RC_OK);
|
||||
memset(&iterator, 0, sizeof(iterator));
|
||||
iterator.json = field->value_start;
|
||||
iterator.end = field->value_end;
|
||||
return (rc_json_parse_object(&iterator, fields, field_count, &field->array_size) == RC_OK);
|
||||
}
|
||||
|
||||
static int rc_json_get_array_entry_value(rc_json_field_t* field, rc_json_field_t* iterator) {
|
||||
if (!iterator->array_size)
|
||||
static int rc_json_get_array_entry_value(rc_json_field_t* field, rc_json_iterator_t* iterator) {
|
||||
rc_json_skip_whitespace(iterator);
|
||||
|
||||
if (iterator->json >= iterator->end)
|
||||
return 0;
|
||||
|
||||
while (isspace((unsigned char)*iterator->value_start))
|
||||
++iterator->value_start;
|
||||
if (rc_json_parse_field(iterator, field) != RC_OK)
|
||||
return 0;
|
||||
|
||||
rc_json_parse_field(&iterator->value_start, field);
|
||||
rc_json_skip_whitespace(iterator);
|
||||
|
||||
while (isspace((unsigned char)*iterator->value_start))
|
||||
++iterator->value_start;
|
||||
if (!rc_json_match_char(iterator, ','))
|
||||
rc_json_match_char(iterator, ']');
|
||||
|
||||
++iterator->value_start; /* skip , or ] */
|
||||
|
||||
--iterator->array_size;
|
||||
return 1;
|
||||
}
|
||||
|
||||
int rc_json_get_required_unum_array(unsigned** entries, unsigned* num_entries, rc_api_response_t* response, const rc_json_field_t* field, const char* field_name) {
|
||||
rc_json_field_t iterator;
|
||||
rc_json_iterator_t iterator;
|
||||
rc_json_field_t array;
|
||||
rc_json_field_t value;
|
||||
unsigned* entry;
|
||||
|
||||
if (!rc_json_get_required_array(num_entries, &iterator, response, field, field_name))
|
||||
if (!rc_json_get_required_array(num_entries, &array, response, field, field_name))
|
||||
return RC_MISSING_VALUE;
|
||||
|
||||
if (*num_entries) {
|
||||
|
@ -338,6 +399,10 @@ int rc_json_get_required_unum_array(unsigned** entries, unsigned* num_entries, r
|
|||
|
||||
value.name = field_name;
|
||||
|
||||
memset(&iterator, 0, sizeof(iterator));
|
||||
iterator.json = array.value_start;
|
||||
iterator.end = array.value_end;
|
||||
|
||||
entry = *entries;
|
||||
while (rc_json_get_array_entry_value(&value, &iterator)) {
|
||||
if (!rc_json_get_unum(entry, &value, field_name))
|
||||
|
@ -353,10 +418,12 @@ int rc_json_get_required_unum_array(unsigned** entries, unsigned* num_entries, r
|
|||
return RC_OK;
|
||||
}
|
||||
|
||||
int rc_json_get_required_array(unsigned* num_entries, rc_json_field_t* iterator, rc_api_response_t* response, const rc_json_field_t* field, const char* field_name) {
|
||||
int rc_json_get_required_array(unsigned* num_entries, rc_json_field_t* array_field, rc_api_response_t* response, const rc_json_field_t* field, const char* field_name) {
|
||||
#ifndef NDEBUG
|
||||
if (strcmp(field->name, field_name) != 0)
|
||||
return 0;
|
||||
#else
|
||||
(void)field_name;
|
||||
#endif
|
||||
|
||||
if (!field->value_start || *field->value_start != '[') {
|
||||
|
@ -364,28 +431,27 @@ int rc_json_get_required_array(unsigned* num_entries, rc_json_field_t* iterator,
|
|||
return rc_json_missing_field(response, field);
|
||||
}
|
||||
|
||||
memcpy(iterator, field, sizeof(*iterator));
|
||||
++iterator->value_start; /* skip [ */
|
||||
memcpy(array_field, field, sizeof(*array_field));
|
||||
++array_field->value_start; /* skip [ */
|
||||
|
||||
*num_entries = field->array_size;
|
||||
return 1;
|
||||
}
|
||||
|
||||
int rc_json_get_array_entry_object(rc_json_field_t* fields, size_t field_count, rc_json_field_t* iterator) {
|
||||
if (!iterator->array_size)
|
||||
int rc_json_get_array_entry_object(rc_json_field_t* fields, size_t field_count, rc_json_iterator_t* iterator) {
|
||||
rc_json_skip_whitespace(iterator);
|
||||
|
||||
if (iterator->json >= iterator->end)
|
||||
return 0;
|
||||
|
||||
while (isspace((unsigned char)*iterator->value_start))
|
||||
++iterator->value_start;
|
||||
if (rc_json_parse_object(iterator, fields, field_count, NULL) != RC_OK)
|
||||
return 0;
|
||||
|
||||
rc_json_parse_object(&iterator->value_start, fields, field_count, NULL);
|
||||
rc_json_skip_whitespace(iterator);
|
||||
|
||||
while (isspace((unsigned char)*iterator->value_start))
|
||||
++iterator->value_start;
|
||||
if (!rc_json_match_char(iterator, ','))
|
||||
rc_json_match_char(iterator, ']');
|
||||
|
||||
++iterator->value_start; /* skip , or ] */
|
||||
|
||||
--iterator->array_size;
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
@ -451,6 +517,8 @@ int rc_json_get_string(const char** out, rc_api_buffer_t* buffer, const rc_json_
|
|||
#ifndef NDEBUG
|
||||
if (strcmp(field->name, field_name) != 0)
|
||||
return 0;
|
||||
#else
|
||||
(void)field_name;
|
||||
#endif
|
||||
|
||||
if (!src) {
|
||||
|
@ -562,6 +630,8 @@ int rc_json_get_num(int* out, const rc_json_field_t* field, const char* field_na
|
|||
#ifndef NDEBUG
|
||||
if (strcmp(field->name, field_name) != 0)
|
||||
return 0;
|
||||
#else
|
||||
(void)field_name;
|
||||
#endif
|
||||
|
||||
if (!src) {
|
||||
|
@ -613,6 +683,8 @@ int rc_json_get_unum(unsigned* out, const rc_json_field_t* field, const char* fi
|
|||
#ifndef NDEBUG
|
||||
if (strcmp(field->name, field_name) != 0)
|
||||
return 0;
|
||||
#else
|
||||
(void)field_name;
|
||||
#endif
|
||||
|
||||
if (!src) {
|
||||
|
@ -654,11 +726,13 @@ int rc_json_get_datetime(time_t* out, const rc_json_field_t* field, const char*
|
|||
#ifndef NDEBUG
|
||||
if (strcmp(field->name, field_name) != 0)
|
||||
return 0;
|
||||
#else
|
||||
(void)field_name;
|
||||
#endif
|
||||
|
||||
if (*field->value_start == '\"') {
|
||||
memset(&tm, 0, sizeof(tm));
|
||||
if (sscanf(field->value_start + 1, "%d-%d-%d %d:%d:%d",
|
||||
if (sscanf_s(field->value_start + 1, "%d-%d-%d %d:%d:%d",
|
||||
&tm.tm_year, &tm.tm_mon, &tm.tm_mday, &tm.tm_hour, &tm.tm_min, &tm.tm_sec) == 6) {
|
||||
tm.tm_mon--; /* 0-based */
|
||||
tm.tm_year -= 1900; /* 1900 based */
|
||||
|
@ -669,9 +743,11 @@ int rc_json_get_datetime(time_t* out, const rc_json_field_t* field, const char*
|
|||
* timezone conversion twice and manually removing the difference */
|
||||
{
|
||||
time_t local_timet = mktime(&tm);
|
||||
struct tm* gmt_tm = gmtime(&local_timet);
|
||||
time_t skewed_timet = mktime(gmt_tm); /* applies local time adjustment second time */
|
||||
time_t tz_offset = skewed_timet - local_timet;
|
||||
time_t skewed_timet, tz_offset;
|
||||
struct tm gmt_tm;
|
||||
gmtime_s(&gmt_tm, &local_timet);
|
||||
skewed_timet = mktime(&gmt_tm); /* applies local time adjustment second time */
|
||||
tz_offset = skewed_timet - local_timet;
|
||||
*out = local_timet - tz_offset;
|
||||
}
|
||||
|
||||
|
@ -696,6 +772,8 @@ int rc_json_get_bool(int* out, const rc_json_field_t* field, const char* field_n
|
|||
#ifndef NDEBUG
|
||||
if (strcmp(field->name, field_name) != 0)
|
||||
return 0;
|
||||
#else
|
||||
(void)field_name;
|
||||
#endif
|
||||
|
||||
if (src) {
|
||||
|
@ -731,31 +809,32 @@ int rc_json_get_required_bool(int* out, rc_api_response_t* response, const rc_js
|
|||
/* --- rc_buf --- */
|
||||
|
||||
void rc_buf_init(rc_api_buffer_t* buffer) {
|
||||
buffer->write = &buffer->data[0];
|
||||
buffer->end = &buffer->data[sizeof(buffer->data)];
|
||||
buffer->next = NULL;
|
||||
buffer->chunk.write = buffer->chunk.start = &buffer->data[0];
|
||||
buffer->chunk.end = &buffer->data[sizeof(buffer->data)];
|
||||
buffer->chunk.next = NULL;
|
||||
}
|
||||
|
||||
void rc_buf_destroy(rc_api_buffer_t* buffer) {
|
||||
rc_api_buffer_chunk_t *chunk;
|
||||
#ifdef DEBUG_BUFFERS
|
||||
int count = 0;
|
||||
int wasted = 0;
|
||||
int total = 0;
|
||||
#endif
|
||||
|
||||
/* first buffer is not allocated */
|
||||
buffer = buffer->next;
|
||||
/* first chunk is not allocated. skip it. */
|
||||
chunk = buffer->chunk.next;
|
||||
|
||||
/* deallocate any additional buffers */
|
||||
while (buffer) {
|
||||
rc_api_buffer_t* next = buffer->next;
|
||||
while (chunk) {
|
||||
rc_api_buffer_chunk_t* next = chunk->next;
|
||||
#ifdef DEBUG_BUFFERS
|
||||
total += (int)(buffer->end - buffer->data);
|
||||
wasted += (int)(buffer->end - buffer->write);
|
||||
total += (int)(chunk->end - chunk->data);
|
||||
wasted += (int)(chunk->end - chunk->write);
|
||||
++count;
|
||||
#endif
|
||||
free(buffer);
|
||||
buffer = next;
|
||||
free(chunk);
|
||||
chunk = next;
|
||||
}
|
||||
|
||||
#ifdef DEBUG_BUFFERS
|
||||
|
@ -765,47 +844,50 @@ void rc_buf_destroy(rc_api_buffer_t* buffer) {
|
|||
}
|
||||
|
||||
char* rc_buf_reserve(rc_api_buffer_t* buffer, size_t amount) {
|
||||
rc_api_buffer_chunk_t* chunk = &buffer->chunk;
|
||||
size_t remaining;
|
||||
while (buffer) {
|
||||
remaining = buffer->end - buffer->write;
|
||||
while (chunk) {
|
||||
remaining = chunk->end - chunk->write;
|
||||
if (remaining >= amount)
|
||||
return buffer->write;
|
||||
return chunk->write;
|
||||
|
||||
if (!buffer->next) {
|
||||
/* allocate a chunk of memory that is a multiple of 256-bytes. casting it to an rc_api_buffer_t will
|
||||
* effectively unbound the data field, so use write and end pointers to track how data is being used.
|
||||
if (!chunk->next) {
|
||||
/* allocate a chunk of memory that is a multiple of 256-bytes. the first 32 bytes will be associated
|
||||
* to the chunk header, and the remaining will be used for data.
|
||||
*/
|
||||
const size_t buffer_prefix_size = sizeof(rc_api_buffer_t) - sizeof(buffer->data);
|
||||
const size_t alloc_size = (amount + buffer_prefix_size + 0xFF) & ~0xFF;
|
||||
buffer->next = (rc_api_buffer_t*)malloc(alloc_size);
|
||||
if (!buffer->next)
|
||||
const size_t chunk_header_size = sizeof(rc_api_buffer_chunk_t);
|
||||
const size_t alloc_size = (chunk_header_size + amount + 0xFF) & ~0xFF;
|
||||
chunk->next = (rc_api_buffer_chunk_t*)malloc(alloc_size);
|
||||
if (!chunk->next)
|
||||
break;
|
||||
|
||||
buffer->next->write = buffer->next->data;
|
||||
buffer->next->end = buffer->next->write + (alloc_size - buffer_prefix_size);
|
||||
buffer->next->next = NULL;
|
||||
chunk->next->start = (char*)chunk->next + chunk_header_size;
|
||||
chunk->next->write = chunk->next->start;
|
||||
chunk->next->end = (char*)chunk->next + alloc_size;
|
||||
chunk->next->next = NULL;
|
||||
}
|
||||
|
||||
buffer = buffer->next;
|
||||
chunk = chunk->next;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void rc_buf_consume(rc_api_buffer_t* buffer, const char* start, char* end) {
|
||||
rc_api_buffer_chunk_t* chunk = &buffer->chunk;
|
||||
do {
|
||||
if (buffer->write == start) {
|
||||
size_t offset = (end - buffer->data);
|
||||
if (chunk->write == start) {
|
||||
size_t offset = (end - chunk->start);
|
||||
offset = (offset + 7) & ~7;
|
||||
buffer->write = &buffer->data[offset];
|
||||
chunk->write = &chunk->start[offset];
|
||||
|
||||
if (buffer->write > buffer->end)
|
||||
buffer->write = buffer->end;
|
||||
if (chunk->write > chunk->end)
|
||||
chunk->write = chunk->end;
|
||||
break;
|
||||
}
|
||||
|
||||
buffer = buffer->next;
|
||||
} while (buffer);
|
||||
chunk = chunk->next;
|
||||
} while (chunk);
|
||||
}
|
||||
|
||||
void* rc_buf_alloc(rc_api_buffer_t* buffer, size_t amount) {
|
||||
|
@ -814,6 +896,18 @@ void* rc_buf_alloc(rc_api_buffer_t* buffer, size_t amount) {
|
|||
return (void*)ptr;
|
||||
}
|
||||
|
||||
char* rc_buf_strncpy(rc_api_buffer_t* buffer, const char* src, size_t len) {
|
||||
char* dst = rc_buf_reserve(buffer, len + 1);
|
||||
memcpy(dst, src, len);
|
||||
dst[len] = '\0';
|
||||
rc_buf_consume(buffer, dst, dst + len + 2);
|
||||
return dst;
|
||||
}
|
||||
|
||||
char* rc_buf_strcpy(rc_api_buffer_t* buffer, const char* src) {
|
||||
return rc_buf_strncpy(buffer, src, strlen(src));
|
||||
}
|
||||
|
||||
void rc_api_destroy_request(rc_api_request_t* request) {
|
||||
rc_buf_destroy(&request->buffer);
|
||||
}
|
||||
|
@ -828,13 +922,13 @@ void rc_api_format_md5(char checksum[33], const unsigned char digest[16]) {
|
|||
/* --- rc_url_builder --- */
|
||||
|
||||
void rc_url_builder_init(rc_api_url_builder_t* builder, rc_api_buffer_t* buffer, size_t estimated_size) {
|
||||
rc_api_buffer_t* used_buffer;
|
||||
rc_api_buffer_chunk_t* used_buffer;
|
||||
|
||||
memset(builder, 0, sizeof(*builder));
|
||||
builder->buffer = buffer;
|
||||
builder->write = builder->start = rc_buf_reserve(buffer, estimated_size);
|
||||
|
||||
used_buffer = buffer;
|
||||
used_buffer = &buffer->chunk;
|
||||
while (used_buffer && used_buffer->write != builder->write)
|
||||
used_buffer = used_buffer->next;
|
||||
|
||||
|
@ -857,7 +951,7 @@ static int rc_url_builder_reserve(rc_api_url_builder_t* builder, size_t amount)
|
|||
if (remaining < amount) {
|
||||
const size_t used = builder->write - builder->start;
|
||||
const size_t current_size = builder->end - builder->start;
|
||||
const size_t buffer_prefix_size = sizeof(rc_api_buffer_t) - sizeof(builder->buffer->data);
|
||||
const size_t buffer_prefix_size = sizeof(rc_api_buffer_chunk_t);
|
||||
char* new_start;
|
||||
size_t new_size = (current_size < 256) ? 256 : current_size * 2;
|
||||
do {
|
||||
|
@ -1054,6 +1148,16 @@ static void rc_api_update_host(char** host, const char* hostname) {
|
|||
|
||||
void rc_api_set_host(const char* hostname) {
|
||||
rc_api_update_host(&g_host, hostname);
|
||||
|
||||
if (!hostname) {
|
||||
/* also clear out the image hostname */
|
||||
rc_api_set_image_host(NULL);
|
||||
}
|
||||
else if (strcmp(hostname, RETROACHIEVEMENTS_HOST_NONSSL) == 0) {
|
||||
/* if just pointing at the non-HTTPS host, explicitly use the default image host
|
||||
* so it doesn't try to use the web host directly */
|
||||
rc_api_set_image_host(RETROACHIEVEMENTS_IMAGE_HOST_NONSSL);
|
||||
}
|
||||
}
|
||||
|
||||
void rc_api_set_image_host(const char* hostname) {
|
||||
|
|
|
@ -10,10 +10,13 @@
|
|||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define RC_CONTENT_TYPE_URLENCODED "application/x-www-form-urlencoded"
|
||||
|
||||
typedef struct rc_api_url_builder_t {
|
||||
char* write;
|
||||
char* start;
|
||||
char* end;
|
||||
/* pointer to a preallocated rc_api_buffer_t */
|
||||
rc_api_buffer_t* buffer;
|
||||
int result;
|
||||
}
|
||||
|
@ -23,22 +26,24 @@ void rc_url_builder_init(rc_api_url_builder_t* builder, rc_api_buffer_t* buffer,
|
|||
void rc_url_builder_append(rc_api_url_builder_t* builder, const char* data, size_t len);
|
||||
const char* rc_url_builder_finalize(rc_api_url_builder_t* builder);
|
||||
|
||||
#define RC_JSON_NEW_FIELD(n) {NULL,NULL,n,sizeof(n)-1,0}
|
||||
|
||||
typedef struct rc_json_field_t {
|
||||
const char* name;
|
||||
const char* value_start;
|
||||
const char* value_end;
|
||||
const char* name;
|
||||
size_t name_len;
|
||||
unsigned array_size;
|
||||
}
|
||||
rc_json_field_t;
|
||||
|
||||
typedef struct rc_json_object_field_iterator_t {
|
||||
rc_json_field_t field;
|
||||
typedef struct rc_json_iterator_t {
|
||||
const char* json;
|
||||
size_t name_len;
|
||||
const char* end;
|
||||
}
|
||||
rc_json_object_field_iterator_t;
|
||||
rc_json_iterator_t;
|
||||
|
||||
int rc_json_parse_response(rc_api_response_t* response, const char* json, rc_json_field_t* fields, size_t field_count);
|
||||
int rc_json_parse_server_response(rc_api_response_t* response, const rc_api_server_response_t* server_response, rc_json_field_t* fields, size_t field_count);
|
||||
int rc_json_get_string(const char** out, rc_api_buffer_t* buffer, const rc_json_field_t* field, const char* field_name);
|
||||
int rc_json_get_num(int* out, const rc_json_field_t* field, const char* field_name);
|
||||
int rc_json_get_unum(unsigned* out, const rc_json_field_t* field, const char* field_name);
|
||||
|
@ -55,15 +60,18 @@ int rc_json_get_required_bool(int* out, rc_api_response_t* response, const rc_js
|
|||
int rc_json_get_required_datetime(time_t* out, rc_api_response_t* response, const rc_json_field_t* field, const char* field_name);
|
||||
int rc_json_get_required_object(rc_json_field_t* fields, size_t field_count, rc_api_response_t* response, rc_json_field_t* field, const char* field_name);
|
||||
int rc_json_get_required_unum_array(unsigned** entries, unsigned* num_entries, rc_api_response_t* response, const rc_json_field_t* field, const char* field_name);
|
||||
int rc_json_get_required_array(unsigned* num_entries, rc_json_field_t* iterator, rc_api_response_t* response, const rc_json_field_t* field, const char* field_name);
|
||||
int rc_json_get_array_entry_object(rc_json_field_t* fields, size_t field_count, rc_json_field_t* iterator);
|
||||
int rc_json_get_next_object_field(rc_json_object_field_iterator_t* iterator);
|
||||
int rc_json_get_required_array(unsigned* num_entries, rc_json_field_t* array_field, rc_api_response_t* response, const rc_json_field_t* field, const char* field_name);
|
||||
int rc_json_get_array_entry_object(rc_json_field_t* fields, size_t field_count, rc_json_iterator_t* iterator);
|
||||
int rc_json_get_next_object_field(rc_json_iterator_t* iterator, rc_json_field_t* field);
|
||||
int rc_json_get_object_string_length(const char* json);
|
||||
|
||||
void rc_buf_init(rc_api_buffer_t* buffer);
|
||||
void rc_buf_destroy(rc_api_buffer_t* buffer);
|
||||
char* rc_buf_reserve(rc_api_buffer_t* buffer, size_t amount);
|
||||
void rc_buf_consume(rc_api_buffer_t* buffer, const char* start, char* end);
|
||||
void* rc_buf_alloc(rc_api_buffer_t* buffer, size_t amount);
|
||||
char* rc_buf_strcpy(rc_api_buffer_t* buffer, const char* src);
|
||||
char* rc_buf_strncpy(rc_api_buffer_t* buffer, const char* src, size_t len);
|
||||
|
||||
void rc_url_builder_append_encoded_str(rc_api_url_builder_t* builder, const char* str);
|
||||
void rc_url_builder_append_num_param(rc_api_url_builder_t* builder, const char* param, int value);
|
||||
|
|
|
@ -22,12 +22,24 @@ int rc_api_init_fetch_code_notes_request(rc_api_request_t* request, const rc_api
|
|||
rc_url_builder_append_unum_param(&builder, "g", api_params->game_id);
|
||||
|
||||
request->post_data = rc_url_builder_finalize(&builder);
|
||||
request->content_type = RC_CONTENT_TYPE_URLENCODED;
|
||||
|
||||
return builder.result;
|
||||
}
|
||||
|
||||
int rc_api_process_fetch_code_notes_response(rc_api_fetch_code_notes_response_t* response, const char* server_response) {
|
||||
rc_json_field_t iterator;
|
||||
rc_api_server_response_t response_obj;
|
||||
|
||||
memset(&response_obj, 0, sizeof(response_obj));
|
||||
response_obj.body = server_response;
|
||||
response_obj.body_length = rc_json_get_object_string_length(server_response);
|
||||
|
||||
return rc_api_process_fetch_code_notes_server_response(response, &response_obj);
|
||||
}
|
||||
|
||||
int rc_api_process_fetch_code_notes_server_response(rc_api_fetch_code_notes_response_t* response, const rc_api_server_response_t* server_response) {
|
||||
rc_json_field_t array_field;
|
||||
rc_json_iterator_t iterator;
|
||||
rc_api_code_note_t* note;
|
||||
const char* address_str;
|
||||
const char* last_author = "";
|
||||
|
@ -36,25 +48,25 @@ int rc_api_process_fetch_code_notes_response(rc_api_fetch_code_notes_response_t*
|
|||
int result;
|
||||
|
||||
rc_json_field_t fields[] = {
|
||||
{"Success"},
|
||||
{"Error"},
|
||||
{"CodeNotes"}
|
||||
RC_JSON_NEW_FIELD("Success"),
|
||||
RC_JSON_NEW_FIELD("Error"),
|
||||
RC_JSON_NEW_FIELD("CodeNotes")
|
||||
};
|
||||
|
||||
rc_json_field_t note_fields[] = {
|
||||
{"Address"},
|
||||
{"User"},
|
||||
{"Note"}
|
||||
RC_JSON_NEW_FIELD("Address"),
|
||||
RC_JSON_NEW_FIELD("User"),
|
||||
RC_JSON_NEW_FIELD("Note")
|
||||
};
|
||||
|
||||
memset(response, 0, sizeof(*response));
|
||||
rc_buf_init(&response->response.buffer);
|
||||
|
||||
result = rc_json_parse_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0]));
|
||||
result = rc_json_parse_server_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0]));
|
||||
if (result != RC_OK || !response->response.succeeded)
|
||||
return result;
|
||||
|
||||
if (!rc_json_get_required_array(&response->num_notes, &iterator, &response->response, &fields[2], "CodeNotes"))
|
||||
if (!rc_json_get_required_array(&response->num_notes, &array_field, &response->response, &fields[2], "CodeNotes"))
|
||||
return RC_MISSING_VALUE;
|
||||
|
||||
if (response->num_notes) {
|
||||
|
@ -62,15 +74,21 @@ int rc_api_process_fetch_code_notes_response(rc_api_fetch_code_notes_response_t*
|
|||
if (!response->notes)
|
||||
return RC_OUT_OF_MEMORY;
|
||||
|
||||
memset(&iterator, 0, sizeof(iterator));
|
||||
iterator.json = array_field.value_start;
|
||||
iterator.end = array_field.value_end;
|
||||
|
||||
note = response->notes;
|
||||
while (rc_json_get_array_entry_object(note_fields, sizeof(note_fields) / sizeof(note_fields[0]), &iterator)) {
|
||||
/* an empty note represents a record that was deleted on the server */
|
||||
/* a note set to '' also represents a deleted note (remnant of a bug) */
|
||||
/* NOTE: len will include the quotes */
|
||||
len = note_fields[2].value_end - note_fields[2].value_start;
|
||||
if (len == 2 || (len == 4 && note_fields[2].value_start[1] == '\'' && note_fields[2].value_start[2] == '\'')) {
|
||||
--response->num_notes;
|
||||
continue;
|
||||
if (note_fields[2].value_start) {
|
||||
len = note_fields[2].value_end - note_fields[2].value_start;
|
||||
if (len == 2 || (len == 4 && note_fields[2].value_start[1] == '\'' && note_fields[2].value_start[2] == '\'')) {
|
||||
--response->num_notes;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (!rc_json_get_required_string(&address_str, &response->response, ¬e_fields[0], "Address"))
|
||||
|
@ -123,27 +141,38 @@ int rc_api_init_update_code_note_request(rc_api_request_t* request, const rc_api
|
|||
rc_url_builder_append_str_param(&builder, "n", api_params->note);
|
||||
|
||||
request->post_data = rc_url_builder_finalize(&builder);
|
||||
request->content_type = RC_CONTENT_TYPE_URLENCODED;
|
||||
|
||||
return builder.result;
|
||||
}
|
||||
|
||||
int rc_api_process_update_code_note_response(rc_api_update_code_note_response_t* response, const char* server_response) {
|
||||
rc_api_server_response_t response_obj;
|
||||
|
||||
memset(&response_obj, 0, sizeof(response_obj));
|
||||
response_obj.body = server_response;
|
||||
response_obj.body_length = rc_json_get_object_string_length(server_response);
|
||||
|
||||
return rc_api_process_update_code_note_server_response(response, &response_obj);
|
||||
}
|
||||
|
||||
int rc_api_process_update_code_note_server_response(rc_api_update_code_note_response_t* response, const rc_api_server_response_t* server_response) {
|
||||
int result;
|
||||
|
||||
rc_json_field_t fields[] = {
|
||||
{"Success"},
|
||||
{"Error"}
|
||||
RC_JSON_NEW_FIELD("Success"),
|
||||
RC_JSON_NEW_FIELD("Error")
|
||||
/* unused fields
|
||||
{"GameID"},
|
||||
{"Address"},
|
||||
{"Note"}
|
||||
RC_JSON_NEW_FIELD("GameID"),
|
||||
RC_JSON_NEW_FIELD("Address"),
|
||||
RC_JSON_NEW_FIELD("Note")
|
||||
*/
|
||||
};
|
||||
|
||||
memset(response, 0, sizeof(*response));
|
||||
rc_buf_init(&response->response.buffer);
|
||||
|
||||
result = rc_json_parse_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0]));
|
||||
result = rc_json_parse_server_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0]));
|
||||
if (result != RC_OK || !response->response.succeeded)
|
||||
return result;
|
||||
|
||||
|
@ -206,23 +235,34 @@ int rc_api_init_update_achievement_request(rc_api_request_t* request, const rc_a
|
|||
rc_url_builder_append_str_param(&builder, "h", buffer);
|
||||
|
||||
request->post_data = rc_url_builder_finalize(&builder);
|
||||
request->content_type = RC_CONTENT_TYPE_URLENCODED;
|
||||
|
||||
return builder.result;
|
||||
}
|
||||
|
||||
int rc_api_process_update_achievement_response(rc_api_update_achievement_response_t* response, const char* server_response) {
|
||||
rc_api_server_response_t response_obj;
|
||||
|
||||
memset(&response_obj, 0, sizeof(response_obj));
|
||||
response_obj.body = server_response;
|
||||
response_obj.body_length = rc_json_get_object_string_length(server_response);
|
||||
|
||||
return rc_api_process_update_achievement_server_response(response, &response_obj);
|
||||
}
|
||||
|
||||
int rc_api_process_update_achievement_server_response(rc_api_update_achievement_response_t* response, const rc_api_server_response_t* server_response) {
|
||||
int result;
|
||||
|
||||
rc_json_field_t fields[] = {
|
||||
{"Success"},
|
||||
{"Error"},
|
||||
{"AchievementID"}
|
||||
RC_JSON_NEW_FIELD("Success"),
|
||||
RC_JSON_NEW_FIELD("Error"),
|
||||
RC_JSON_NEW_FIELD("AchievementID")
|
||||
};
|
||||
|
||||
memset(response, 0, sizeof(*response));
|
||||
rc_buf_init(&response->response.buffer);
|
||||
|
||||
result = rc_json_parse_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0]));
|
||||
result = rc_json_parse_server_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0]));
|
||||
if (result != RC_OK || !response->response.succeeded)
|
||||
return result;
|
||||
|
||||
|
@ -297,23 +337,34 @@ int rc_api_init_update_leaderboard_request(rc_api_request_t* request, const rc_a
|
|||
rc_url_builder_append_str_param(&builder, "h", buffer);
|
||||
|
||||
request->post_data = rc_url_builder_finalize(&builder);
|
||||
request->content_type = RC_CONTENT_TYPE_URLENCODED;
|
||||
|
||||
return builder.result;
|
||||
}
|
||||
|
||||
int rc_api_process_update_leaderboard_response(rc_api_update_leaderboard_response_t* response, const char* server_response) {
|
||||
rc_api_server_response_t response_obj;
|
||||
|
||||
memset(&response_obj, 0, sizeof(response_obj));
|
||||
response_obj.body = server_response;
|
||||
response_obj.body_length = rc_json_get_object_string_length(server_response);
|
||||
|
||||
return rc_api_process_update_leaderboard_server_response(response, &response_obj);
|
||||
}
|
||||
|
||||
int rc_api_process_update_leaderboard_server_response(rc_api_update_leaderboard_response_t* response, const rc_api_server_response_t* server_response) {
|
||||
int result;
|
||||
|
||||
rc_json_field_t fields[] = {
|
||||
{"Success"},
|
||||
{"Error"},
|
||||
{"LeaderboardID"}
|
||||
RC_JSON_NEW_FIELD("Success"),
|
||||
RC_JSON_NEW_FIELD("Error"),
|
||||
RC_JSON_NEW_FIELD("LeaderboardID")
|
||||
};
|
||||
|
||||
memset(response, 0, sizeof(*response));
|
||||
rc_buf_init(&response->response.buffer);
|
||||
|
||||
result = rc_json_parse_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0]));
|
||||
result = rc_json_parse_server_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0]));
|
||||
if (result != RC_OK || !response->response.succeeded)
|
||||
return result;
|
||||
|
||||
|
@ -338,24 +389,37 @@ int rc_api_init_fetch_badge_range_request(rc_api_request_t* request, const rc_ap
|
|||
rc_url_builder_append_str_param(&builder, "r", "badgeiter");
|
||||
|
||||
request->post_data = rc_url_builder_finalize(&builder);
|
||||
request->content_type = RC_CONTENT_TYPE_URLENCODED;
|
||||
|
||||
(void)api_params;
|
||||
|
||||
return builder.result;
|
||||
}
|
||||
|
||||
int rc_api_process_fetch_badge_range_response(rc_api_fetch_badge_range_response_t* response, const char* server_response) {
|
||||
rc_api_server_response_t response_obj;
|
||||
|
||||
memset(&response_obj, 0, sizeof(response_obj));
|
||||
response_obj.body = server_response;
|
||||
response_obj.body_length = rc_json_get_object_string_length(server_response);
|
||||
|
||||
return rc_api_process_fetch_badge_range_server_response(response, &response_obj);
|
||||
}
|
||||
|
||||
int rc_api_process_fetch_badge_range_server_response(rc_api_fetch_badge_range_response_t* response, const rc_api_server_response_t* server_response) {
|
||||
int result;
|
||||
|
||||
rc_json_field_t fields[] = {
|
||||
{"Success"},
|
||||
{"Error"},
|
||||
{"FirstBadge"},
|
||||
{"NextBadge"}
|
||||
RC_JSON_NEW_FIELD("Success"),
|
||||
RC_JSON_NEW_FIELD("Error"),
|
||||
RC_JSON_NEW_FIELD("FirstBadge"),
|
||||
RC_JSON_NEW_FIELD("NextBadge")
|
||||
};
|
||||
|
||||
memset(response, 0, sizeof(*response));
|
||||
rc_buf_init(&response->response.buffer);
|
||||
|
||||
result = rc_json_parse_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0]));
|
||||
result = rc_json_parse_server_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0]));
|
||||
if (result != RC_OK || !response->response.succeeded)
|
||||
return result;
|
||||
|
||||
|
@ -399,33 +463,44 @@ int rc_api_init_add_game_hash_request(rc_api_request_t* request, const rc_api_ad
|
|||
rc_url_builder_append_str_param(&builder, "d", api_params->hash_description);
|
||||
|
||||
request->post_data = rc_url_builder_finalize(&builder);
|
||||
request->content_type = RC_CONTENT_TYPE_URLENCODED;
|
||||
|
||||
return builder.result;
|
||||
}
|
||||
|
||||
int rc_api_process_add_game_hash_response(rc_api_add_game_hash_response_t* response, const char* server_response) {
|
||||
rc_api_server_response_t response_obj;
|
||||
|
||||
memset(&response_obj, 0, sizeof(response_obj));
|
||||
response_obj.body = server_response;
|
||||
response_obj.body_length = rc_json_get_object_string_length(server_response);
|
||||
|
||||
return rc_api_process_add_game_hash_server_response(response, &response_obj);
|
||||
}
|
||||
|
||||
int rc_api_process_add_game_hash_server_response(rc_api_add_game_hash_response_t* response, const rc_api_server_response_t* server_response) {
|
||||
int result;
|
||||
|
||||
rc_json_field_t fields[] = {
|
||||
{"Success"},
|
||||
{"Error"},
|
||||
{"Response"}
|
||||
RC_JSON_NEW_FIELD("Success"),
|
||||
RC_JSON_NEW_FIELD("Error"),
|
||||
RC_JSON_NEW_FIELD("Response")
|
||||
};
|
||||
|
||||
rc_json_field_t response_fields[] = {
|
||||
{"GameID"}
|
||||
RC_JSON_NEW_FIELD("GameID")
|
||||
/* unused fields
|
||||
{"MD5"},
|
||||
{"ConsoleID"},
|
||||
{"GameTitle"},
|
||||
{"Success"}
|
||||
RC_JSON_NEW_FIELD("MD5"),
|
||||
RC_JSON_NEW_FIELD("ConsoleID"),
|
||||
RC_JSON_NEW_FIELD("GameTitle"),
|
||||
RC_JSON_NEW_FIELD("Success")
|
||||
*/
|
||||
};
|
||||
|
||||
memset(response, 0, sizeof(*response));
|
||||
rc_buf_init(&response->response.buffer);
|
||||
|
||||
result = rc_json_parse_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0]));
|
||||
result = rc_json_parse_server_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0]));
|
||||
if (result != RC_OK || !response->response.succeeded)
|
||||
return result;
|
||||
|
||||
|
|
|
@ -27,45 +27,57 @@ int rc_api_init_fetch_achievement_info_request(rc_api_request_t* request, const
|
|||
rc_url_builder_append_unum_param(&builder, "c", api_params->count);
|
||||
|
||||
request->post_data = rc_url_builder_finalize(&builder);
|
||||
request->content_type = RC_CONTENT_TYPE_URLENCODED;
|
||||
}
|
||||
|
||||
return builder.result;
|
||||
}
|
||||
|
||||
int rc_api_process_fetch_achievement_info_response(rc_api_fetch_achievement_info_response_t* response, const char* server_response) {
|
||||
rc_api_server_response_t response_obj;
|
||||
|
||||
memset(&response_obj, 0, sizeof(response_obj));
|
||||
response_obj.body = server_response;
|
||||
response_obj.body_length = rc_json_get_object_string_length(server_response);
|
||||
|
||||
return rc_api_process_fetch_achievement_info_server_response(response, &response_obj);
|
||||
}
|
||||
|
||||
int rc_api_process_fetch_achievement_info_server_response(rc_api_fetch_achievement_info_response_t* response, const rc_api_server_response_t* server_response) {
|
||||
rc_api_achievement_awarded_entry_t* entry;
|
||||
rc_json_field_t iterator;
|
||||
rc_json_field_t array_field;
|
||||
rc_json_iterator_t iterator;
|
||||
unsigned timet;
|
||||
int result;
|
||||
|
||||
rc_json_field_t fields[] = {
|
||||
{"Success"},
|
||||
{"Error"},
|
||||
{"AchievementID"},
|
||||
{"Response"}
|
||||
RC_JSON_NEW_FIELD("Success"),
|
||||
RC_JSON_NEW_FIELD("Error"),
|
||||
RC_JSON_NEW_FIELD("AchievementID"),
|
||||
RC_JSON_NEW_FIELD("Response")
|
||||
/* unused fields
|
||||
{"Offset"},
|
||||
{"Count"},
|
||||
{"FriendsOnly"},
|
||||
RC_JSON_NEW_FIELD("Offset"),
|
||||
RC_JSON_NEW_FIELD("Count"),
|
||||
RC_JSON_NEW_FIELD("FriendsOnly")
|
||||
* unused fields */
|
||||
};
|
||||
|
||||
rc_json_field_t response_fields[] = {
|
||||
{"NumEarned"},
|
||||
{"TotalPlayers"},
|
||||
{"GameID"},
|
||||
{"RecentWinner"} /* array */
|
||||
RC_JSON_NEW_FIELD("NumEarned"),
|
||||
RC_JSON_NEW_FIELD("TotalPlayers"),
|
||||
RC_JSON_NEW_FIELD("GameID"),
|
||||
RC_JSON_NEW_FIELD("RecentWinner") /* array */
|
||||
};
|
||||
|
||||
rc_json_field_t entry_fields[] = {
|
||||
{"User"},
|
||||
{"DateAwarded"}
|
||||
RC_JSON_NEW_FIELD("User"),
|
||||
RC_JSON_NEW_FIELD("DateAwarded")
|
||||
};
|
||||
|
||||
memset(response, 0, sizeof(*response));
|
||||
rc_buf_init(&response->response.buffer);
|
||||
|
||||
result = rc_json_parse_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0]));
|
||||
result = rc_json_parse_server_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0]));
|
||||
if (result != RC_OK)
|
||||
return result;
|
||||
|
||||
|
@ -81,7 +93,7 @@ int rc_api_process_fetch_achievement_info_response(rc_api_fetch_achievement_info
|
|||
if (!rc_json_get_required_unum(&response->game_id, &response->response, &response_fields[2], "GameID"))
|
||||
return RC_MISSING_VALUE;
|
||||
|
||||
if (!rc_json_get_required_array(&response->num_recently_awarded, &iterator, &response->response, &response_fields[3], "RecentWinner"))
|
||||
if (!rc_json_get_required_array(&response->num_recently_awarded, &array_field, &response->response, &response_fields[3], "RecentWinner"))
|
||||
return RC_MISSING_VALUE;
|
||||
|
||||
if (response->num_recently_awarded) {
|
||||
|
@ -89,6 +101,10 @@ int rc_api_process_fetch_achievement_info_response(rc_api_fetch_achievement_info
|
|||
if (!response->recently_awarded)
|
||||
return RC_OUT_OF_MEMORY;
|
||||
|
||||
memset(&iterator, 0, sizeof(iterator));
|
||||
iterator.json = array_field.value_start;
|
||||
iterator.end = array_field.value_end;
|
||||
|
||||
entry = response->recently_awarded;
|
||||
while (rc_json_get_array_entry_object(entry_fields, sizeof(entry_fields) / sizeof(entry_fields[0]), &iterator)) {
|
||||
if (!rc_json_get_required_string(&entry->username, &response->response, &entry_fields[0], "User"))
|
||||
|
@ -130,57 +146,69 @@ int rc_api_init_fetch_leaderboard_info_request(rc_api_request_t* request, const
|
|||
|
||||
rc_url_builder_append_unum_param(&builder, "c", api_params->count);
|
||||
request->post_data = rc_url_builder_finalize(&builder);
|
||||
request->content_type = RC_CONTENT_TYPE_URLENCODED;
|
||||
|
||||
return builder.result;
|
||||
}
|
||||
|
||||
int rc_api_process_fetch_leaderboard_info_response(rc_api_fetch_leaderboard_info_response_t* response, const char* server_response) {
|
||||
rc_api_server_response_t response_obj;
|
||||
|
||||
memset(&response_obj, 0, sizeof(response_obj));
|
||||
response_obj.body = server_response;
|
||||
response_obj.body_length = rc_json_get_object_string_length(server_response);
|
||||
|
||||
return rc_api_process_fetch_leaderboard_info_server_response(response, &response_obj);
|
||||
}
|
||||
|
||||
int rc_api_process_fetch_leaderboard_info_server_response(rc_api_fetch_leaderboard_info_response_t* response, const rc_api_server_response_t* server_response) {
|
||||
rc_api_lboard_info_entry_t* entry;
|
||||
rc_json_field_t iterator;
|
||||
rc_json_field_t array_field;
|
||||
rc_json_iterator_t iterator;
|
||||
unsigned timet;
|
||||
int result;
|
||||
size_t len;
|
||||
char format[16];
|
||||
|
||||
rc_json_field_t fields[] = {
|
||||
{"Success"},
|
||||
{"Error"},
|
||||
{"LeaderboardData"}
|
||||
RC_JSON_NEW_FIELD("Success"),
|
||||
RC_JSON_NEW_FIELD("Error"),
|
||||
RC_JSON_NEW_FIELD("LeaderboardData")
|
||||
};
|
||||
|
||||
rc_json_field_t leaderboarddata_fields[] = {
|
||||
{"LBID"},
|
||||
{"LBFormat"},
|
||||
{"LowerIsBetter"},
|
||||
{"LBTitle"},
|
||||
{"LBDesc"},
|
||||
{"LBMem"},
|
||||
{"GameID"},
|
||||
{"LBAuthor"},
|
||||
{"LBCreated"},
|
||||
{"LBUpdated"},
|
||||
{"Entries"} /* array */
|
||||
RC_JSON_NEW_FIELD("LBID"),
|
||||
RC_JSON_NEW_FIELD("LBFormat"),
|
||||
RC_JSON_NEW_FIELD("LowerIsBetter"),
|
||||
RC_JSON_NEW_FIELD("LBTitle"),
|
||||
RC_JSON_NEW_FIELD("LBDesc"),
|
||||
RC_JSON_NEW_FIELD("LBMem"),
|
||||
RC_JSON_NEW_FIELD("GameID"),
|
||||
RC_JSON_NEW_FIELD("LBAuthor"),
|
||||
RC_JSON_NEW_FIELD("LBCreated"),
|
||||
RC_JSON_NEW_FIELD("LBUpdated"),
|
||||
RC_JSON_NEW_FIELD("Entries") /* array */
|
||||
/* unused fields
|
||||
{"GameTitle"},
|
||||
{"ConsoleID"},
|
||||
{"ConsoleName"},
|
||||
{"ForumTopicID"},
|
||||
{"GameIcon"},
|
||||
RC_JSON_NEW_FIELD("GameTitle"),
|
||||
RC_JSON_NEW_FIELD("ConsoleID"),
|
||||
RC_JSON_NEW_FIELD("ConsoleName"),
|
||||
RC_JSON_NEW_FIELD("ForumTopicID"),
|
||||
RC_JSON_NEW_FIELD("GameIcon")
|
||||
* unused fields */
|
||||
};
|
||||
|
||||
rc_json_field_t entry_fields[] = {
|
||||
{"User"},
|
||||
{"Rank"},
|
||||
{"Index"},
|
||||
{"Score"},
|
||||
{"DateSubmitted"}
|
||||
RC_JSON_NEW_FIELD("User"),
|
||||
RC_JSON_NEW_FIELD("Rank"),
|
||||
RC_JSON_NEW_FIELD("Index"),
|
||||
RC_JSON_NEW_FIELD("Score"),
|
||||
RC_JSON_NEW_FIELD("DateSubmitted")
|
||||
};
|
||||
|
||||
memset(response, 0, sizeof(*response));
|
||||
rc_buf_init(&response->response.buffer);
|
||||
|
||||
result = rc_json_parse_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0]));
|
||||
result = rc_json_parse_server_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0]));
|
||||
if (result != RC_OK)
|
||||
return result;
|
||||
|
||||
|
@ -218,7 +246,7 @@ int rc_api_process_fetch_leaderboard_info_response(rc_api_fetch_leaderboard_info
|
|||
response->format = RC_FORMAT_VALUE;
|
||||
}
|
||||
|
||||
if (!rc_json_get_required_array(&response->num_entries, &iterator, &response->response, &leaderboarddata_fields[10], "Entries"))
|
||||
if (!rc_json_get_required_array(&response->num_entries, &array_field, &response->response, &leaderboarddata_fields[10], "Entries"))
|
||||
return RC_MISSING_VALUE;
|
||||
|
||||
if (response->num_entries) {
|
||||
|
@ -226,6 +254,10 @@ int rc_api_process_fetch_leaderboard_info_response(rc_api_fetch_leaderboard_info
|
|||
if (!response->entries)
|
||||
return RC_OUT_OF_MEMORY;
|
||||
|
||||
memset(&iterator, 0, sizeof(iterator));
|
||||
iterator.json = array_field.value_start;
|
||||
iterator.end = array_field.value_end;
|
||||
|
||||
entry = response->entries;
|
||||
while (rc_json_get_array_entry_object(entry_fields, sizeof(entry_fields) / sizeof(entry_fields[0]), &iterator)) {
|
||||
if (!rc_json_get_required_string(&entry->username, &response->response, &entry_fields[0], "User"))
|
||||
|
@ -270,26 +302,38 @@ int rc_api_init_fetch_games_list_request(rc_api_request_t* request, const rc_api
|
|||
rc_url_builder_append_unum_param(&builder, "c", api_params->console_id);
|
||||
|
||||
request->post_data = rc_url_builder_finalize(&builder);
|
||||
request->content_type = RC_CONTENT_TYPE_URLENCODED;
|
||||
|
||||
return builder.result;
|
||||
}
|
||||
|
||||
int rc_api_process_fetch_games_list_response(rc_api_fetch_games_list_response_t* response, const char* server_response) {
|
||||
rc_api_server_response_t response_obj;
|
||||
|
||||
memset(&response_obj, 0, sizeof(response_obj));
|
||||
response_obj.body = server_response;
|
||||
response_obj.body_length = rc_json_get_object_string_length(server_response);
|
||||
|
||||
return rc_api_process_fetch_games_list_server_response(response, &response_obj);
|
||||
}
|
||||
|
||||
int rc_api_process_fetch_games_list_server_response(rc_api_fetch_games_list_response_t* response, const rc_api_server_response_t* server_response) {
|
||||
rc_api_game_list_entry_t* entry;
|
||||
rc_json_object_field_iterator_t iterator;
|
||||
rc_json_iterator_t iterator;
|
||||
rc_json_field_t field;
|
||||
int result;
|
||||
char* end;
|
||||
|
||||
rc_json_field_t fields[] = {
|
||||
{"Success"},
|
||||
{"Error"},
|
||||
{"Response"}
|
||||
RC_JSON_NEW_FIELD("Success"),
|
||||
RC_JSON_NEW_FIELD("Error"),
|
||||
RC_JSON_NEW_FIELD("Response")
|
||||
};
|
||||
|
||||
memset(response, 0, sizeof(*response));
|
||||
rc_buf_init(&response->response.buffer);
|
||||
|
||||
result = rc_json_parse_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0]));
|
||||
result = rc_json_parse_server_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0]));
|
||||
if (result != RC_OK)
|
||||
return result;
|
||||
|
||||
|
@ -308,13 +352,14 @@ int rc_api_process_fetch_games_list_response(rc_api_fetch_games_list_response_t*
|
|||
|
||||
memset(&iterator, 0, sizeof(iterator));
|
||||
iterator.json = fields[2].value_start;
|
||||
iterator.end = fields[2].value_end;
|
||||
|
||||
entry = response->entries;
|
||||
while (rc_json_get_next_object_field(&iterator)) {
|
||||
entry->id = strtol(iterator.field.name, &end, 10);
|
||||
while (rc_json_get_next_object_field(&iterator, &field)) {
|
||||
entry->id = strtol(field.name, &end, 10);
|
||||
|
||||
iterator.field.name = "";
|
||||
if (!rc_json_get_string(&entry->name, &response->response.buffer, &iterator.field, ""))
|
||||
field.name = "";
|
||||
if (!rc_json_get_string(&entry->name, &response->response.buffer, &field, ""))
|
||||
return RC_MISSING_VALUE;
|
||||
|
||||
++entry;
|
||||
|
|
|
@ -24,22 +24,33 @@ int rc_api_init_resolve_hash_request(rc_api_request_t* request, const rc_api_res
|
|||
rc_url_builder_append_str_param(&builder, "r", "gameid");
|
||||
rc_url_builder_append_str_param(&builder, "m", api_params->game_hash);
|
||||
request->post_data = rc_url_builder_finalize(&builder);
|
||||
request->content_type = RC_CONTENT_TYPE_URLENCODED;
|
||||
|
||||
return builder.result;
|
||||
}
|
||||
|
||||
int rc_api_process_resolve_hash_response(rc_api_resolve_hash_response_t* response, const char* server_response) {
|
||||
rc_api_server_response_t response_obj;
|
||||
|
||||
memset(&response_obj, 0, sizeof(response_obj));
|
||||
response_obj.body = server_response;
|
||||
response_obj.body_length = rc_json_get_object_string_length(server_response);
|
||||
|
||||
return rc_api_process_resolve_hash_server_response(response, &response_obj);
|
||||
}
|
||||
|
||||
int rc_api_process_resolve_hash_server_response(rc_api_resolve_hash_response_t* response, const rc_api_server_response_t* server_response) {
|
||||
int result;
|
||||
rc_json_field_t fields[] = {
|
||||
{"Success"},
|
||||
{"Error"},
|
||||
{"GameID"},
|
||||
RC_JSON_NEW_FIELD("Success"),
|
||||
RC_JSON_NEW_FIELD("Error"),
|
||||
RC_JSON_NEW_FIELD("GameID")
|
||||
};
|
||||
|
||||
memset(response, 0, sizeof(*response));
|
||||
rc_buf_init(&response->response.buffer);
|
||||
|
||||
result = rc_json_parse_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0]));
|
||||
result = rc_json_parse_server_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0]));
|
||||
if (result != RC_OK)
|
||||
return result;
|
||||
|
||||
|
@ -65,17 +76,30 @@ int rc_api_init_fetch_game_data_request(rc_api_request_t* request, const rc_api_
|
|||
if (rc_api_url_build_dorequest(&builder, "patch", api_params->username, api_params->api_token)) {
|
||||
rc_url_builder_append_unum_param(&builder, "g", api_params->game_id);
|
||||
request->post_data = rc_url_builder_finalize(&builder);
|
||||
request->content_type = RC_CONTENT_TYPE_URLENCODED;
|
||||
}
|
||||
|
||||
return builder.result;
|
||||
}
|
||||
|
||||
int rc_api_process_fetch_game_data_response(rc_api_fetch_game_data_response_t* response, const char* server_response) {
|
||||
rc_api_server_response_t response_obj;
|
||||
|
||||
memset(&response_obj, 0, sizeof(response_obj));
|
||||
response_obj.body = server_response;
|
||||
response_obj.body_length = rc_json_get_object_string_length(server_response);
|
||||
|
||||
return rc_api_process_fetch_game_data_server_response(response, &response_obj);
|
||||
}
|
||||
|
||||
int rc_api_process_fetch_game_data_server_response(rc_api_fetch_game_data_response_t* response, const rc_api_server_response_t* server_response) {
|
||||
rc_api_achievement_definition_t* achievement;
|
||||
rc_api_leaderboard_definition_t* leaderboard;
|
||||
rc_json_field_t iterator;
|
||||
rc_json_field_t array_field;
|
||||
rc_json_iterator_t iterator;
|
||||
const char* str;
|
||||
const char* last_author = "";
|
||||
const char* last_author_field = "";
|
||||
size_t last_author_len = 0;
|
||||
size_t len;
|
||||
unsigned timet;
|
||||
|
@ -83,52 +107,52 @@ int rc_api_process_fetch_game_data_response(rc_api_fetch_game_data_response_t* r
|
|||
char format[16];
|
||||
|
||||
rc_json_field_t fields[] = {
|
||||
{"Success"},
|
||||
{"Error"},
|
||||
{"PatchData"} /* nested object */
|
||||
RC_JSON_NEW_FIELD("Success"),
|
||||
RC_JSON_NEW_FIELD("Error"),
|
||||
RC_JSON_NEW_FIELD("PatchData") /* nested object */
|
||||
};
|
||||
|
||||
rc_json_field_t patchdata_fields[] = {
|
||||
{"ID"},
|
||||
{"Title"},
|
||||
{"ConsoleID"},
|
||||
{"ImageIcon"},
|
||||
{"RichPresencePatch"},
|
||||
{"Achievements"}, /* array */
|
||||
{"Leaderboards"} /* array */
|
||||
RC_JSON_NEW_FIELD("ID"),
|
||||
RC_JSON_NEW_FIELD("Title"),
|
||||
RC_JSON_NEW_FIELD("ConsoleID"),
|
||||
RC_JSON_NEW_FIELD("ImageIcon"),
|
||||
RC_JSON_NEW_FIELD("RichPresencePatch"),
|
||||
RC_JSON_NEW_FIELD("Achievements"), /* array */
|
||||
RC_JSON_NEW_FIELD("Leaderboards") /* array */
|
||||
/* unused fields
|
||||
{"ForumTopicID"},
|
||||
{"Flags"},
|
||||
RC_JSON_NEW_FIELD("ForumTopicID"),
|
||||
RC_JSON_NEW_FIELD("Flags")
|
||||
* unused fields */
|
||||
};
|
||||
|
||||
rc_json_field_t achievement_fields[] = {
|
||||
{"ID"},
|
||||
{"Title"},
|
||||
{"Description"},
|
||||
{"Flags"},
|
||||
{"Points"},
|
||||
{"MemAddr"},
|
||||
{"Author"},
|
||||
{"BadgeName"},
|
||||
{"Created"},
|
||||
{"Modified"}
|
||||
RC_JSON_NEW_FIELD("ID"),
|
||||
RC_JSON_NEW_FIELD("Title"),
|
||||
RC_JSON_NEW_FIELD("Description"),
|
||||
RC_JSON_NEW_FIELD("Flags"),
|
||||
RC_JSON_NEW_FIELD("Points"),
|
||||
RC_JSON_NEW_FIELD("MemAddr"),
|
||||
RC_JSON_NEW_FIELD("Author"),
|
||||
RC_JSON_NEW_FIELD("BadgeName"),
|
||||
RC_JSON_NEW_FIELD("Created"),
|
||||
RC_JSON_NEW_FIELD("Modified")
|
||||
};
|
||||
|
||||
rc_json_field_t leaderboard_fields[] = {
|
||||
{"ID"},
|
||||
{"Title"},
|
||||
{"Description"},
|
||||
{"Mem"},
|
||||
{"Format"},
|
||||
{"LowerIsBetter"},
|
||||
{"Hidden"}
|
||||
RC_JSON_NEW_FIELD("ID"),
|
||||
RC_JSON_NEW_FIELD("Title"),
|
||||
RC_JSON_NEW_FIELD("Description"),
|
||||
RC_JSON_NEW_FIELD("Mem"),
|
||||
RC_JSON_NEW_FIELD("Format"),
|
||||
RC_JSON_NEW_FIELD("LowerIsBetter"),
|
||||
RC_JSON_NEW_FIELD("Hidden")
|
||||
};
|
||||
|
||||
memset(response, 0, sizeof(*response));
|
||||
rc_buf_init(&response->response.buffer);
|
||||
|
||||
result = rc_json_parse_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0]));
|
||||
result = rc_json_parse_server_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0]));
|
||||
if (result != RC_OK || !response->response.succeeded)
|
||||
return result;
|
||||
|
||||
|
@ -174,7 +198,7 @@ int rc_api_process_fetch_game_data_response(rc_api_fetch_game_data_response_t* r
|
|||
if (!response->rich_presence_script)
|
||||
response->rich_presence_script = "";
|
||||
|
||||
if (!rc_json_get_required_array(&response->num_achievements, &iterator, &response->response, &patchdata_fields[5], "Achievements"))
|
||||
if (!rc_json_get_required_array(&response->num_achievements, &array_field, &response->response, &patchdata_fields[5], "Achievements"))
|
||||
return RC_MISSING_VALUE;
|
||||
|
||||
if (response->num_achievements) {
|
||||
|
@ -182,6 +206,10 @@ int rc_api_process_fetch_game_data_response(rc_api_fetch_game_data_response_t* r
|
|||
if (!response->achievements)
|
||||
return RC_OUT_OF_MEMORY;
|
||||
|
||||
memset(&iterator, 0, sizeof(iterator));
|
||||
iterator.json = array_field.value_start;
|
||||
iterator.end = array_field.value_end;
|
||||
|
||||
achievement = response->achievements;
|
||||
while (rc_json_get_array_entry_object(achievement_fields, sizeof(achievement_fields) / sizeof(achievement_fields[0]), &iterator)) {
|
||||
if (!rc_json_get_required_unum(&achievement->id, &response->response, &achievement_fields[0], "ID"))
|
||||
|
@ -199,8 +227,8 @@ int rc_api_process_fetch_game_data_response(rc_api_fetch_game_data_response_t* r
|
|||
if (!rc_json_get_required_string(&achievement->badge_name, &response->response, &achievement_fields[7], "BadgeName"))
|
||||
return RC_MISSING_VALUE;
|
||||
|
||||
len = achievement_fields[7].value_end - achievement_fields[7].value_start;
|
||||
if (len == last_author_len && memcmp(achievement_fields[7].value_start, last_author, len) == 0) {
|
||||
len = achievement_fields[6].value_end - achievement_fields[6].value_start;
|
||||
if (len == last_author_len && memcmp(achievement_fields[6].value_start, last_author_field, len) == 0) {
|
||||
achievement->author = last_author;
|
||||
}
|
||||
else {
|
||||
|
@ -208,6 +236,7 @@ int rc_api_process_fetch_game_data_response(rc_api_fetch_game_data_response_t* r
|
|||
return RC_MISSING_VALUE;
|
||||
|
||||
last_author = achievement->author;
|
||||
last_author_field = achievement_fields[6].value_start;
|
||||
last_author_len = len;
|
||||
}
|
||||
|
||||
|
@ -222,7 +251,7 @@ int rc_api_process_fetch_game_data_response(rc_api_fetch_game_data_response_t* r
|
|||
}
|
||||
}
|
||||
|
||||
if (!rc_json_get_required_array(&response->num_leaderboards, &iterator, &response->response, &patchdata_fields[6], "Leaderboards"))
|
||||
if (!rc_json_get_required_array(&response->num_leaderboards, &array_field, &response->response, &patchdata_fields[6], "Leaderboards"))
|
||||
return RC_MISSING_VALUE;
|
||||
|
||||
if (response->num_leaderboards) {
|
||||
|
@ -230,6 +259,10 @@ int rc_api_process_fetch_game_data_response(rc_api_fetch_game_data_response_t* r
|
|||
if (!response->leaderboards)
|
||||
return RC_OUT_OF_MEMORY;
|
||||
|
||||
memset(&iterator, 0, sizeof(iterator));
|
||||
iterator.json = array_field.value_start;
|
||||
iterator.end = array_field.value_end;
|
||||
|
||||
leaderboard = response->leaderboards;
|
||||
while (rc_json_get_array_entry_object(leaderboard_fields, sizeof(leaderboard_fields) / sizeof(leaderboard_fields[0]), &iterator)) {
|
||||
if (!rc_json_get_required_unum(&leaderboard->id, &response->response, &leaderboard_fields[0], "ID"))
|
||||
|
@ -284,21 +317,32 @@ int rc_api_init_ping_request(rc_api_request_t* request, const rc_api_ping_reques
|
|||
rc_url_builder_append_str_param(&builder, "m", api_params->rich_presence);
|
||||
|
||||
request->post_data = rc_url_builder_finalize(&builder);
|
||||
request->content_type = RC_CONTENT_TYPE_URLENCODED;
|
||||
}
|
||||
|
||||
return builder.result;
|
||||
}
|
||||
|
||||
int rc_api_process_ping_response(rc_api_ping_response_t* response, const char* server_response) {
|
||||
rc_api_server_response_t response_obj;
|
||||
|
||||
memset(&response_obj, 0, sizeof(response_obj));
|
||||
response_obj.body = server_response;
|
||||
response_obj.body_length = rc_json_get_object_string_length(server_response);
|
||||
|
||||
return rc_api_process_ping_server_response(response, &response_obj);
|
||||
}
|
||||
|
||||
int rc_api_process_ping_server_response(rc_api_ping_response_t* response, const rc_api_server_response_t* server_response) {
|
||||
rc_json_field_t fields[] = {
|
||||
{"Success"},
|
||||
{"Error"}
|
||||
RC_JSON_NEW_FIELD("Success"),
|
||||
RC_JSON_NEW_FIELD("Error")
|
||||
};
|
||||
|
||||
memset(response, 0, sizeof(*response));
|
||||
rc_buf_init(&response->response.buffer);
|
||||
|
||||
return rc_json_parse_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0]));
|
||||
return rc_json_parse_server_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0]));
|
||||
}
|
||||
|
||||
void rc_api_destroy_ping_response(rc_api_ping_response_t* response) {
|
||||
|
@ -337,25 +381,37 @@ int rc_api_init_award_achievement_request(rc_api_request_t* request, const rc_ap
|
|||
rc_url_builder_append_str_param(&builder, "v", buffer);
|
||||
|
||||
request->post_data = rc_url_builder_finalize(&builder);
|
||||
request->content_type = RC_CONTENT_TYPE_URLENCODED;
|
||||
}
|
||||
|
||||
return builder.result;
|
||||
}
|
||||
|
||||
int rc_api_process_award_achievement_response(rc_api_award_achievement_response_t* response, const char* server_response) {
|
||||
rc_api_server_response_t response_obj;
|
||||
|
||||
memset(&response_obj, 0, sizeof(response_obj));
|
||||
response_obj.body = server_response;
|
||||
response_obj.body_length = rc_json_get_object_string_length(server_response);
|
||||
|
||||
return rc_api_process_award_achievement_server_response(response, &response_obj);
|
||||
}
|
||||
|
||||
int rc_api_process_award_achievement_server_response(rc_api_award_achievement_response_t* response, const rc_api_server_response_t* server_response) {
|
||||
int result;
|
||||
rc_json_field_t fields[] = {
|
||||
{"Success"},
|
||||
{"Error"},
|
||||
{"Score"},
|
||||
{"AchievementID"},
|
||||
{"AchievementsRemaining"}
|
||||
RC_JSON_NEW_FIELD("Success"),
|
||||
RC_JSON_NEW_FIELD("Error"),
|
||||
RC_JSON_NEW_FIELD("Score"),
|
||||
RC_JSON_NEW_FIELD("SoftcoreScore"),
|
||||
RC_JSON_NEW_FIELD("AchievementID"),
|
||||
RC_JSON_NEW_FIELD("AchievementsRemaining")
|
||||
};
|
||||
|
||||
memset(response, 0, sizeof(*response));
|
||||
rc_buf_init(&response->response.buffer);
|
||||
|
||||
result = rc_json_parse_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0]));
|
||||
result = rc_json_parse_server_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0]));
|
||||
if (result != RC_OK)
|
||||
return result;
|
||||
|
||||
|
@ -373,8 +429,9 @@ int rc_api_process_award_achievement_response(rc_api_award_achievement_response_
|
|||
}
|
||||
|
||||
rc_json_get_optional_unum(&response->new_player_score, &fields[2], "Score", 0);
|
||||
rc_json_get_optional_unum(&response->awarded_achievement_id, &fields[3], "AchievementID", 0);
|
||||
rc_json_get_optional_unum(&response->achievements_remaining, &fields[4], "AchievementsRemaining", (unsigned)-1);
|
||||
rc_json_get_optional_unum(&response->new_player_score_softcore, &fields[3], "SoftcoreScore", 0);
|
||||
rc_json_get_optional_unum(&response->awarded_achievement_id, &fields[4], "AchievementID", 0);
|
||||
rc_json_get_optional_unum(&response->achievements_remaining, &fields[5], "AchievementsRemaining", (unsigned)-1);
|
||||
|
||||
return RC_OK;
|
||||
}
|
||||
|
@ -416,67 +473,79 @@ int rc_api_init_submit_lboard_entry_request(rc_api_request_t* request, const rc_
|
|||
rc_url_builder_append_str_param(&builder, "v", buffer);
|
||||
|
||||
request->post_data = rc_url_builder_finalize(&builder);
|
||||
request->content_type = RC_CONTENT_TYPE_URLENCODED;
|
||||
}
|
||||
|
||||
return builder.result;
|
||||
}
|
||||
|
||||
int rc_api_process_submit_lboard_entry_response(rc_api_submit_lboard_entry_response_t* response, const char* server_response) {
|
||||
rc_api_server_response_t response_obj;
|
||||
|
||||
memset(&response_obj, 0, sizeof(response_obj));
|
||||
response_obj.body = server_response;
|
||||
response_obj.body_length = rc_json_get_object_string_length(server_response);
|
||||
|
||||
return rc_api_process_submit_lboard_entry_server_response(response, &response_obj);
|
||||
}
|
||||
|
||||
int rc_api_process_submit_lboard_entry_server_response(rc_api_submit_lboard_entry_response_t* response, const rc_api_server_response_t* server_response) {
|
||||
rc_api_lboard_entry_t* entry;
|
||||
rc_json_field_t iterator;
|
||||
rc_json_field_t array_field;
|
||||
rc_json_iterator_t iterator;
|
||||
const char* str;
|
||||
int result;
|
||||
|
||||
rc_json_field_t fields[] = {
|
||||
{"Success"},
|
||||
{"Error"},
|
||||
{"Response"} /* nested object */
|
||||
RC_JSON_NEW_FIELD("Success"),
|
||||
RC_JSON_NEW_FIELD("Error"),
|
||||
RC_JSON_NEW_FIELD("Response") /* nested object */
|
||||
};
|
||||
|
||||
rc_json_field_t response_fields[] = {
|
||||
{"Score"},
|
||||
{"BestScore"},
|
||||
{"RankInfo"}, /* nested object */
|
||||
{"TopEntries"} /* array */
|
||||
RC_JSON_NEW_FIELD("Score"),
|
||||
RC_JSON_NEW_FIELD("BestScore"),
|
||||
RC_JSON_NEW_FIELD("RankInfo"), /* nested object */
|
||||
RC_JSON_NEW_FIELD("TopEntries") /* array */
|
||||
/* unused fields
|
||||
{"LBData"}, / * array * /
|
||||
{"ScoreFormatted"},
|
||||
{"TopEntriesFriends"}, / * array * /
|
||||
RC_JSON_NEW_FIELD("LBData"), / * array * /
|
||||
RC_JSON_NEW_FIELD("ScoreFormatted"),
|
||||
RC_JSON_NEW_FIELD("TopEntriesFriends") / * array * /
|
||||
* unused fields */
|
||||
};
|
||||
|
||||
/* unused fields
|
||||
rc_json_field_t lbdata_fields[] = {
|
||||
{"Format"},
|
||||
{"LeaderboardID"},
|
||||
{"GameID"},
|
||||
{"Title"},
|
||||
{"LowerIsBetter"}
|
||||
RC_JSON_NEW_FIELD("Format"),
|
||||
RC_JSON_NEW_FIELD("LeaderboardID"),
|
||||
RC_JSON_NEW_FIELD("GameID"),
|
||||
RC_JSON_NEW_FIELD("Title"),
|
||||
RC_JSON_NEW_FIELD("LowerIsBetter")
|
||||
};
|
||||
* unused fields */
|
||||
|
||||
rc_json_field_t entry_fields[] = {
|
||||
{"User"},
|
||||
{"Rank"},
|
||||
{"Score"}
|
||||
RC_JSON_NEW_FIELD("User"),
|
||||
RC_JSON_NEW_FIELD("Rank"),
|
||||
RC_JSON_NEW_FIELD("Score")
|
||||
/* unused fields
|
||||
{"DateSubmitted"},
|
||||
RC_JSON_NEW_FIELD("DateSubmitted")
|
||||
* unused fields */
|
||||
};
|
||||
|
||||
rc_json_field_t rank_info_fields[] = {
|
||||
{"Rank"},
|
||||
{"NumEntries"}
|
||||
RC_JSON_NEW_FIELD("Rank"),
|
||||
RC_JSON_NEW_FIELD("NumEntries")
|
||||
/* unused fields
|
||||
{"LowerIsBetter"},
|
||||
{"UserRank"},
|
||||
RC_JSON_NEW_FIELD("LowerIsBetter"),
|
||||
RC_JSON_NEW_FIELD("UserRank")
|
||||
* unused fields */
|
||||
};
|
||||
|
||||
memset(response, 0, sizeof(*response));
|
||||
rc_buf_init(&response->response.buffer);
|
||||
|
||||
result = rc_json_parse_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0]));
|
||||
result = rc_json_parse_server_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0]));
|
||||
if (result != RC_OK || !response->response.succeeded)
|
||||
return result;
|
||||
|
||||
|
@ -495,7 +564,7 @@ int rc_api_process_submit_lboard_entry_response(rc_api_submit_lboard_entry_respo
|
|||
return RC_MISSING_VALUE;
|
||||
response->num_entries = (unsigned)atoi(str);
|
||||
|
||||
if (!rc_json_get_required_array(&response->num_top_entries, &iterator, &response->response, &response_fields[3], "TopEntries"))
|
||||
if (!rc_json_get_required_array(&response->num_top_entries, &array_field, &response->response, &response_fields[3], "TopEntries"))
|
||||
return RC_MISSING_VALUE;
|
||||
|
||||
if (response->num_top_entries) {
|
||||
|
@ -503,6 +572,10 @@ int rc_api_process_submit_lboard_entry_response(rc_api_submit_lboard_entry_respo
|
|||
if (!response->top_entries)
|
||||
return RC_OUT_OF_MEMORY;
|
||||
|
||||
memset(&iterator, 0, sizeof(iterator));
|
||||
iterator.json = array_field.value_start;
|
||||
iterator.end = array_field.value_end;
|
||||
|
||||
entry = response->top_entries;
|
||||
while (rc_json_get_array_entry_object(entry_fields, sizeof(entry_fields) / sizeof(entry_fields[0]), &iterator)) {
|
||||
if (!rc_json_get_required_string(&entry->username, &response->response, &entry_fields[0], "User"))
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
#include "rc_api_user.h"
|
||||
#include "rc_api_common.h"
|
||||
|
||||
#include "../rcheevos/rc_version.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
/* --- Login --- */
|
||||
|
@ -25,26 +27,38 @@ int rc_api_init_login_request(rc_api_request_t* request, const rc_api_login_requ
|
|||
return RC_INVALID_STATE;
|
||||
|
||||
request->post_data = rc_url_builder_finalize(&builder);
|
||||
request->content_type = RC_CONTENT_TYPE_URLENCODED;
|
||||
|
||||
return builder.result;
|
||||
}
|
||||
|
||||
int rc_api_process_login_response(rc_api_login_response_t* response, const char* server_response) {
|
||||
rc_api_server_response_t response_obj;
|
||||
|
||||
memset(&response_obj, 0, sizeof(response_obj));
|
||||
response_obj.body = server_response;
|
||||
response_obj.body_length = rc_json_get_object_string_length(server_response);
|
||||
|
||||
return rc_api_process_login_server_response(response, &response_obj);
|
||||
}
|
||||
|
||||
int rc_api_process_login_server_response(rc_api_login_response_t* response, const rc_api_server_response_t* server_response) {
|
||||
int result;
|
||||
rc_json_field_t fields[] = {
|
||||
{"Success"},
|
||||
{"Error"},
|
||||
{"User"},
|
||||
{"Token"},
|
||||
{"Score"},
|
||||
{"Messages"},
|
||||
{"DisplayName"}
|
||||
RC_JSON_NEW_FIELD("Success"),
|
||||
RC_JSON_NEW_FIELD("Error"),
|
||||
RC_JSON_NEW_FIELD("User"),
|
||||
RC_JSON_NEW_FIELD("Token"),
|
||||
RC_JSON_NEW_FIELD("Score"),
|
||||
RC_JSON_NEW_FIELD("SoftcoreScore"),
|
||||
RC_JSON_NEW_FIELD("Messages"),
|
||||
RC_JSON_NEW_FIELD("DisplayName")
|
||||
};
|
||||
|
||||
memset(response, 0, sizeof(*response));
|
||||
rc_buf_init(&response->response.buffer);
|
||||
|
||||
result = rc_json_parse_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0]));
|
||||
result = rc_json_parse_server_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0]));
|
||||
if (result != RC_OK || !response->response.succeeded)
|
||||
return result;
|
||||
|
||||
|
@ -54,9 +68,10 @@ int rc_api_process_login_response(rc_api_login_response_t* response, const char*
|
|||
return RC_MISSING_VALUE;
|
||||
|
||||
rc_json_get_optional_unum(&response->score, &fields[4], "Score", 0);
|
||||
rc_json_get_optional_unum(&response->num_unread_messages, &fields[5], "Messages", 0);
|
||||
rc_json_get_optional_unum(&response->score_softcore, &fields[5], "SoftcoreScore", 0);
|
||||
rc_json_get_optional_unum(&response->num_unread_messages, &fields[6], "Messages", 0);
|
||||
|
||||
rc_json_get_optional_string(&response->display_name, &response->response, &fields[6], "DisplayName", response->username);
|
||||
rc_json_get_optional_string(&response->display_name, &response->response, &fields[7], "DisplayName", response->username);
|
||||
|
||||
return RC_OK;
|
||||
}
|
||||
|
@ -86,22 +101,34 @@ int rc_api_init_start_session_request(rc_api_request_t* request, const rc_api_st
|
|||
*/
|
||||
rc_url_builder_append_unum_param(&builder, "a", 3);
|
||||
rc_url_builder_append_unum_param(&builder, "m", api_params->game_id);
|
||||
rc_url_builder_append_str_param(&builder, "l", RCHEEVOS_VERSION_STRING);
|
||||
request->post_data = rc_url_builder_finalize(&builder);
|
||||
request->content_type = RC_CONTENT_TYPE_URLENCODED;
|
||||
}
|
||||
|
||||
return builder.result;
|
||||
}
|
||||
|
||||
int rc_api_process_start_session_response(rc_api_start_session_response_t* response, const char* server_response) {
|
||||
rc_api_server_response_t response_obj;
|
||||
|
||||
memset(&response_obj, 0, sizeof(response_obj));
|
||||
response_obj.body = server_response;
|
||||
response_obj.body_length = rc_json_get_object_string_length(server_response);
|
||||
|
||||
return rc_api_process_start_session_server_response(response, &response_obj);
|
||||
}
|
||||
|
||||
int rc_api_process_start_session_server_response(rc_api_start_session_response_t* response, const rc_api_server_response_t* server_response) {
|
||||
rc_json_field_t fields[] = {
|
||||
{"Success"},
|
||||
{"Error"}
|
||||
RC_JSON_NEW_FIELD("Success"),
|
||||
RC_JSON_NEW_FIELD("Error")
|
||||
};
|
||||
|
||||
memset(response, 0, sizeof(*response));
|
||||
rc_buf_init(&response->response.buffer);
|
||||
|
||||
return rc_json_parse_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0]));
|
||||
return rc_json_parse_server_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0]));
|
||||
}
|
||||
|
||||
void rc_api_destroy_start_session_response(rc_api_start_session_response_t* response) {
|
||||
|
@ -120,27 +147,38 @@ int rc_api_init_fetch_user_unlocks_request(rc_api_request_t* request, const rc_a
|
|||
rc_url_builder_append_unum_param(&builder, "g", api_params->game_id);
|
||||
rc_url_builder_append_unum_param(&builder, "h", api_params->hardcore ? 1 : 0);
|
||||
request->post_data = rc_url_builder_finalize(&builder);
|
||||
request->content_type = RC_CONTENT_TYPE_URLENCODED;
|
||||
}
|
||||
|
||||
return builder.result;
|
||||
}
|
||||
|
||||
int rc_api_process_fetch_user_unlocks_response(rc_api_fetch_user_unlocks_response_t* response, const char* server_response) {
|
||||
rc_api_server_response_t response_obj;
|
||||
|
||||
memset(&response_obj, 0, sizeof(response_obj));
|
||||
response_obj.body = server_response;
|
||||
response_obj.body_length = rc_json_get_object_string_length(server_response);
|
||||
|
||||
return rc_api_process_fetch_user_unlocks_server_response(response, &response_obj);
|
||||
}
|
||||
|
||||
int rc_api_process_fetch_user_unlocks_server_response(rc_api_fetch_user_unlocks_response_t* response, const rc_api_server_response_t* server_response) {
|
||||
int result;
|
||||
rc_json_field_t fields[] = {
|
||||
{"Success"},
|
||||
{"Error"},
|
||||
{"UserUnlocks"}
|
||||
RC_JSON_NEW_FIELD("Success"),
|
||||
RC_JSON_NEW_FIELD("Error"),
|
||||
RC_JSON_NEW_FIELD("UserUnlocks")
|
||||
/* unused fields
|
||||
{ "GameID" },
|
||||
{ "HardcoreMode" }
|
||||
RC_JSON_NEW_FIELD("GameID"),
|
||||
RC_JSON_NEW_FIELD("HardcoreMode")
|
||||
* unused fields */
|
||||
};
|
||||
|
||||
memset(response, 0, sizeof(*response));
|
||||
rc_buf_init(&response->response.buffer);
|
||||
|
||||
result = rc_json_parse_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0]));
|
||||
result = rc_json_parse_server_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0]));
|
||||
if (result != RC_OK || !response->response.succeeded)
|
||||
return result;
|
||||
|
||||
|
|
|
@ -159,6 +159,17 @@ void rc_destroy_parse_state(rc_parse_state_t* parse)
|
|||
}
|
||||
}
|
||||
|
||||
unsigned rc_djb2(const char* input)
|
||||
{
|
||||
unsigned result = 5381;
|
||||
char c;
|
||||
|
||||
while ((c = *input++) != '\0')
|
||||
result = ((result << 5) + result) + c; /* result = result * 33 + c */
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
const char* rc_error_str(int ret)
|
||||
{
|
||||
switch (ret) {
|
||||
|
@ -189,7 +200,12 @@ const char* rc_error_str(int ret)
|
|||
case RC_INVALID_COMPARISON: return "Invalid comparison";
|
||||
case RC_INVALID_STATE: return "Invalid state";
|
||||
case RC_INVALID_JSON: return "Invalid JSON";
|
||||
|
||||
case RC_API_FAILURE: return "API call failed";
|
||||
case RC_LOGIN_REQUIRED: return "Login required";
|
||||
case RC_NO_GAME_LOADED: return "No game loaded";
|
||||
case RC_HARDCORE_DISABLED: return "Hardcore disabled";
|
||||
case RC_ABORTED: return "Aborted";
|
||||
case RC_NO_RESPONSE: return "No response";
|
||||
default: return "Unknown error";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,8 @@
|
|||
#include <ctype.h>
|
||||
#include <stdarg.h>
|
||||
|
||||
#ifdef RC_C89_HELPERS
|
||||
|
||||
int rc_strncasecmp(const char* left, const char* right, size_t length)
|
||||
{
|
||||
while (length)
|
||||
|
@ -44,20 +46,94 @@ char* rc_strdup(const char* str)
|
|||
{
|
||||
const size_t length = strlen(str);
|
||||
char* buffer = (char*)malloc(length + 1);
|
||||
memcpy(buffer, str, length + 1);
|
||||
if (buffer)
|
||||
memcpy(buffer, str, length + 1);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
int rc_snprintf(char* buffer, size_t size, const char* format, ...)
|
||||
{
|
||||
int result;
|
||||
va_list args;
|
||||
int result;
|
||||
va_list args;
|
||||
|
||||
va_start(args, format);
|
||||
/* assume buffer is large enough and ignore size */
|
||||
(void)size;
|
||||
result = vsprintf(buffer, format, args);
|
||||
va_end(args);
|
||||
va_start(args, format);
|
||||
|
||||
return result;
|
||||
#ifdef __STDC_WANT_SECURE_LIB__
|
||||
result = vsprintf_s(buffer, size, format, args);
|
||||
#else
|
||||
/* assume buffer is large enough and ignore size */
|
||||
(void)size;
|
||||
result = vsprintf(buffer, format, args);
|
||||
#endif
|
||||
|
||||
va_end(args);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#ifndef __STDC_WANT_SECURE_LIB__
|
||||
|
||||
struct tm* rc_gmtime_s(struct tm* buf, const time_t* timer)
|
||||
{
|
||||
struct tm* tm = gmtime(timer);
|
||||
memcpy(buf, tm, sizeof(*tm));
|
||||
return buf;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#ifndef RC_NO_THREADS
|
||||
#ifdef _WIN32
|
||||
|
||||
/* https://gist.github.com/roxlu/1c1af99f92bafff9d8d9 */
|
||||
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
|
||||
void rc_mutex_init(rc_mutex_t* mutex)
|
||||
{
|
||||
/* default security, not owned by calling thread, unnamed */
|
||||
mutex->handle = CreateMutex(NULL, FALSE, NULL);
|
||||
}
|
||||
|
||||
void rc_mutex_destroy(rc_mutex_t* mutex)
|
||||
{
|
||||
CloseHandle(mutex->handle);
|
||||
}
|
||||
|
||||
void rc_mutex_lock(rc_mutex_t* mutex)
|
||||
{
|
||||
WaitForSingleObject(mutex->handle, 0xFFFFFFFF);
|
||||
}
|
||||
|
||||
void rc_mutex_unlock(rc_mutex_t* mutex)
|
||||
{
|
||||
ReleaseMutex(mutex->handle);
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
void rc_mutex_init(rc_mutex_t* mutex)
|
||||
{
|
||||
pthread_mutex_init(mutex, NULL);
|
||||
}
|
||||
|
||||
void rc_mutex_destroy(rc_mutex_t* mutex)
|
||||
{
|
||||
pthread_mutex_destroy(mutex);
|
||||
}
|
||||
|
||||
void rc_mutex_lock(rc_mutex_t* mutex)
|
||||
{
|
||||
pthread_mutex_lock(mutex);
|
||||
}
|
||||
|
||||
void rc_mutex_unlock(rc_mutex_t* mutex)
|
||||
{
|
||||
pthread_mutex_unlock(mutex);
|
||||
}
|
||||
|
||||
#endif
|
||||
#endif /* RC_NO_THREADS */
|
||||
|
|
|
@ -1,6 +1,91 @@
|
|||
#include "rc_internal.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <assert.h>
|
||||
|
||||
static int rc_test_condition_compare(unsigned value1, unsigned value2, char oper) {
|
||||
switch (oper) {
|
||||
case RC_OPERATOR_EQ: return value1 == value2;
|
||||
case RC_OPERATOR_NE: return value1 != value2;
|
||||
case RC_OPERATOR_LT: return value1 < value2;
|
||||
case RC_OPERATOR_LE: return value1 <= value2;
|
||||
case RC_OPERATOR_GT: return value1 > value2;
|
||||
case RC_OPERATOR_GE: return value1 >= value2;
|
||||
default: return 1;
|
||||
}
|
||||
}
|
||||
|
||||
static char rc_condition_determine_comparator(const rc_condition_t* self) {
|
||||
switch (self->oper) {
|
||||
case RC_OPERATOR_EQ:
|
||||
case RC_OPERATOR_NE:
|
||||
case RC_OPERATOR_LT:
|
||||
case RC_OPERATOR_LE:
|
||||
case RC_OPERATOR_GT:
|
||||
case RC_OPERATOR_GE:
|
||||
break;
|
||||
|
||||
default:
|
||||
/* not a comparison. should not be getting compared. but if it is, legacy behavior was to return 1 */
|
||||
return RC_PROCESSING_COMPARE_ALWAYS_TRUE;
|
||||
}
|
||||
|
||||
if ((self->operand1.type == RC_OPERAND_ADDRESS || self->operand1.type == RC_OPERAND_DELTA) &&
|
||||
!self->operand1.value.memref->value.is_indirect && !rc_operand_is_float(&self->operand1)) {
|
||||
/* left side is an integer memory reference */
|
||||
int needs_translate = (self->operand1.size != self->operand1.value.memref->value.size);
|
||||
|
||||
if (self->operand2.type == RC_OPERAND_CONST) {
|
||||
/* right side is a constant */
|
||||
if (self->operand1.type == RC_OPERAND_ADDRESS)
|
||||
return needs_translate ? RC_PROCESSING_COMPARE_MEMREF_TO_CONST_TRANSFORMED : RC_PROCESSING_COMPARE_MEMREF_TO_CONST;
|
||||
|
||||
return needs_translate ? RC_PROCESSING_COMPARE_DELTA_TO_CONST_TRANSFORMED : RC_PROCESSING_COMPARE_DELTA_TO_CONST;
|
||||
}
|
||||
else if ((self->operand2.type == RC_OPERAND_ADDRESS || self->operand2.type == RC_OPERAND_DELTA) &&
|
||||
!self->operand2.value.memref->value.is_indirect && !rc_operand_is_float(&self->operand2)) {
|
||||
/* right side is an integer memory reference */
|
||||
const int is_same_memref = (self->operand1.value.memref == self->operand2.value.memref);
|
||||
needs_translate |= (self->operand2.size != self->operand2.value.memref->value.size);
|
||||
|
||||
if (self->operand1.type == RC_OPERAND_ADDRESS) {
|
||||
if (self->operand2.type == RC_OPERAND_ADDRESS) {
|
||||
if (is_same_memref && !needs_translate) {
|
||||
/* comparing a memref to itself, will evaluate to a constant */
|
||||
return rc_test_condition_compare(0, 0, self->oper) ? RC_PROCESSING_COMPARE_ALWAYS_TRUE : RC_PROCESSING_COMPARE_ALWAYS_FALSE;
|
||||
}
|
||||
|
||||
return needs_translate ? RC_PROCESSING_COMPARE_MEMREF_TO_MEMREF_TRANSFORMED : RC_PROCESSING_COMPARE_MEMREF_TO_MEMREF;
|
||||
}
|
||||
|
||||
assert(self->operand2.type == RC_OPERAND_DELTA);
|
||||
|
||||
if (is_same_memref) {
|
||||
/* delta comparison is optimized to compare with itself (for detecting change) */
|
||||
return needs_translate ? RC_PROCESSING_COMPARE_MEMREF_TO_DELTA_TRANSFORMED : RC_PROCESSING_COMPARE_MEMREF_TO_DELTA;
|
||||
}
|
||||
}
|
||||
else {
|
||||
assert(self->operand1.type == RC_OPERAND_DELTA);
|
||||
|
||||
if (self->operand2.type == RC_OPERAND_ADDRESS) {
|
||||
if (is_same_memref) {
|
||||
/* delta comparison is optimized to compare with itself (for detecting change) */
|
||||
return needs_translate ? RC_PROCESSING_COMPARE_DELTA_TO_MEMREF_TRANSFORMED : RC_PROCESSING_COMPARE_DELTA_TO_MEMREF;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (self->operand1.type == RC_OPERAND_CONST && self->operand2.type == RC_OPERAND_CONST) {
|
||||
/* comparing constants will always generate a constant result */
|
||||
return rc_test_condition_compare(self->operand1.value.num, self->operand2.value.num, self->oper) ?
|
||||
RC_PROCESSING_COMPARE_ALWAYS_TRUE : RC_PROCESSING_COMPARE_ALWAYS_FALSE;
|
||||
}
|
||||
|
||||
return RC_PROCESSING_COMPARE_DEFAULT;
|
||||
}
|
||||
|
||||
static int rc_parse_operator(const char** memaddr) {
|
||||
const char* oper = *memaddr;
|
||||
|
@ -50,6 +135,10 @@ static int rc_parse_operator(const char** memaddr) {
|
|||
++(*memaddr);
|
||||
return RC_OPERATOR_AND;
|
||||
|
||||
case '^':
|
||||
++(*memaddr);
|
||||
return RC_OPERATOR_XOR;
|
||||
|
||||
case '\0':/* end of string */
|
||||
case '_': /* next condition */
|
||||
case 'S': /* next condset */
|
||||
|
@ -71,6 +160,7 @@ rc_condition_t* rc_parse_condition(const char** memaddr, rc_parse_state_t* parse
|
|||
self->current_hits = 0;
|
||||
self->is_true = 0;
|
||||
self->pause = 0;
|
||||
self->optimized_comparator = RC_PROCESSING_COMPARE_DEFAULT;
|
||||
|
||||
if (*aux != 0 && aux[1] == ':') {
|
||||
switch (*aux) {
|
||||
|
@ -135,6 +225,7 @@ rc_condition_t* rc_parse_condition(const char** memaddr, rc_parse_state_t* parse
|
|||
case RC_OPERATOR_MULT:
|
||||
case RC_OPERATOR_DIV:
|
||||
case RC_OPERATOR_AND:
|
||||
case RC_OPERATOR_XOR:
|
||||
/* modifying operators are only valid on modifying statements */
|
||||
if (can_modify)
|
||||
break;
|
||||
|
@ -209,6 +300,9 @@ rc_condition_t* rc_parse_condition(const char** memaddr, rc_parse_state_t* parse
|
|||
self->required_hits = 0;
|
||||
}
|
||||
|
||||
if (parse->buffer != 0)
|
||||
self->optimized_comparator = rc_condition_determine_comparator(self);
|
||||
|
||||
*memaddr = aux;
|
||||
return self;
|
||||
}
|
||||
|
@ -228,12 +322,203 @@ int rc_condition_is_combining(const rc_condition_t* self) {
|
|||
}
|
||||
}
|
||||
|
||||
static int rc_test_condition_compare_memref_to_const(rc_condition_t* self) {
|
||||
const unsigned value1 = self->operand1.value.memref->value.value;
|
||||
const unsigned value2 = self->operand2.value.num;
|
||||
assert(self->operand1.size == self->operand1.value.memref->value.size);
|
||||
return rc_test_condition_compare(value1, value2, self->oper);
|
||||
}
|
||||
|
||||
static int rc_test_condition_compare_delta_to_const(rc_condition_t* self) {
|
||||
const rc_memref_value_t* memref1 = &self->operand1.value.memref->value;
|
||||
const unsigned value1 = (memref1->changed) ? memref1->prior : memref1->value;
|
||||
const unsigned value2 = self->operand2.value.num;
|
||||
assert(self->operand1.size == self->operand1.value.memref->value.size);
|
||||
return rc_test_condition_compare(value1, value2, self->oper);
|
||||
}
|
||||
|
||||
static int rc_test_condition_compare_memref_to_memref(rc_condition_t* self) {
|
||||
const unsigned value1 = self->operand1.value.memref->value.value;
|
||||
const unsigned value2 = self->operand2.value.memref->value.value;
|
||||
assert(self->operand1.size == self->operand1.value.memref->value.size);
|
||||
assert(self->operand2.size == self->operand2.value.memref->value.size);
|
||||
return rc_test_condition_compare(value1, value2, self->oper);
|
||||
}
|
||||
|
||||
static int rc_test_condition_compare_memref_to_delta(rc_condition_t* self) {
|
||||
const rc_memref_value_t* memref = &self->operand1.value.memref->value;
|
||||
assert(self->operand1.value.memref == self->operand2.value.memref);
|
||||
assert(self->operand1.size == self->operand1.value.memref->value.size);
|
||||
assert(self->operand2.size == self->operand2.value.memref->value.size);
|
||||
|
||||
if (memref->changed)
|
||||
return rc_test_condition_compare(memref->value, memref->prior, self->oper);
|
||||
|
||||
switch (self->oper) {
|
||||
case RC_OPERATOR_EQ:
|
||||
case RC_OPERATOR_GE:
|
||||
case RC_OPERATOR_LE:
|
||||
return 1;
|
||||
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static int rc_test_condition_compare_delta_to_memref(rc_condition_t* self) {
|
||||
const rc_memref_value_t* memref = &self->operand1.value.memref->value;
|
||||
assert(self->operand1.value.memref == self->operand2.value.memref);
|
||||
assert(self->operand1.size == self->operand1.value.memref->value.size);
|
||||
assert(self->operand2.size == self->operand2.value.memref->value.size);
|
||||
|
||||
if (memref->changed)
|
||||
return rc_test_condition_compare(memref->prior, memref->value, self->oper);
|
||||
|
||||
switch (self->oper) {
|
||||
case RC_OPERATOR_EQ:
|
||||
case RC_OPERATOR_GE:
|
||||
case RC_OPERATOR_LE:
|
||||
return 1;
|
||||
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static int rc_test_condition_compare_memref_to_const_transformed(rc_condition_t* self) {
|
||||
rc_typed_value_t value1;
|
||||
const unsigned value2 = self->operand2.value.num;
|
||||
|
||||
value1.type = RC_VALUE_TYPE_UNSIGNED;
|
||||
value1.value.u32 = self->operand1.value.memref->value.value;
|
||||
rc_transform_memref_value(&value1, self->operand1.size);
|
||||
|
||||
return rc_test_condition_compare(value1.value.u32, value2, self->oper);
|
||||
}
|
||||
|
||||
static int rc_test_condition_compare_delta_to_const_transformed(rc_condition_t* self) {
|
||||
rc_typed_value_t value1;
|
||||
const rc_memref_value_t* memref1 = &self->operand1.value.memref->value;
|
||||
const unsigned value2 = self->operand2.value.num;
|
||||
|
||||
value1.type = RC_VALUE_TYPE_UNSIGNED;
|
||||
value1.value.u32 = (memref1->changed) ? memref1->prior : memref1->value;
|
||||
rc_transform_memref_value(&value1, self->operand1.size);
|
||||
|
||||
return rc_test_condition_compare(value1.value.u32, value2, self->oper);
|
||||
}
|
||||
|
||||
static int rc_test_condition_compare_memref_to_memref_transformed(rc_condition_t* self) {
|
||||
rc_typed_value_t value1, value2;
|
||||
|
||||
value1.type = RC_VALUE_TYPE_UNSIGNED;
|
||||
value1.value.u32 = self->operand1.value.memref->value.value;
|
||||
rc_transform_memref_value(&value1, self->operand1.size);
|
||||
|
||||
value2.type = RC_VALUE_TYPE_UNSIGNED;
|
||||
value2.value.u32 = self->operand2.value.memref->value.value;
|
||||
rc_transform_memref_value(&value2, self->operand2.size);
|
||||
|
||||
return rc_test_condition_compare(value1.value.u32, value2.value.u32, self->oper);
|
||||
}
|
||||
|
||||
static int rc_test_condition_compare_memref_to_delta_transformed(rc_condition_t* self) {
|
||||
const rc_memref_value_t* memref = &self->operand1.value.memref->value;
|
||||
assert(self->operand1.value.memref == self->operand2.value.memref);
|
||||
|
||||
if (memref->changed) {
|
||||
rc_typed_value_t value1, value2;
|
||||
|
||||
value1.type = RC_VALUE_TYPE_UNSIGNED;
|
||||
value1.value.u32 = memref->value;
|
||||
rc_transform_memref_value(&value1, self->operand1.size);
|
||||
|
||||
value2.type = RC_VALUE_TYPE_UNSIGNED;
|
||||
value2.value.u32 = memref->prior;
|
||||
rc_transform_memref_value(&value2, self->operand2.size);
|
||||
|
||||
return rc_test_condition_compare(value1.value.u32, value2.value.u32, self->oper);
|
||||
}
|
||||
|
||||
switch (self->oper) {
|
||||
case RC_OPERATOR_EQ:
|
||||
case RC_OPERATOR_GE:
|
||||
case RC_OPERATOR_LE:
|
||||
return 1;
|
||||
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static int rc_test_condition_compare_delta_to_memref_transformed(rc_condition_t* self) {
|
||||
const rc_memref_value_t* memref = &self->operand1.value.memref->value;
|
||||
assert(self->operand1.value.memref == self->operand2.value.memref);
|
||||
|
||||
if (memref->changed) {
|
||||
rc_typed_value_t value1, value2;
|
||||
|
||||
value1.type = RC_VALUE_TYPE_UNSIGNED;
|
||||
value1.value.u32 = memref->prior;
|
||||
rc_transform_memref_value(&value1, self->operand1.size);
|
||||
|
||||
value2.type = RC_VALUE_TYPE_UNSIGNED;
|
||||
value2.value.u32 = memref->value;
|
||||
rc_transform_memref_value(&value2, self->operand2.size);
|
||||
|
||||
return rc_test_condition_compare(value1.value.u32, value2.value.u32, self->oper);
|
||||
}
|
||||
|
||||
switch (self->oper) {
|
||||
case RC_OPERATOR_EQ:
|
||||
case RC_OPERATOR_GE:
|
||||
case RC_OPERATOR_LE:
|
||||
return 1;
|
||||
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
int rc_test_condition(rc_condition_t* self, rc_eval_state_t* eval_state) {
|
||||
rc_typed_value_t value1, value2;
|
||||
|
||||
rc_evaluate_operand(&value1, &self->operand1, eval_state);
|
||||
if (eval_state->add_value.type != RC_VALUE_TYPE_NONE)
|
||||
if (eval_state->add_value.type != RC_VALUE_TYPE_NONE) {
|
||||
/* if there's an accumulator, we can't use the optimized comparators */
|
||||
rc_evaluate_operand(&value1, &self->operand1, eval_state);
|
||||
rc_typed_value_add(&value1, &eval_state->add_value);
|
||||
} else {
|
||||
/* use an optimized comparator whenever possible */
|
||||
switch (self->optimized_comparator) {
|
||||
case RC_PROCESSING_COMPARE_MEMREF_TO_CONST:
|
||||
return rc_test_condition_compare_memref_to_const(self);
|
||||
case RC_PROCESSING_COMPARE_MEMREF_TO_DELTA:
|
||||
return rc_test_condition_compare_memref_to_delta(self);
|
||||
case RC_PROCESSING_COMPARE_MEMREF_TO_MEMREF:
|
||||
return rc_test_condition_compare_memref_to_memref(self);
|
||||
case RC_PROCESSING_COMPARE_DELTA_TO_CONST:
|
||||
return rc_test_condition_compare_delta_to_const(self);
|
||||
case RC_PROCESSING_COMPARE_DELTA_TO_MEMREF:
|
||||
return rc_test_condition_compare_delta_to_memref(self);
|
||||
case RC_PROCESSING_COMPARE_MEMREF_TO_CONST_TRANSFORMED:
|
||||
return rc_test_condition_compare_memref_to_const_transformed(self);
|
||||
case RC_PROCESSING_COMPARE_MEMREF_TO_DELTA_TRANSFORMED:
|
||||
return rc_test_condition_compare_memref_to_delta_transformed(self);
|
||||
case RC_PROCESSING_COMPARE_MEMREF_TO_MEMREF_TRANSFORMED:
|
||||
return rc_test_condition_compare_memref_to_memref_transformed(self);
|
||||
case RC_PROCESSING_COMPARE_DELTA_TO_CONST_TRANSFORMED:
|
||||
return rc_test_condition_compare_delta_to_const_transformed(self);
|
||||
case RC_PROCESSING_COMPARE_DELTA_TO_MEMREF_TRANSFORMED:
|
||||
return rc_test_condition_compare_delta_to_memref_transformed(self);
|
||||
case RC_PROCESSING_COMPARE_ALWAYS_TRUE:
|
||||
return 1;
|
||||
case RC_PROCESSING_COMPARE_ALWAYS_FALSE:
|
||||
return 0;
|
||||
default:
|
||||
rc_evaluate_operand(&value1, &self->operand1, eval_state);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
rc_evaluate_operand(&value2, &self->operand2, eval_state);
|
||||
|
||||
|
@ -260,5 +545,11 @@ void rc_evaluate_condition_value(rc_typed_value_t* value, rc_condition_t* self,
|
|||
rc_typed_value_convert(&amount, RC_VALUE_TYPE_UNSIGNED);
|
||||
value->value.u32 &= amount.value.u32;
|
||||
break;
|
||||
|
||||
case RC_OPERATOR_XOR:
|
||||
rc_typed_value_convert(value, RC_VALUE_TYPE_UNSIGNED);
|
||||
rc_typed_value_convert(&amount, RC_VALUE_TYPE_UNSIGNED);
|
||||
value->value.u32 ^= amount.value.u32;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -84,6 +84,7 @@ rc_condset_t* rc_parse_condset(const char** memaddr, rc_parse_state_t* parse, in
|
|||
switch ((*next)->oper)
|
||||
{
|
||||
case RC_OPERATOR_AND:
|
||||
case RC_OPERATOR_XOR:
|
||||
case RC_OPERATOR_DIV:
|
||||
case RC_OPERATOR_MULT:
|
||||
case RC_OPERATOR_NONE:
|
||||
|
@ -209,8 +210,7 @@ static int rc_test_condset_internal(rc_condset_t* self, int processing_pause, rc
|
|||
|
||||
case RC_CONDITION_SUB_SOURCE:
|
||||
rc_evaluate_condition_value(&value, condition, eval_state);
|
||||
rc_typed_value_convert(&value, RC_VALUE_TYPE_SIGNED);
|
||||
value.value.i32 = -value.value.i32;
|
||||
rc_typed_value_negate(&value);
|
||||
rc_typed_value_add(&eval_state->add_value, &value);
|
||||
eval_state->add_address = 0;
|
||||
continue;
|
||||
|
|
|
@ -39,6 +39,9 @@ const char* rc_console_name(int console_id)
|
|||
case RC_CONSOLE_ATARI_JAGUAR:
|
||||
return "Atari Jaguar";
|
||||
|
||||
case RC_CONSOLE_ATARI_JAGUAR_CD:
|
||||
return "Atari Jaguar CD";
|
||||
|
||||
case RC_CONSOLE_ATARI_LYNX:
|
||||
return "Atari Lynx";
|
||||
|
||||
|
@ -132,6 +135,9 @@ const char* rc_console_name(int console_id)
|
|||
case RC_CONSOLE_NINTENDO_DS:
|
||||
return "Nintendo DS";
|
||||
|
||||
case RC_CONSOLE_NINTENDO_DSI:
|
||||
return "Nintendo DSi";
|
||||
|
||||
case RC_CONSOLE_NINTENDO_3DS:
|
||||
return "Nintendo 3DS";
|
||||
|
||||
|
@ -156,6 +162,9 @@ const char* rc_console_name(int console_id)
|
|||
case RC_CONSOLE_PC_ENGINE:
|
||||
return "PC Engine";
|
||||
|
||||
case RC_CONSOLE_PC_ENGINE_CD:
|
||||
return "PC Engine CD";
|
||||
|
||||
case RC_CONSOLE_PLAYSTATION:
|
||||
return "PlayStation";
|
||||
|
||||
|
@ -198,9 +207,15 @@ const char* rc_console_name(int console_id)
|
|||
case RC_CONSOLE_THOMSONTO8:
|
||||
return "Thomson TO8";
|
||||
|
||||
case RC_CONSOLE_TI83:
|
||||
return "TI-83";
|
||||
|
||||
case RC_CONSOLE_TIC80:
|
||||
return "TIC-80";
|
||||
|
||||
case RC_CONSOLE_UZEBOX:
|
||||
return "Uzebox";
|
||||
|
||||
case RC_CONSOLE_VECTREX:
|
||||
return "Vectrex";
|
||||
|
||||
|
@ -435,6 +450,13 @@ static const rc_memory_region_t _rc_memory_regions_gameboy_advance[] = {
|
|||
};
|
||||
static const rc_memory_regions_t rc_memory_regions_gameboy_advance = { _rc_memory_regions_gameboy_advance, 2 };
|
||||
|
||||
/* ===== GameCube ===== */
|
||||
/* https://wiibrew.org/wiki/Memory_map */
|
||||
static const rc_memory_region_t _rc_memory_regions_gamecube[] = {
|
||||
{ 0x00000000U, 0x017FFFFF, 0x80000000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" }
|
||||
};
|
||||
static const rc_memory_regions_t rc_memory_regions_gamecube = { _rc_memory_regions_gamecube, 1 };
|
||||
|
||||
/* ===== Game Gear ===== */
|
||||
/* http://www.smspower.org/Development/MemoryMap */
|
||||
static const rc_memory_region_t _rc_memory_regions_game_gear[] = {
|
||||
|
@ -518,6 +540,15 @@ static const rc_memory_region_t _rc_memory_regions_megadrive[] = {
|
|||
};
|
||||
static const rc_memory_regions_t rc_memory_regions_megadrive = { _rc_memory_regions_megadrive, 2 };
|
||||
|
||||
/* ===== MegaDrive 32X (Genesis 32X) ===== */
|
||||
/* http://devster.monkeeh.com/sega/32xguide1.txt */
|
||||
static const rc_memory_region_t _rc_memory_regions_megadrive_32x[] = {
|
||||
{ 0x000000U, 0x00FFFFU, 0x00FF0000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" }, /* Main MegaDrive RAM */
|
||||
{ 0x010000U, 0x04FFFFU, 0x06000000U, RC_MEMORY_TYPE_SYSTEM_RAM, "32X RAM"}, /* Additional 32X RAM */
|
||||
{ 0x050000U, 0x05FFFFU, 0x00000000U, RC_MEMORY_TYPE_SAVE_RAM, "Cartridge RAM" }
|
||||
};
|
||||
static const rc_memory_regions_t rc_memory_regions_megadrive_32x = { _rc_memory_regions_megadrive_32x, 3 };
|
||||
|
||||
/* ===== MSX ===== */
|
||||
/* https://www.msx.org/wiki/The_Memory */
|
||||
/* MSX only has 64KB of addressable RAM, of which 32KB is reserved for the system/BIOS.
|
||||
|
@ -540,6 +571,35 @@ static const rc_memory_region_t _rc_memory_regions_neo_geo_pocket[] = {
|
|||
};
|
||||
static const rc_memory_regions_t rc_memory_regions_neo_geo_pocket = { _rc_memory_regions_neo_geo_pocket, 1 };
|
||||
|
||||
/* ===== Neo Geo CD ===== */
|
||||
/* https://wiki.neogeodev.org/index.php?title=68k_memory_map */
|
||||
/* NeoCD exposes $000000-$1FFFFF as System RAM, but it seems like only the WORKRAM section is used.
|
||||
* This is consistent with http://www.hardmvs.fr/manuals/NeoGeoProgrammersGuide.pdf (page25), which says:
|
||||
*
|
||||
* Furthermore, the NEO-GEO provides addresses 100000H-10FFFFH as a work area, out of which the
|
||||
* addresses 10F300H-10FFFFH are reserved exclusively for use by the system program. Therefore,
|
||||
* every game is to use addresses 100000H-10F2FFH.
|
||||
*
|
||||
* Also note that PRG files (game ROM) can be loaded anywhere else in the $000000-$1FFFFF range.
|
||||
* AoF3 illustrates this pretty clearly: https://wiki.neogeodev.org/index.php?title=IPL_file
|
||||
*
|
||||
* PROG_CD.PRG,0,0
|
||||
* PROG_CDX.PRG,0,058000
|
||||
* CNV_NM.PRG,0,0C0000
|
||||
* FIX_DATA.PRG,0,0FD000
|
||||
* OBJACTLK.PRG,0,130000
|
||||
* SSEL_CNV.PRG,0,15A000
|
||||
* SSEL_BAK.PRG,0,16F000
|
||||
* HITMSG.PRG,0,170000
|
||||
* SSEL_SPR.PRG,0,19D000
|
||||
*/
|
||||
static const rc_memory_region_t _rc_memory_regions_neo_geo_cd[] = {
|
||||
{ 0x000000U, 0x00F2FFU, 0x00100000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" },
|
||||
/* NOTE: some BIOS settings are exposed through the reserved RAM: https://wiki.neogeodev.org/index.php?title=68k_ASM_defines */
|
||||
{ 0x00F300U, 0x00FFFFU, 0x0010F300U, RC_MEMORY_TYPE_SYSTEM_RAM, "Reserved RAM" },
|
||||
};
|
||||
static const rc_memory_regions_t rc_memory_regions_neo_geo_cd = { _rc_memory_regions_neo_geo_cd, 2 };
|
||||
|
||||
/* ===== Nintendo Entertainment System ===== */
|
||||
/* https://wiki.nesdev.com/w/index.php/CPU_memory_map */
|
||||
static const rc_memory_region_t _rc_memory_regions_nes[] = {
|
||||
|
@ -587,6 +647,13 @@ static const rc_memory_region_t _rc_memory_regions_nintendo_ds[] = {
|
|||
};
|
||||
static const rc_memory_regions_t rc_memory_regions_nintendo_ds = { _rc_memory_regions_nintendo_ds, 1 };
|
||||
|
||||
/* ===== Nintendo DSi ===== */
|
||||
/* https://problemkaputt.de/gbatek.htm#dsiiomap */
|
||||
static const rc_memory_region_t _rc_memory_regions_nintendo_dsi[] = {
|
||||
{ 0x000000U, 0xFFFFFFU, 0x02000000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" }
|
||||
};
|
||||
static const rc_memory_regions_t rc_memory_regions_nintendo_dsi = { _rc_memory_regions_nintendo_dsi, 1 };
|
||||
|
||||
/* ===== Oric ===== */
|
||||
static const rc_memory_region_t _rc_memory_regions_oric[] = {
|
||||
/* actual size depends on machine type - up to 64KB */
|
||||
|
@ -605,11 +672,18 @@ static const rc_memory_regions_t rc_memory_regions_pc8800 = { _rc_memory_regions
|
|||
/* http://www.archaicpixels.com/Memory_Map */
|
||||
static const rc_memory_region_t _rc_memory_regions_pc_engine[] = {
|
||||
{ 0x000000U, 0x001FFFU, 0x1F0000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" },
|
||||
};
|
||||
static const rc_memory_regions_t rc_memory_regions_pc_engine = { _rc_memory_regions_pc_engine, 1 };
|
||||
|
||||
/* ===== PC Engine CD===== */
|
||||
/* http://www.archaicpixels.com/Memory_Map */
|
||||
static const rc_memory_region_t _rc_memory_regions_pc_engine_cd[] = {
|
||||
{ 0x000000U, 0x001FFFU, 0x1F0000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" },
|
||||
{ 0x002000U, 0x011FFFU, 0x100000U, RC_MEMORY_TYPE_SYSTEM_RAM, "CD RAM" },
|
||||
{ 0x012000U, 0x041FFFU, 0x0D0000U, RC_MEMORY_TYPE_SYSTEM_RAM, "Super System Card RAM" },
|
||||
{ 0x042000U, 0x0427FFU, 0x1EE000U, RC_MEMORY_TYPE_SAVE_RAM, "CD Battery-backed RAM" }
|
||||
};
|
||||
static const rc_memory_regions_t rc_memory_regions_pc_engine = { _rc_memory_regions_pc_engine, 4 };
|
||||
static const rc_memory_regions_t rc_memory_regions_pc_engine_cd = { _rc_memory_regions_pc_engine_cd, 4 };
|
||||
|
||||
/* ===== PC-FX ===== */
|
||||
/* http://daifukkat.su/pcfx/data/memmap.html */
|
||||
|
@ -711,6 +785,13 @@ static const rc_memory_region_t _rc_memory_regions_thomson_to8[] = {
|
|||
};
|
||||
static const rc_memory_regions_t rc_memory_regions_thomson_to8 = { _rc_memory_regions_thomson_to8, 1 };
|
||||
|
||||
/* ===== TI-83 ===== */
|
||||
/* https://tutorials.eeems.ca/ASMin28Days/lesson/day03.html#mem */
|
||||
static const rc_memory_region_t _rc_memory_regions_ti83[] = {
|
||||
{ 0x000000U, 0x007FFFU, 0x008000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" },
|
||||
};
|
||||
static const rc_memory_regions_t rc_memory_regions_ti83 = { _rc_memory_regions_ti83, 1 };
|
||||
|
||||
/* ===== TIC-80 ===== */
|
||||
/* https://github.com/nesbox/TIC-80/wiki/RAM */
|
||||
static const rc_memory_region_t _rc_memory_regions_tic80[] = {
|
||||
|
@ -727,6 +808,13 @@ static const rc_memory_region_t _rc_memory_regions_tic80[] = {
|
|||
};
|
||||
static const rc_memory_regions_t rc_memory_regions_tic80 = { _rc_memory_regions_tic80, 10 };
|
||||
|
||||
/* ===== Uzebox ===== */
|
||||
/* https://uzebox.org/index.php */
|
||||
static const rc_memory_region_t _rc_memory_regions_uzebox[] = {
|
||||
{ 0x000000U, 0x000FFFU, 0x000000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" }
|
||||
};
|
||||
static const rc_memory_regions_t rc_memory_regions_uzebox = { _rc_memory_regions_uzebox, 1 };
|
||||
|
||||
/* ===== Vectrex ===== */
|
||||
/* https://roadsidethoughts.com/vectrex/vectrex-memory-map.htm */
|
||||
static const rc_memory_region_t _rc_memory_regions_vectrex[] = {
|
||||
|
@ -761,6 +849,14 @@ static const rc_memory_region_t _rc_memory_regions_wasm4[] = {
|
|||
};
|
||||
static const rc_memory_regions_t rc_memory_regions_wasm4 = { _rc_memory_regions_wasm4, 1 };
|
||||
|
||||
/* ===== Wii ===== */
|
||||
/* https://wiibrew.org/wiki/Memory_map */
|
||||
static const rc_memory_region_t _rc_memory_regions_wii[] = {
|
||||
{ 0x00000000U, 0x017FFFFF, 0x80000000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" },
|
||||
{ 0x01800000U, 0x057FFFFF, 0x90000000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" }
|
||||
};
|
||||
static const rc_memory_regions_t rc_memory_regions_wii = { _rc_memory_regions_wii, 2 };
|
||||
|
||||
/* ===== WonderSwan ===== */
|
||||
/* http://daifukkat.su/docs/wsman/#ovr_memmap */
|
||||
static const rc_memory_region_t _rc_memory_regions_wonderswan[] = {
|
||||
|
@ -810,6 +906,7 @@ const rc_memory_regions_t* rc_console_memory_regions(int console_id)
|
|||
return &rc_memory_regions_atari7800;
|
||||
|
||||
case RC_CONSOLE_ATARI_JAGUAR:
|
||||
case RC_CONSOLE_ATARI_JAGUAR_CD:
|
||||
return &rc_memory_regions_atari_jaguar;
|
||||
|
||||
case RC_CONSOLE_ATARI_LYNX:
|
||||
|
@ -840,6 +937,9 @@ const rc_memory_regions_t* rc_console_memory_regions(int console_id)
|
|||
case RC_CONSOLE_GAMEBOY_ADVANCE:
|
||||
return &rc_memory_regions_gameboy_advance;
|
||||
|
||||
case RC_CONSOLE_GAMECUBE:
|
||||
return &rc_memory_regions_gamecube;
|
||||
|
||||
case RC_CONSOLE_GAME_GEAR:
|
||||
return &rc_memory_regions_game_gear;
|
||||
|
||||
|
@ -856,17 +956,20 @@ const rc_memory_regions_t* rc_console_memory_regions(int console_id)
|
|||
return &rc_memory_regions_master_system;
|
||||
|
||||
case RC_CONSOLE_MEGA_DRIVE:
|
||||
case RC_CONSOLE_SEGA_32X:
|
||||
/* NOTE: 32x adds an extra 512KB of memory (256KB RAM + 256KB VRAM) to the
|
||||
* Genesis, but we currently don't support it. */
|
||||
return &rc_memory_regions_megadrive;
|
||||
|
||||
case RC_CONSOLE_SEGA_32X:
|
||||
return &rc_memory_regions_megadrive_32x;
|
||||
|
||||
case RC_CONSOLE_MSX:
|
||||
return &rc_memory_regions_msx;
|
||||
|
||||
case RC_CONSOLE_NEOGEO_POCKET:
|
||||
return &rc_memory_regions_neo_geo_pocket;
|
||||
|
||||
case RC_CONSOLE_NEO_GEO_CD:
|
||||
return &rc_memory_regions_neo_geo_cd;
|
||||
|
||||
case RC_CONSOLE_NINTENDO:
|
||||
return &rc_memory_regions_nes;
|
||||
|
||||
|
@ -876,6 +979,9 @@ const rc_memory_regions_t* rc_console_memory_regions(int console_id)
|
|||
case RC_CONSOLE_NINTENDO_DS:
|
||||
return &rc_memory_regions_nintendo_ds;
|
||||
|
||||
case RC_CONSOLE_NINTENDO_DSI:
|
||||
return &rc_memory_regions_nintendo_dsi;
|
||||
|
||||
case RC_CONSOLE_ORIC:
|
||||
return &rc_memory_regions_oric;
|
||||
|
||||
|
@ -885,6 +991,9 @@ const rc_memory_regions_t* rc_console_memory_regions(int console_id)
|
|||
case RC_CONSOLE_PC_ENGINE:
|
||||
return &rc_memory_regions_pc_engine;
|
||||
|
||||
case RC_CONSOLE_PC_ENGINE_CD:
|
||||
return &rc_memory_regions_pc_engine_cd;
|
||||
|
||||
case RC_CONSOLE_PCFX:
|
||||
return &rc_memory_regions_pcfx;
|
||||
|
||||
|
@ -921,9 +1030,15 @@ const rc_memory_regions_t* rc_console_memory_regions(int console_id)
|
|||
case RC_CONSOLE_THOMSONTO8:
|
||||
return &rc_memory_regions_thomson_to8;
|
||||
|
||||
case RC_CONSOLE_TI83:
|
||||
return &rc_memory_regions_ti83;
|
||||
|
||||
case RC_CONSOLE_TIC80:
|
||||
return &rc_memory_regions_tic80;
|
||||
|
||||
case RC_CONSOLE_UZEBOX:
|
||||
return &rc_memory_regions_uzebox;
|
||||
|
||||
case RC_CONSOLE_VECTREX:
|
||||
return &rc_memory_regions_vectrex;
|
||||
|
||||
|
@ -933,6 +1048,9 @@ const rc_memory_regions_t* rc_console_memory_regions(int console_id)
|
|||
case RC_CONSOLE_WASM4:
|
||||
return &rc_memory_regions_wasm4;
|
||||
|
||||
case RC_CONSOLE_WII:
|
||||
return &rc_memory_regions_wii;
|
||||
|
||||
case RC_CONSOLE_WONDERSWAN:
|
||||
return &rc_memory_regions_wonderswan;
|
||||
|
||||
|
|
|
@ -95,6 +95,7 @@ int rc_parse_memref(const char** memaddr, char* size, unsigned* address) {
|
|||
switch (*aux++) {
|
||||
case 'f': case 'F': *size = RC_MEMSIZE_FLOAT; break;
|
||||
case 'm': case 'M': *size = RC_MEMSIZE_MBF32; break;
|
||||
case 'l': case 'L': *size = RC_MEMSIZE_MBF32_LE; break;
|
||||
|
||||
default:
|
||||
return RC_INVALID_FP_OPERAND;
|
||||
|
@ -201,6 +202,21 @@ static void rc_transform_memref_mbf32(rc_typed_value_t* value) {
|
|||
value->type = RC_VALUE_TYPE_FLOAT;
|
||||
}
|
||||
|
||||
static void rc_transform_memref_mbf32_le(rc_typed_value_t* value) {
|
||||
/* decodes a Microsoft Binary Format float */
|
||||
/* Locomotive BASIC (CPC) uses MBF40, but in little endian format */
|
||||
const unsigned mantissa = value->value.u32 & 0x007FFFFF;
|
||||
const int exponent = (int)(value->value.u32 >> 24) - 129;
|
||||
const int sign = (value->value.u32 & 0x00800000);
|
||||
|
||||
if (mantissa == 0 && exponent == -129)
|
||||
value->value.f32 = (sign) ? -0.0f : 0.0f;
|
||||
else
|
||||
value->value.f32 = rc_build_float(mantissa, exponent, sign);
|
||||
|
||||
value->type = RC_VALUE_TYPE_FLOAT;
|
||||
}
|
||||
|
||||
static const unsigned char rc_bits_set[16] = { 0,1,1,2,1,2,2,3,1,2,2,3,2,3,3,4 };
|
||||
|
||||
void rc_transform_memref_value(rc_typed_value_t* value, char size) {
|
||||
|
@ -293,6 +309,10 @@ void rc_transform_memref_value(rc_typed_value_t* value, char size) {
|
|||
rc_transform_memref_mbf32(value);
|
||||
break;
|
||||
|
||||
case RC_MEMSIZE_MBF32_LE:
|
||||
rc_transform_memref_mbf32_le(value);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
@ -319,6 +339,7 @@ static const unsigned rc_memref_masks[] = {
|
|||
0xffffffff, /* RC_MEMSIZE_32_BITS_BE */
|
||||
0xffffffff, /* RC_MEMSIZE_FLOAT */
|
||||
0xffffffff, /* RC_MEMSIZE_MBF32 */
|
||||
0xffffffff, /* RC_MEMSIZE_MBF32_LE */
|
||||
0xffffffff /* RC_MEMSIZE_VARIABLE */
|
||||
};
|
||||
|
||||
|
@ -354,6 +375,7 @@ static const char rc_memref_shared_sizes[] = {
|
|||
RC_MEMSIZE_32_BITS, /* RC_MEMSIZE_32_BITS_BE */
|
||||
RC_MEMSIZE_32_BITS, /* RC_MEMSIZE_FLOAT */
|
||||
RC_MEMSIZE_32_BITS, /* RC_MEMSIZE_MBF32 */
|
||||
RC_MEMSIZE_32_BITS, /* RC_MEMSIZE_MBF32_LE */
|
||||
RC_MEMSIZE_32_BITS /* RC_MEMSIZE_VARIABLE */
|
||||
};
|
||||
|
||||
|
@ -365,7 +387,7 @@ char rc_memref_shared_size(char size) {
|
|||
return rc_memref_shared_sizes[index];
|
||||
}
|
||||
|
||||
static unsigned rc_peek_value(unsigned address, char size, rc_peek_t peek, void* ud) {
|
||||
unsigned rc_peek_value(unsigned address, char size, rc_peek_t peek, void* ud) {
|
||||
if (!peek)
|
||||
return 0;
|
||||
|
||||
|
@ -422,7 +444,7 @@ void rc_init_parse_state_memrefs(rc_parse_state_t* parse, rc_memref_t** memrefs)
|
|||
*memrefs = 0;
|
||||
}
|
||||
|
||||
static unsigned rc_get_memref_value_value(rc_memref_value_t* memref, int operand_type) {
|
||||
static unsigned rc_get_memref_value_value(const rc_memref_value_t* memref, int operand_type) {
|
||||
switch (operand_type)
|
||||
{
|
||||
/* most common case explicitly first, even though it could be handled by default case.
|
||||
|
|
|
@ -59,6 +59,8 @@ static int rc_parse_operand_lua(rc_operand_t* self, const char** memaddr, rc_par
|
|||
self->value.luafunc = luaL_ref(parse->L, LUA_REGISTRYINDEX);
|
||||
}
|
||||
|
||||
#else
|
||||
(void)parse;
|
||||
#endif /* RC_DISABLE_LUA */
|
||||
|
||||
self->type = RC_OPERAND_LUA;
|
||||
|
@ -300,6 +302,7 @@ int rc_operand_is_float_memref(const rc_operand_t* self) {
|
|||
switch (self->size) {
|
||||
case RC_MEMSIZE_FLOAT:
|
||||
case RC_MEMSIZE_MBF32:
|
||||
case RC_MEMSIZE_MBF32_LE:
|
||||
return 1;
|
||||
|
||||
default:
|
||||
|
@ -319,6 +322,13 @@ int rc_operand_is_memref(const rc_operand_t* self) {
|
|||
}
|
||||
}
|
||||
|
||||
int rc_operand_is_float(const rc_operand_t* self) {
|
||||
if (self->type == RC_OPERAND_FP)
|
||||
return 1;
|
||||
|
||||
return rc_operand_is_float_memref(self);
|
||||
}
|
||||
|
||||
unsigned rc_transform_operand_value(unsigned value, const rc_operand_t* self) {
|
||||
switch (self->type)
|
||||
{
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,290 @@
|
|||
#ifndef RC_CLIENT_INTERNAL_H
|
||||
#define RC_CLIENT_INTERNAL_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include "rc_client.h"
|
||||
|
||||
#include "rc_compat.h"
|
||||
#include "rc_runtime.h"
|
||||
#include "rc_runtime_types.h"
|
||||
|
||||
typedef struct rc_client_callbacks_t {
|
||||
rc_client_read_memory_func_t read_memory;
|
||||
rc_client_event_handler_t event_handler;
|
||||
rc_client_server_call_t server_call;
|
||||
rc_client_message_callback_t log_call;
|
||||
|
||||
void* client_data;
|
||||
} rc_client_callbacks_t;
|
||||
|
||||
struct rc_client_scheduled_callback_data_t;
|
||||
typedef void (*rc_client_scheduled_callback_t)(struct rc_client_scheduled_callback_data_t* callback_data, rc_client_t* client, clock_t now);
|
||||
|
||||
typedef struct rc_client_scheduled_callback_data_t
|
||||
{
|
||||
clock_t when;
|
||||
unsigned related_id;
|
||||
rc_client_scheduled_callback_t callback;
|
||||
void* data;
|
||||
struct rc_client_scheduled_callback_data_t* next;
|
||||
} rc_client_scheduled_callback_data_t;
|
||||
|
||||
void rc_client_schedule_callback(rc_client_t* client, rc_client_scheduled_callback_data_t* scheduled_callback);
|
||||
|
||||
enum {
|
||||
RC_CLIENT_ACHIEVEMENT_PENDING_EVENT_NONE = 0,
|
||||
RC_CLIENT_ACHIEVEMENT_PENDING_EVENT_TRIGGERED = (1 << 1),
|
||||
RC_CLIENT_ACHIEVEMENT_PENDING_EVENT_CHALLENGE_INDICATOR_SHOW = (1 << 2),
|
||||
RC_CLIENT_ACHIEVEMENT_PENDING_EVENT_CHALLENGE_INDICATOR_HIDE = (1 << 3),
|
||||
RC_CLIENT_ACHIEVEMENT_PENDING_EVENT_UPDATE = (1 << 4) /* not a real event, just triggers update */
|
||||
};
|
||||
|
||||
typedef struct rc_client_achievement_info_t {
|
||||
rc_client_achievement_t public_;
|
||||
|
||||
rc_trigger_t* trigger;
|
||||
uint8_t md5[16];
|
||||
|
||||
time_t unlock_time_hardcore;
|
||||
time_t unlock_time_softcore;
|
||||
|
||||
uint8_t pending_events;
|
||||
|
||||
const char* author;
|
||||
time_t created_time;
|
||||
time_t updated_time;
|
||||
} rc_client_achievement_info_t;
|
||||
|
||||
enum {
|
||||
RC_CLIENT_PROGRESS_TRACKER_ACTION_NONE,
|
||||
RC_CLIENT_PROGRESS_TRACKER_ACTION_SHOW,
|
||||
RC_CLIENT_PROGRESS_TRACKER_ACTION_UPDATE,
|
||||
RC_CLIENT_PROGRESS_TRACKER_ACTION_HIDE
|
||||
};
|
||||
|
||||
typedef struct rc_client_progress_tracker_t {
|
||||
rc_client_achievement_info_t* achievement;
|
||||
float progress;
|
||||
|
||||
rc_client_scheduled_callback_data_t* hide_callback;
|
||||
uint8_t action;
|
||||
} rc_client_progress_tracker_t;
|
||||
|
||||
enum {
|
||||
RC_CLIENT_LEADERBOARD_TRACKER_PENDING_EVENT_NONE = 0,
|
||||
RC_CLIENT_LEADERBOARD_TRACKER_PENDING_EVENT_UPDATE = (1 << 1),
|
||||
RC_CLIENT_LEADERBOARD_TRACKER_PENDING_EVENT_SHOW = (1 << 2),
|
||||
RC_CLIENT_LEADERBOARD_TRACKER_PENDING_EVENT_HIDE = (1 << 3)
|
||||
};
|
||||
|
||||
typedef struct rc_client_leaderboard_tracker_info_t {
|
||||
rc_client_leaderboard_tracker_t public_;
|
||||
struct rc_client_leaderboard_tracker_info_t* next;
|
||||
int raw_value;
|
||||
|
||||
uint32_t value_djb2;
|
||||
|
||||
uint8_t format;
|
||||
uint8_t pending_events;
|
||||
uint8_t reference_count;
|
||||
uint8_t value_from_hits;
|
||||
} rc_client_leaderboard_tracker_info_t;
|
||||
|
||||
enum {
|
||||
RC_CLIENT_LEADERBOARD_PENDING_EVENT_NONE = 0,
|
||||
RC_CLIENT_LEADERBOARD_PENDING_EVENT_STARTED = (1 << 1),
|
||||
RC_CLIENT_LEADERBOARD_PENDING_EVENT_FAILED = (1 << 2),
|
||||
RC_CLIENT_LEADERBOARD_PENDING_EVENT_SUBMITTED = (1 << 3)
|
||||
};
|
||||
|
||||
typedef struct rc_client_leaderboard_info_t {
|
||||
rc_client_leaderboard_t public_;
|
||||
|
||||
rc_lboard_t* lboard;
|
||||
uint8_t md5[16];
|
||||
|
||||
rc_client_leaderboard_tracker_info_t* tracker;
|
||||
|
||||
uint32_t value_djb2;
|
||||
int value;
|
||||
|
||||
uint8_t format;
|
||||
uint8_t pending_events;
|
||||
uint8_t bucket;
|
||||
uint8_t hidden;
|
||||
} rc_client_leaderboard_info_t;
|
||||
|
||||
enum {
|
||||
RC_CLIENT_SUBSET_PENDING_EVENT_NONE = 0,
|
||||
RC_CLIENT_SUBSET_PENDING_EVENT_ACHIEVEMENT = (1 << 1),
|
||||
RC_CLIENT_SUBSET_PENDING_EVENT_LEADERBOARD = (1 << 2)
|
||||
};
|
||||
|
||||
enum {
|
||||
RC_CLIENT_GAME_PENDING_EVENT_NONE = 0,
|
||||
RC_CLIENT_GAME_PENDING_EVENT_LEADERBOARD_TRACKER = (1 << 1),
|
||||
RC_CLIENT_GAME_PENDING_EVENT_UPDATE_ACTIVE_ACHIEVEMENTS = (1 << 2),
|
||||
RC_CLIENT_GAME_PENDING_EVENT_PROGRESS_TRACKER = (1 << 3)
|
||||
};
|
||||
|
||||
typedef struct rc_client_subset_info_t {
|
||||
rc_client_subset_t public_;
|
||||
|
||||
rc_client_achievement_info_t* achievements;
|
||||
rc_client_leaderboard_info_t* leaderboards;
|
||||
|
||||
struct rc_client_subset_info_t* next;
|
||||
|
||||
const char* all_label;
|
||||
const char* inactive_label;
|
||||
const char* locked_label;
|
||||
const char* unlocked_label;
|
||||
const char* unofficial_label;
|
||||
const char* unsupported_label;
|
||||
|
||||
uint8_t active;
|
||||
uint8_t mastery;
|
||||
uint8_t pending_events;
|
||||
} rc_client_subset_info_t;
|
||||
|
||||
typedef struct rc_client_game_hash_t {
|
||||
char hash[33];
|
||||
uint32_t game_id;
|
||||
struct rc_client_game_hash_t* next;
|
||||
} rc_client_game_hash_t;
|
||||
|
||||
rc_client_game_hash_t* rc_client_find_game_hash(rc_client_t* client, const char* hash);
|
||||
|
||||
typedef struct rc_client_media_hash_t {
|
||||
rc_client_game_hash_t* game_hash;
|
||||
struct rc_client_media_hash_t* next;
|
||||
uint32_t path_djb2;
|
||||
} rc_client_media_hash_t;
|
||||
|
||||
typedef struct rc_client_game_info_t {
|
||||
rc_client_game_t public_;
|
||||
rc_client_leaderboard_tracker_info_t* leaderboard_trackers;
|
||||
rc_client_progress_tracker_t progress_tracker;
|
||||
|
||||
rc_client_subset_info_t* subsets;
|
||||
|
||||
rc_client_media_hash_t* media_hash;
|
||||
|
||||
rc_runtime_t runtime;
|
||||
|
||||
uint32_t max_valid_address;
|
||||
|
||||
uint8_t waiting_for_reset;
|
||||
uint8_t pending_events;
|
||||
|
||||
rc_api_buffer_t buffer;
|
||||
} rc_client_game_info_t;
|
||||
|
||||
enum {
|
||||
RC_CLIENT_LOAD_STATE_NONE,
|
||||
RC_CLIENT_LOAD_STATE_IDENTIFYING_GAME,
|
||||
RC_CLIENT_LOAD_STATE_AWAIT_LOGIN,
|
||||
RC_CLIENT_LOAD_STATE_FETCHING_GAME_DATA,
|
||||
RC_CLIENT_LOAD_STATE_STARTING_SESSION,
|
||||
RC_CLIENT_LOAD_STATE_DONE,
|
||||
RC_CLIENT_LOAD_STATE_UNKNOWN_GAME
|
||||
};
|
||||
|
||||
enum {
|
||||
RC_CLIENT_USER_STATE_NONE,
|
||||
RC_CLIENT_USER_STATE_LOGIN_REQUESTED,
|
||||
RC_CLIENT_USER_STATE_LOGGED_IN
|
||||
};
|
||||
|
||||
enum {
|
||||
RC_CLIENT_MASTERY_STATE_NONE,
|
||||
RC_CLIENT_MASTERY_STATE_PENDING,
|
||||
RC_CLIENT_MASTERY_STATE_SHOWN
|
||||
};
|
||||
|
||||
enum {
|
||||
RC_CLIENT_SPECTATOR_MODE_OFF,
|
||||
RC_CLIENT_SPECTATOR_MODE_ON,
|
||||
RC_CLIENT_SPECTATOR_MODE_LOCKED
|
||||
};
|
||||
|
||||
struct rc_client_load_state_t;
|
||||
|
||||
typedef struct rc_client_state_t {
|
||||
rc_mutex_t mutex;
|
||||
rc_api_buffer_t buffer;
|
||||
|
||||
rc_client_scheduled_callback_data_t* scheduled_callbacks;
|
||||
|
||||
uint8_t hardcore;
|
||||
uint8_t encore_mode;
|
||||
uint8_t spectator_mode;
|
||||
uint8_t unofficial_enabled;
|
||||
uint8_t log_level;
|
||||
uint8_t user;
|
||||
|
||||
struct rc_client_load_state_t* load;
|
||||
rc_memref_t* processing_memref;
|
||||
|
||||
rc_peek_t legacy_peek;
|
||||
} rc_client_state_t;
|
||||
|
||||
struct rc_client_t {
|
||||
rc_client_game_info_t* game;
|
||||
rc_client_game_hash_t* hashes;
|
||||
|
||||
rc_client_user_t user;
|
||||
|
||||
rc_client_callbacks_t callbacks;
|
||||
|
||||
rc_client_state_t state;
|
||||
};
|
||||
|
||||
#ifdef RC_NO_VARIADIC_MACROS
|
||||
void RC_CLIENT_LOG_ERR_FORMATTED(const rc_client_t* client, const char* format, ...);
|
||||
void RC_CLIENT_LOG_WARN_FORMATTED(const rc_client_t* client, const char* format, ...);
|
||||
void RC_CLIENT_LOG_INFO_FORMATTED(const rc_client_t* client, const char* format, ...);
|
||||
void RC_CLIENT_LOG_VERBOSE_FORMATTED(const rc_client_t* client, const char* format, ...);
|
||||
#else
|
||||
void rc_client_log_message_formatted(const rc_client_t* client, const char* format, ...);
|
||||
#define RC_CLIENT_LOG_ERR_FORMATTED(client, format, ...) { if (client->state.log_level >= RC_CLIENT_LOG_LEVEL_ERROR) rc_client_log_message_formatted(client, format, __VA_ARGS__); }
|
||||
#define RC_CLIENT_LOG_WARN_FORMATTED(client, format, ...) { if (client->state.log_level >= RC_CLIENT_LOG_LEVEL_WARN) rc_client_log_message_formatted(client, format, __VA_ARGS__); }
|
||||
#define RC_CLIENT_LOG_INFO_FORMATTED(client, format, ...) { if (client->state.log_level >= RC_CLIENT_LOG_LEVEL_INFO) rc_client_log_message_formatted(client, format, __VA_ARGS__); }
|
||||
#define RC_CLIENT_LOG_VERBOSE_FORMATTED(client, format, ...) { if (client->state.log_level >= RC_CLIENT_LOG_LEVEL_VERBOSE) rc_client_log_message_formatted(client, format, __VA_ARGS__); }
|
||||
#endif
|
||||
|
||||
void rc_client_log_message(const rc_client_t* client, const char* message);
|
||||
#define RC_CLIENT_LOG_ERR(client, message) { if (client->state.log_level >= RC_CLIENT_LOG_LEVEL_ERROR) rc_client_log_message(client, message); }
|
||||
#define RC_CLIENT_LOG_WARN(client, message) { if (client->state.log_level >= RC_CLIENT_LOG_LEVEL_WARN) rc_client_log_message(client, message); }
|
||||
#define RC_CLIENT_LOG_INFO(client, message) { if (client->state.log_level >= RC_CLIENT_LOG_LEVEL_INFO) rc_client_log_message(client, message); }
|
||||
#define RC_CLIENT_LOG_VERBOSE(client, message) { if (client->state.log_level >= RC_CLIENT_LOG_LEVEL_VERBOSE) rc_client_log_message(client, message); }
|
||||
|
||||
/* internals pulled from runtime.c */
|
||||
void rc_runtime_checksum(const char* memaddr, unsigned char* md5);
|
||||
int rc_trigger_contains_memref(const rc_trigger_t* trigger, const rc_memref_t* memref);
|
||||
int rc_value_contains_memref(const rc_value_t* value, const rc_memref_t* memref);
|
||||
/* end runtime.c internals */
|
||||
|
||||
/* helper functions for unit tests */
|
||||
struct rc_hash_iterator;
|
||||
struct rc_hash_iterator* rc_client_get_load_state_hash_iterator(rc_client_t* client);
|
||||
/* end helper functions for unit tests */
|
||||
|
||||
enum {
|
||||
RC_CLIENT_LEGACY_PEEK_AUTO,
|
||||
RC_CLIENT_LEGACY_PEEK_CONSTRUCTED,
|
||||
RC_CLIENT_LEGACY_PEEK_LITTLE_ENDIAN_READS
|
||||
};
|
||||
|
||||
void rc_client_set_legacy_peek(rc_client_t* client, int method);
|
||||
|
||||
void rc_client_begin_load_subset(rc_client_t* client, uint32_t subset_id, rc_client_callback_t callback, void* callback_userdata);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* RC_CLIENT_INTERNAL_H */
|
|
@ -13,6 +13,8 @@ extern "C" {
|
|||
|
||||
/* MinGW redefinitions */
|
||||
|
||||
#define RC_NO_VARIADIC_MACROS 1
|
||||
|
||||
#elif defined(_MSC_VER)
|
||||
|
||||
/* Visual Studio redefinitions */
|
||||
|
@ -30,6 +32,9 @@ extern "C" {
|
|||
#elif __STDC_VERSION__ < 199901L
|
||||
|
||||
/* C89 redefinitions */
|
||||
#define RC_C89_HELPERS 1
|
||||
|
||||
#define RC_NO_VARIADIC_MACROS 1
|
||||
|
||||
#ifndef snprintf
|
||||
extern int rc_snprintf(char* buffer, size_t size, const char* format, ...);
|
||||
|
@ -53,6 +58,40 @@ extern "C" {
|
|||
|
||||
#endif /* __STDC_VERSION__ < 199901L */
|
||||
|
||||
#ifndef __STDC_WANT_SECURE_LIB__
|
||||
/* _CRT_SECURE_NO_WARNINGS redefinitions */
|
||||
#define strcpy_s(dest, sz, src) strcpy(dest, src)
|
||||
#define sscanf_s sscanf
|
||||
|
||||
/* NOTE: Microsoft secure gmtime_s parameter order differs from C11 standard */
|
||||
#include <time.h>
|
||||
extern struct tm* rc_gmtime_s(struct tm* buf, const time_t* timer);
|
||||
#define gmtime_s rc_gmtime_s
|
||||
#endif
|
||||
|
||||
#ifdef RC_NO_THREADS
|
||||
typedef int rc_mutex_t;
|
||||
|
||||
#define rc_mutex_init(mutex)
|
||||
#define rc_mutex_destroy(mutex)
|
||||
#define rc_mutex_lock(mutex)
|
||||
#define rc_mutex_unlock(mutex)
|
||||
#else
|
||||
#ifdef _WIN32
|
||||
typedef struct rc_mutex_t {
|
||||
void* handle; /* HANDLE is defined as "void*" */
|
||||
} rc_mutex_t;
|
||||
#else
|
||||
#include <pthread.h>
|
||||
typedef pthread_mutex_t rc_mutex_t;
|
||||
#endif
|
||||
|
||||
void rc_mutex_init(rc_mutex_t* mutex);
|
||||
void rc_mutex_destroy(rc_mutex_t* mutex);
|
||||
void rc_mutex_lock(rc_mutex_t* mutex);
|
||||
void rc_mutex_unlock(rc_mutex_t* mutex);
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -36,6 +36,9 @@ RC_ALLOW_ALIGN(char)
|
|||
#define RC_ALLOC(t, p) ((t*)rc_alloc((p)->buffer, &(p)->offset, sizeof(t), RC_ALIGNOF(t), &(p)->scratch, RC_OFFSETOF((p)->scratch.objs, __ ## t)))
|
||||
#define RC_ALLOC_SCRATCH(t, p) ((t*)rc_alloc_scratch((p)->buffer, &(p)->offset, sizeof(t), RC_ALIGNOF(t), &(p)->scratch, RC_OFFSETOF((p)->scratch.objs, __ ## t)))
|
||||
|
||||
/* force alignment to 4 bytes on 32-bit systems, or 8 bytes on 64-bit systems */
|
||||
#define RC_ALIGN(n) (((n) + (sizeof(void*)-1)) & ~(sizeof(void*)-1))
|
||||
|
||||
typedef struct rc_scratch_buffer {
|
||||
struct rc_scratch_buffer* next;
|
||||
int offset;
|
||||
|
@ -83,6 +86,8 @@ typedef struct {
|
|||
}
|
||||
rc_typed_value_t;
|
||||
|
||||
#define RC_MEASURED_UNKNOWN 0xFFFFFFFF
|
||||
|
||||
typedef struct {
|
||||
rc_typed_value_t add_value;/* AddSource/SubSource */
|
||||
int add_hits; /* AddHits */
|
||||
|
@ -130,6 +135,8 @@ void* rc_alloc(void* pointer, int* offset, int size, int alignment, rc_scratch_t
|
|||
void* rc_alloc_scratch(void* pointer, int* offset, int size, int alignment, rc_scratch_t* scratch, int scratch_object_pointer_offset);
|
||||
char* rc_alloc_str(rc_parse_state_t* parse, const char* text, int length);
|
||||
|
||||
unsigned rc_djb2(const char* input);
|
||||
|
||||
rc_memref_t* rc_alloc_memref(rc_parse_state_t* parse, unsigned address, char size, char is_indirect);
|
||||
int rc_parse_memref(const char** memaddr, char* size, unsigned* address);
|
||||
void rc_update_memref_values(rc_memref_t* memref, rc_peek_t peek, void* ud);
|
||||
|
@ -138,6 +145,7 @@ unsigned rc_get_memref_value(rc_memref_t* memref, int operand_type, rc_eval_stat
|
|||
char rc_memref_shared_size(char size);
|
||||
unsigned rc_memref_mask(char size);
|
||||
void rc_transform_memref_value(rc_typed_value_t* value, char size);
|
||||
unsigned rc_peek_value(unsigned address, char size, rc_peek_t peek, void* ud);
|
||||
|
||||
void rc_parse_trigger_internal(rc_trigger_t* self, const char** memaddr, rc_parse_state_t* parse);
|
||||
int rc_trigger_state_active(int state);
|
||||
|
@ -146,6 +154,22 @@ rc_condset_t* rc_parse_condset(const char** memaddr, rc_parse_state_t* parse, in
|
|||
int rc_test_condset(rc_condset_t* self, rc_eval_state_t* eval_state);
|
||||
void rc_reset_condset(rc_condset_t* self);
|
||||
|
||||
enum {
|
||||
RC_PROCESSING_COMPARE_DEFAULT = 0,
|
||||
RC_PROCESSING_COMPARE_MEMREF_TO_CONST,
|
||||
RC_PROCESSING_COMPARE_MEMREF_TO_DELTA,
|
||||
RC_PROCESSING_COMPARE_MEMREF_TO_MEMREF,
|
||||
RC_PROCESSING_COMPARE_DELTA_TO_MEMREF,
|
||||
RC_PROCESSING_COMPARE_DELTA_TO_CONST,
|
||||
RC_PROCESSING_COMPARE_MEMREF_TO_CONST_TRANSFORMED,
|
||||
RC_PROCESSING_COMPARE_MEMREF_TO_DELTA_TRANSFORMED,
|
||||
RC_PROCESSING_COMPARE_MEMREF_TO_MEMREF_TRANSFORMED,
|
||||
RC_PROCESSING_COMPARE_DELTA_TO_MEMREF_TRANSFORMED,
|
||||
RC_PROCESSING_COMPARE_DELTA_TO_CONST_TRANSFORMED,
|
||||
RC_PROCESSING_COMPARE_ALWAYS_TRUE,
|
||||
RC_PROCESSING_COMPARE_ALWAYS_FALSE
|
||||
};
|
||||
|
||||
rc_condition_t* rc_parse_condition(const char** memaddr, rc_parse_state_t* parse, int is_indirect);
|
||||
int rc_test_condition(rc_condition_t* self, rc_eval_state_t* eval_state);
|
||||
void rc_evaluate_condition_value(rc_typed_value_t* value, rc_condition_t* self, rc_eval_state_t* eval_state);
|
||||
|
@ -154,10 +178,12 @@ int rc_condition_is_combining(const rc_condition_t* self);
|
|||
int rc_parse_operand(rc_operand_t* self, const char** memaddr, int is_indirect, rc_parse_state_t* parse);
|
||||
void rc_evaluate_operand(rc_typed_value_t* value, rc_operand_t* self, rc_eval_state_t* eval_state);
|
||||
int rc_operand_is_float_memref(const rc_operand_t* self);
|
||||
int rc_operand_is_float(const rc_operand_t* self);
|
||||
|
||||
void rc_parse_value_internal(rc_value_t* self, const char** memaddr, rc_parse_state_t* parse);
|
||||
int rc_evaluate_value_typed(rc_value_t* self, rc_typed_value_t* value, rc_peek_t peek, void* ud, lua_State* L);
|
||||
void rc_reset_value(rc_value_t* self);
|
||||
int rc_value_from_hits(rc_value_t* self);
|
||||
rc_value_t* rc_alloc_helper_variable(const char* memaddr, int memaddr_len, rc_parse_state_t* parse);
|
||||
void rc_update_variables(rc_value_t* variable, rc_peek_t peek, void* ud, lua_State* L);
|
||||
|
||||
|
@ -165,6 +191,7 @@ void rc_typed_value_convert(rc_typed_value_t* value, char new_type);
|
|||
void rc_typed_value_add(rc_typed_value_t* value, const rc_typed_value_t* amount);
|
||||
void rc_typed_value_multiply(rc_typed_value_t* value, const rc_typed_value_t* amount);
|
||||
void rc_typed_value_divide(rc_typed_value_t* value, const rc_typed_value_t* amount);
|
||||
void rc_typed_value_negate(rc_typed_value_t* value);
|
||||
int rc_typed_value_compare(const rc_typed_value_t* value1, const rc_typed_value_t* value2, char oper);
|
||||
void rc_typed_value_from_memref_value(rc_typed_value_t* value, const rc_memref_value_t* memref);
|
||||
|
||||
|
|
|
@ -0,0 +1,816 @@
|
|||
/* This file provides a series of functions for integrating RetroAchievements with libretro.
|
||||
* These functions will be called by a libretro frontend to validate certain expected behaviors
|
||||
* and simplify mapping core data to the RAIntegration DLL.
|
||||
*
|
||||
* Originally designed to be shared between RALibretro and RetroArch, but will simplify
|
||||
* integrating with any other frontends.
|
||||
*/
|
||||
|
||||
#include "rc_libretro.h"
|
||||
|
||||
#include "rc_consoles.h"
|
||||
#include "rc_compat.h"
|
||||
|
||||
#include <ctype.h>
|
||||
#include <string.h>
|
||||
|
||||
/* internal helper functions in hash.c */
|
||||
extern void* rc_file_open(const char* path);
|
||||
extern void rc_file_seek(void* file_handle, int64_t offset, int origin);
|
||||
extern int64_t rc_file_tell(void* file_handle);
|
||||
extern size_t rc_file_read(void* file_handle, void* buffer, int requested_bytes);
|
||||
extern void rc_file_close(void* file_handle);
|
||||
extern int rc_path_compare_extension(const char* path, const char* ext);
|
||||
extern int rc_hash_error(const char* message);
|
||||
|
||||
|
||||
static rc_libretro_message_callback rc_libretro_verbose_message_callback = NULL;
|
||||
|
||||
/* a value that starts with a comma is a CSV.
|
||||
* if it starts with an exclamation point, it's everything but the provided value.
|
||||
* if it starts with an exclamntion point followed by a comma, it's everything but the CSV values.
|
||||
* values are case-insensitive */
|
||||
typedef struct rc_disallowed_core_settings_t
|
||||
{
|
||||
const char* library_name;
|
||||
const rc_disallowed_setting_t* disallowed_settings;
|
||||
} rc_disallowed_core_settings_t;
|
||||
|
||||
static const rc_disallowed_setting_t _rc_disallowed_bsnes_settings[] = {
|
||||
{ "bsnes_region", "pal" },
|
||||
{ NULL, NULL }
|
||||
};
|
||||
|
||||
static const rc_disallowed_setting_t _rc_disallowed_cap32_settings[] = {
|
||||
{ "cap32_autorun", "disabled" },
|
||||
{ NULL, NULL }
|
||||
};
|
||||
|
||||
static const rc_disallowed_setting_t _rc_disallowed_dolphin_settings[] = {
|
||||
{ "dolphin_cheats_enabled", "enabled" },
|
||||
{ NULL, NULL }
|
||||
};
|
||||
|
||||
static const rc_disallowed_setting_t _rc_disallowed_duckstation_settings[] = {
|
||||
{ "duckstation_CDROM.LoadImagePatches", "true" },
|
||||
{ NULL, NULL }
|
||||
};
|
||||
|
||||
static const rc_disallowed_setting_t _rc_disallowed_ecwolf_settings[] = {
|
||||
{ "ecwolf-invulnerability", "enabled" },
|
||||
{ NULL, NULL }
|
||||
};
|
||||
|
||||
static const rc_disallowed_setting_t _rc_disallowed_fbneo_settings[] = {
|
||||
{ "fbneo-allow-patched-romsets", "enabled" },
|
||||
{ "fbneo-cheat-*", "!,Disabled,0 - Disabled" },
|
||||
{ "fbneo-dipswitch-*", "Universe BIOS*" },
|
||||
{ "fbneo-neogeo-mode", "UNIBIOS" },
|
||||
{ NULL, NULL }
|
||||
};
|
||||
|
||||
static const rc_disallowed_setting_t _rc_disallowed_fceumm_settings[] = {
|
||||
{ "fceumm_region", ",PAL,Dendy" },
|
||||
{ NULL, NULL }
|
||||
};
|
||||
|
||||
static const rc_disallowed_setting_t _rc_disallowed_gpgx_settings[] = {
|
||||
{ "genesis_plus_gx_lock_on", ",action replay (pro),game genie" },
|
||||
{ "genesis_plus_gx_region_detect", "pal" },
|
||||
{ NULL, NULL }
|
||||
};
|
||||
|
||||
static const rc_disallowed_setting_t _rc_disallowed_gpgx_wide_settings[] = {
|
||||
{ "genesis_plus_gx_wide_lock_on", ",action replay (pro),game genie" },
|
||||
{ "genesis_plus_gx_wide_region_detect", "pal" },
|
||||
{ NULL, NULL }
|
||||
};
|
||||
|
||||
static const rc_disallowed_setting_t _rc_disallowed_mesen_settings[] = {
|
||||
{ "mesen_region", ",PAL,Dendy" },
|
||||
{ NULL, NULL }
|
||||
};
|
||||
|
||||
static const rc_disallowed_setting_t _rc_disallowed_mesen_s_settings[] = {
|
||||
{ "mesen-s_region", "PAL" },
|
||||
{ NULL, NULL }
|
||||
};
|
||||
|
||||
static const rc_disallowed_setting_t _rc_disallowed_pcsx_rearmed_settings[] = {
|
||||
{ "pcsx_rearmed_region", "pal" },
|
||||
{ NULL, NULL }
|
||||
};
|
||||
|
||||
static const rc_disallowed_setting_t _rc_disallowed_picodrive_settings[] = {
|
||||
{ "picodrive_region", ",Europe,Japan PAL" },
|
||||
{ NULL, NULL }
|
||||
};
|
||||
|
||||
static const rc_disallowed_setting_t _rc_disallowed_ppsspp_settings[] = {
|
||||
{ "ppsspp_cheats", "enabled" },
|
||||
{ NULL, NULL }
|
||||
};
|
||||
|
||||
static const rc_disallowed_setting_t _rc_disallowed_quasi88_settings[] = {
|
||||
{ "q88_cpu_clock", ",1,2" },
|
||||
{ NULL, NULL }
|
||||
};
|
||||
|
||||
static const rc_disallowed_setting_t _rc_disallowed_smsplus_settings[] = {
|
||||
{ "smsplus_region", "pal" },
|
||||
{ NULL, NULL }
|
||||
};
|
||||
|
||||
static const rc_disallowed_setting_t _rc_disallowed_snes9x_settings[] = {
|
||||
{ "snes9x_gfx_clip", "disabled" },
|
||||
{ "snes9x_gfx_transp", "disabled" },
|
||||
{ "snes9x_layer_*", "disabled" },
|
||||
{ "snes9x_region", "pal" },
|
||||
{ NULL, NULL }
|
||||
};
|
||||
|
||||
static const rc_disallowed_setting_t _rc_disallowed_vice_settings[] = {
|
||||
{ "vice_autostart", "disabled" }, /* autostart dictates initial load and reset from menu */
|
||||
{ "vice_reset", "!autostart" }, /* reset dictates behavior when pressing reset button (END) */
|
||||
{ NULL, NULL }
|
||||
};
|
||||
|
||||
static const rc_disallowed_setting_t _rc_disallowed_virtual_jaguar_settings[] = {
|
||||
{ "virtualjaguar_pal", "enabled" },
|
||||
{ NULL, NULL }
|
||||
};
|
||||
|
||||
static const rc_disallowed_core_settings_t rc_disallowed_core_settings[] = {
|
||||
{ "bsnes-mercury", _rc_disallowed_bsnes_settings },
|
||||
{ "cap32", _rc_disallowed_cap32_settings },
|
||||
{ "dolphin-emu", _rc_disallowed_dolphin_settings },
|
||||
{ "DuckStation", _rc_disallowed_duckstation_settings },
|
||||
{ "ecwolf", _rc_disallowed_ecwolf_settings },
|
||||
{ "FCEUmm", _rc_disallowed_fceumm_settings },
|
||||
{ "FinalBurn Neo", _rc_disallowed_fbneo_settings },
|
||||
{ "Genesis Plus GX", _rc_disallowed_gpgx_settings },
|
||||
{ "Genesis Plus GX Wide", _rc_disallowed_gpgx_wide_settings },
|
||||
{ "Mesen", _rc_disallowed_mesen_settings },
|
||||
{ "Mesen-S", _rc_disallowed_mesen_s_settings },
|
||||
{ "PPSSPP", _rc_disallowed_ppsspp_settings },
|
||||
{ "PCSX-ReARMed", _rc_disallowed_pcsx_rearmed_settings },
|
||||
{ "PicoDrive", _rc_disallowed_picodrive_settings },
|
||||
{ "QUASI88", _rc_disallowed_quasi88_settings },
|
||||
{ "SMS Plus GX", _rc_disallowed_smsplus_settings },
|
||||
{ "Snes9x", _rc_disallowed_snes9x_settings },
|
||||
{ "VICE x64", _rc_disallowed_vice_settings },
|
||||
{ "Virtual Jaguar", _rc_disallowed_virtual_jaguar_settings },
|
||||
{ NULL, NULL }
|
||||
};
|
||||
|
||||
static int rc_libretro_string_equal_nocase_wildcard(const char* test, const char* value) {
|
||||
char c1, c2;
|
||||
while ((c1 = *test++)) {
|
||||
if (tolower(c1) != tolower(c2 = *value++))
|
||||
return (c2 == '*');
|
||||
}
|
||||
|
||||
return (*value == '\0');
|
||||
}
|
||||
|
||||
static int rc_libretro_match_value(const char* val, const char* match) {
|
||||
/* if value starts with a comma, it's a CSV list of potential matches */
|
||||
if (*match == ',') {
|
||||
do {
|
||||
const char* ptr = ++match;
|
||||
size_t size;
|
||||
|
||||
while (*match && *match != ',')
|
||||
++match;
|
||||
|
||||
size = match - ptr;
|
||||
if (val[size] == '\0') {
|
||||
if (memcmp(ptr, val, size) == 0) {
|
||||
return 1;
|
||||
}
|
||||
else {
|
||||
char buffer[128];
|
||||
memcpy(buffer, ptr, size);
|
||||
buffer[size] = '\0';
|
||||
if (rc_libretro_string_equal_nocase_wildcard(buffer, val))
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
} while (*match == ',');
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* a leading exclamation point means the provided value(s) are not forbidden (are allowed) */
|
||||
if (*match == '!')
|
||||
return !rc_libretro_match_value(val, &match[1]);
|
||||
|
||||
/* just a single value, attempt to match it */
|
||||
return rc_libretro_string_equal_nocase_wildcard(val, match);
|
||||
}
|
||||
|
||||
int rc_libretro_is_setting_allowed(const rc_disallowed_setting_t* disallowed_settings, const char* setting, const char* value) {
|
||||
const char* key;
|
||||
size_t key_len;
|
||||
|
||||
for (; disallowed_settings->setting; ++disallowed_settings) {
|
||||
key = disallowed_settings->setting;
|
||||
key_len = strlen(key);
|
||||
|
||||
if (key[key_len - 1] == '*') {
|
||||
if (memcmp(setting, key, key_len - 1) == 0) {
|
||||
if (rc_libretro_match_value(value, disallowed_settings->value))
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (memcmp(setting, key, key_len + 1) == 0) {
|
||||
if (rc_libretro_match_value(value, disallowed_settings->value))
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
const rc_disallowed_setting_t* rc_libretro_get_disallowed_settings(const char* library_name) {
|
||||
const rc_disallowed_core_settings_t* core_filter = rc_disallowed_core_settings;
|
||||
size_t library_name_length;
|
||||
|
||||
if (!library_name || !library_name[0])
|
||||
return NULL;
|
||||
|
||||
library_name_length = strlen(library_name) + 1;
|
||||
while (core_filter->library_name) {
|
||||
if (memcmp(core_filter->library_name, library_name, library_name_length) == 0)
|
||||
return core_filter->disallowed_settings;
|
||||
|
||||
++core_filter;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
typedef struct rc_disallowed_core_systems_t
|
||||
{
|
||||
const char* library_name;
|
||||
const int disallowed_consoles[4];
|
||||
} rc_disallowed_core_systems_t;
|
||||
|
||||
static const rc_disallowed_core_systems_t rc_disallowed_core_systems[] = {
|
||||
/* https://github.com/libretro/Mesen-S/issues/8 */
|
||||
{ "Mesen-S", { RC_CONSOLE_GAMEBOY, RC_CONSOLE_GAMEBOY_COLOR, 0 }},
|
||||
{ NULL, { 0 } }
|
||||
};
|
||||
|
||||
int rc_libretro_is_system_allowed(const char* library_name, int console_id) {
|
||||
const rc_disallowed_core_systems_t* core_filter = rc_disallowed_core_systems;
|
||||
size_t library_name_length;
|
||||
size_t i;
|
||||
|
||||
if (!library_name || !library_name[0])
|
||||
return 1;
|
||||
|
||||
library_name_length = strlen(library_name) + 1;
|
||||
while (core_filter->library_name) {
|
||||
if (memcmp(core_filter->library_name, library_name, library_name_length) == 0) {
|
||||
for (i = 0; i < sizeof(core_filter->disallowed_consoles) / sizeof(core_filter->disallowed_consoles[0]); ++i) {
|
||||
if (core_filter->disallowed_consoles[i] == console_id)
|
||||
return 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
++core_filter;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
unsigned char* rc_libretro_memory_find_avail(const rc_libretro_memory_regions_t* regions, unsigned address, unsigned* avail) {
|
||||
unsigned i;
|
||||
|
||||
for (i = 0; i < regions->count; ++i) {
|
||||
const size_t size = regions->size[i];
|
||||
if (address < size) {
|
||||
if (regions->data[i] == NULL)
|
||||
break;
|
||||
|
||||
if (avail)
|
||||
*avail = (unsigned)(size - address);
|
||||
|
||||
return ®ions->data[i][address];
|
||||
}
|
||||
|
||||
address -= (unsigned)size;
|
||||
}
|
||||
|
||||
if (avail)
|
||||
*avail = 0;
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
unsigned char* rc_libretro_memory_find(const rc_libretro_memory_regions_t* regions, unsigned address) {
|
||||
return rc_libretro_memory_find_avail(regions, address, NULL);
|
||||
}
|
||||
|
||||
uint32_t rc_libretro_memory_read(const rc_libretro_memory_regions_t* regions, unsigned address,
|
||||
uint8_t* buffer, uint32_t num_bytes) {
|
||||
unsigned i;
|
||||
uint32_t avail;
|
||||
|
||||
for (i = 0; i < regions->count; ++i) {
|
||||
const size_t size = regions->size[i];
|
||||
if (address < size) {
|
||||
if (regions->data[i] == NULL)
|
||||
break;
|
||||
|
||||
avail = (unsigned)(size - address);
|
||||
if (avail < num_bytes)
|
||||
return avail;
|
||||
|
||||
memcpy(buffer, ®ions->data[i][address], num_bytes);
|
||||
return num_bytes;
|
||||
}
|
||||
|
||||
address -= (unsigned)size;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void rc_libretro_init_verbose_message_callback(rc_libretro_message_callback callback) {
|
||||
rc_libretro_verbose_message_callback = callback;
|
||||
}
|
||||
|
||||
static void rc_libretro_verbose(const char* message) {
|
||||
if (rc_libretro_verbose_message_callback)
|
||||
rc_libretro_verbose_message_callback(message);
|
||||
}
|
||||
|
||||
static const char* rc_memory_type_str(int type) {
|
||||
switch (type)
|
||||
{
|
||||
case RC_MEMORY_TYPE_SAVE_RAM:
|
||||
return "SRAM";
|
||||
case RC_MEMORY_TYPE_VIDEO_RAM:
|
||||
return "VRAM";
|
||||
case RC_MEMORY_TYPE_UNUSED:
|
||||
return "UNUSED";
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return "SYSTEM RAM";
|
||||
}
|
||||
|
||||
static void rc_libretro_memory_register_region(rc_libretro_memory_regions_t* regions, int type,
|
||||
unsigned char* data, size_t size, const char* description) {
|
||||
if (size == 0)
|
||||
return;
|
||||
|
||||
if (regions->count == (sizeof(regions->size) / sizeof(regions->size[0]))) {
|
||||
rc_libretro_verbose("Too many memory memory regions to register");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!data && regions->count > 0 && !regions->data[regions->count - 1]) {
|
||||
/* extend null region */
|
||||
regions->size[regions->count - 1] += size;
|
||||
}
|
||||
else if (data && regions->count > 0 &&
|
||||
data == (regions->data[regions->count - 1] + regions->size[regions->count - 1])) {
|
||||
/* extend non-null region */
|
||||
regions->size[regions->count - 1] += size;
|
||||
}
|
||||
else {
|
||||
/* create new region */
|
||||
regions->data[regions->count] = data;
|
||||
regions->size[regions->count] = size;
|
||||
++regions->count;
|
||||
}
|
||||
|
||||
regions->total_size += size;
|
||||
|
||||
if (rc_libretro_verbose_message_callback) {
|
||||
char message[128];
|
||||
snprintf(message, sizeof(message), "Registered 0x%04X bytes of %s at $%06X (%s)", (unsigned)size,
|
||||
rc_memory_type_str(type), (unsigned)(regions->total_size - size), description);
|
||||
rc_libretro_verbose_message_callback(message);
|
||||
}
|
||||
}
|
||||
|
||||
static void rc_libretro_memory_init_without_regions(rc_libretro_memory_regions_t* regions,
|
||||
rc_libretro_get_core_memory_info_func get_core_memory_info) {
|
||||
/* no regions specified, assume system RAM followed by save RAM */
|
||||
char description[64];
|
||||
rc_libretro_core_memory_info_t info;
|
||||
|
||||
snprintf(description, sizeof(description), "offset 0x%06x", 0);
|
||||
|
||||
get_core_memory_info(RETRO_MEMORY_SYSTEM_RAM, &info);
|
||||
if (info.size)
|
||||
rc_libretro_memory_register_region(regions, RC_MEMORY_TYPE_SYSTEM_RAM, info.data, info.size, description);
|
||||
|
||||
get_core_memory_info(RETRO_MEMORY_SAVE_RAM, &info);
|
||||
if (info.size)
|
||||
rc_libretro_memory_register_region(regions, RC_MEMORY_TYPE_SAVE_RAM, info.data, info.size, description);
|
||||
}
|
||||
|
||||
static const struct retro_memory_descriptor* rc_libretro_memory_get_descriptor(const struct retro_memory_map* mmap, unsigned real_address, size_t* offset)
|
||||
{
|
||||
const struct retro_memory_descriptor* desc = mmap->descriptors;
|
||||
const struct retro_memory_descriptor* end = desc + mmap->num_descriptors;
|
||||
|
||||
for (; desc < end; desc++) {
|
||||
if (desc->select == 0) {
|
||||
/* if select is 0, attempt to explcitly match the address */
|
||||
if (real_address >= desc->start && real_address < desc->start + desc->len) {
|
||||
*offset = real_address - desc->start;
|
||||
return desc;
|
||||
}
|
||||
}
|
||||
else {
|
||||
/* otherwise, attempt to match the address by matching the select bits */
|
||||
/* address is in the block if (addr & select) == (start & select) */
|
||||
if (((desc->start ^ real_address) & desc->select) == 0) {
|
||||
/* get the relative offset of the address from the start of the memory block */
|
||||
unsigned reduced_address = real_address - (unsigned)desc->start;
|
||||
|
||||
/* remove any bits from the reduced_address that correspond to the bits in the disconnect
|
||||
* mask and collapse the remaining bits. this code was copied from the mmap_reduce function
|
||||
* in RetroArch. i'm not exactly sure how it works, but it does. */
|
||||
unsigned disconnect_mask = (unsigned)desc->disconnect;
|
||||
while (disconnect_mask) {
|
||||
const unsigned tmp = (disconnect_mask - 1) & ~disconnect_mask;
|
||||
reduced_address = (reduced_address & tmp) | ((reduced_address >> 1) & ~tmp);
|
||||
disconnect_mask = (disconnect_mask & (disconnect_mask - 1)) >> 1;
|
||||
}
|
||||
|
||||
/* calculate the offset within the descriptor */
|
||||
*offset = reduced_address;
|
||||
|
||||
/* sanity check - make sure the descriptor is large enough to hold the target address */
|
||||
if (reduced_address < desc->len)
|
||||
return desc;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
*offset = 0;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void rc_libretro_memory_init_from_memory_map(rc_libretro_memory_regions_t* regions, const struct retro_memory_map* mmap,
|
||||
const rc_memory_regions_t* console_regions) {
|
||||
char description[64];
|
||||
unsigned i;
|
||||
unsigned char* region_start;
|
||||
unsigned char* desc_start;
|
||||
size_t desc_size;
|
||||
size_t offset;
|
||||
|
||||
for (i = 0; i < console_regions->num_regions; ++i) {
|
||||
const rc_memory_region_t* console_region = &console_regions->region[i];
|
||||
size_t console_region_size = console_region->end_address - console_region->start_address + 1;
|
||||
unsigned real_address = console_region->real_address;
|
||||
unsigned disconnect_size = 0;
|
||||
|
||||
while (console_region_size > 0) {
|
||||
const struct retro_memory_descriptor* desc = rc_libretro_memory_get_descriptor(mmap, real_address, &offset);
|
||||
if (!desc) {
|
||||
if (rc_libretro_verbose_message_callback && console_region->type != RC_MEMORY_TYPE_UNUSED) {
|
||||
snprintf(description, sizeof(description), "Could not map region starting at $%06X",
|
||||
real_address - console_region->real_address + console_region->start_address);
|
||||
rc_libretro_verbose(description);
|
||||
}
|
||||
|
||||
if (disconnect_size && console_region_size > disconnect_size) {
|
||||
rc_libretro_memory_register_region(regions, console_region->type, NULL, disconnect_size, "null filler");
|
||||
console_region_size -= disconnect_size;
|
||||
real_address += disconnect_size;
|
||||
disconnect_size = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
rc_libretro_memory_register_region(regions, console_region->type, NULL, console_region_size, "null filler");
|
||||
break;
|
||||
}
|
||||
|
||||
snprintf(description, sizeof(description), "descriptor %u, offset 0x%06X%s",
|
||||
(unsigned)(desc - mmap->descriptors) + 1, (int)offset, desc->ptr ? "" : " [no pointer]");
|
||||
|
||||
if (desc->ptr) {
|
||||
desc_start = (uint8_t*)desc->ptr + desc->offset;
|
||||
region_start = desc_start + offset;
|
||||
}
|
||||
else {
|
||||
region_start = NULL;
|
||||
}
|
||||
|
||||
desc_size = desc->len - offset;
|
||||
if (desc->disconnect && desc_size > desc->disconnect) {
|
||||
/* if we need to extract a disconnect bit, the largest block we can read is up to
|
||||
* the next time that bit flips */
|
||||
/* https://stackoverflow.com/questions/12247186/find-the-lowest-set-bit */
|
||||
disconnect_size = (desc->disconnect & -((int)desc->disconnect));
|
||||
desc_size = disconnect_size - (real_address & (disconnect_size - 1));
|
||||
}
|
||||
|
||||
if (console_region_size > desc_size) {
|
||||
if (desc_size == 0) {
|
||||
if (rc_libretro_verbose_message_callback && console_region->type != RC_MEMORY_TYPE_UNUSED) {
|
||||
snprintf(description, sizeof(description), "Could not map region starting at $%06X",
|
||||
real_address - console_region->real_address + console_region->start_address);
|
||||
rc_libretro_verbose(description);
|
||||
}
|
||||
|
||||
rc_libretro_memory_register_region(regions, console_region->type, NULL, console_region_size, "null filler");
|
||||
console_region_size = 0;
|
||||
}
|
||||
else {
|
||||
rc_libretro_memory_register_region(regions, console_region->type, region_start, desc_size, description);
|
||||
console_region_size -= desc_size;
|
||||
real_address += (unsigned)desc_size;
|
||||
}
|
||||
}
|
||||
else {
|
||||
rc_libretro_memory_register_region(regions, console_region->type, region_start, console_region_size, description);
|
||||
console_region_size = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static unsigned rc_libretro_memory_console_region_to_ram_type(int region_type) {
|
||||
switch (region_type)
|
||||
{
|
||||
case RC_MEMORY_TYPE_SAVE_RAM:
|
||||
return RETRO_MEMORY_SAVE_RAM;
|
||||
case RC_MEMORY_TYPE_VIDEO_RAM:
|
||||
return RETRO_MEMORY_VIDEO_RAM;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return RETRO_MEMORY_SYSTEM_RAM;
|
||||
}
|
||||
|
||||
static void rc_libretro_memory_init_from_unmapped_memory(rc_libretro_memory_regions_t* regions,
|
||||
rc_libretro_get_core_memory_info_func get_core_memory_info, const rc_memory_regions_t* console_regions) {
|
||||
char description[64];
|
||||
unsigned i, j;
|
||||
rc_libretro_core_memory_info_t info;
|
||||
size_t offset;
|
||||
|
||||
for (i = 0; i < console_regions->num_regions; ++i) {
|
||||
const rc_memory_region_t* console_region = &console_regions->region[i];
|
||||
const size_t console_region_size = console_region->end_address - console_region->start_address + 1;
|
||||
const unsigned type = rc_libretro_memory_console_region_to_ram_type(console_region->type);
|
||||
unsigned base_address = 0;
|
||||
|
||||
for (j = 0; j <= i; ++j) {
|
||||
const rc_memory_region_t* console_region2 = &console_regions->region[j];
|
||||
if (rc_libretro_memory_console_region_to_ram_type(console_region2->type) == type) {
|
||||
base_address = console_region2->start_address;
|
||||
break;
|
||||
}
|
||||
}
|
||||
offset = console_region->start_address - base_address;
|
||||
|
||||
get_core_memory_info(type, &info);
|
||||
|
||||
if (offset < info.size) {
|
||||
info.size -= offset;
|
||||
|
||||
if (info.data) {
|
||||
snprintf(description, sizeof(description), "offset 0x%06X", (int)offset);
|
||||
info.data += offset;
|
||||
}
|
||||
else {
|
||||
snprintf(description, sizeof(description), "null filler");
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (rc_libretro_verbose_message_callback && console_region->type != RC_MEMORY_TYPE_UNUSED) {
|
||||
snprintf(description, sizeof(description), "Could not map region starting at $%06X", console_region->start_address);
|
||||
rc_libretro_verbose(description);
|
||||
}
|
||||
|
||||
info.data = NULL;
|
||||
info.size = 0;
|
||||
}
|
||||
|
||||
if (console_region_size > info.size) {
|
||||
/* want more than what is available, take what we can and null fill the rest */
|
||||
rc_libretro_memory_register_region(regions, console_region->type, info.data, info.size, description);
|
||||
rc_libretro_memory_register_region(regions, console_region->type, NULL, console_region_size - info.size, "null filler");
|
||||
}
|
||||
else {
|
||||
/* only take as much as we need */
|
||||
rc_libretro_memory_register_region(regions, console_region->type, info.data, console_region_size, description);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int rc_libretro_memory_init(rc_libretro_memory_regions_t* regions, const struct retro_memory_map* mmap,
|
||||
rc_libretro_get_core_memory_info_func get_core_memory_info, int console_id) {
|
||||
const rc_memory_regions_t* console_regions = rc_console_memory_regions(console_id);
|
||||
rc_libretro_memory_regions_t new_regions;
|
||||
int has_valid_region = 0;
|
||||
unsigned i;
|
||||
|
||||
if (!regions)
|
||||
return 0;
|
||||
|
||||
memset(&new_regions, 0, sizeof(new_regions));
|
||||
|
||||
if (console_regions == NULL || console_regions->num_regions == 0)
|
||||
rc_libretro_memory_init_without_regions(&new_regions, get_core_memory_info);
|
||||
else if (mmap && mmap->num_descriptors != 0)
|
||||
rc_libretro_memory_init_from_memory_map(&new_regions, mmap, console_regions);
|
||||
else
|
||||
rc_libretro_memory_init_from_unmapped_memory(&new_regions, get_core_memory_info, console_regions);
|
||||
|
||||
/* determine if any valid regions were found */
|
||||
for (i = 0; i < new_regions.count; i++) {
|
||||
if (new_regions.data[i]) {
|
||||
has_valid_region = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
memcpy(regions, &new_regions, sizeof(*regions));
|
||||
return has_valid_region;
|
||||
}
|
||||
|
||||
void rc_libretro_memory_destroy(rc_libretro_memory_regions_t* regions) {
|
||||
memset(regions, 0, sizeof(*regions));
|
||||
}
|
||||
|
||||
void rc_libretro_hash_set_init(struct rc_libretro_hash_set_t* hash_set,
|
||||
const char* m3u_path, rc_libretro_get_image_path_func get_image_path) {
|
||||
char image_path[1024];
|
||||
char* m3u_contents;
|
||||
char* ptr;
|
||||
int64_t file_len;
|
||||
void* file_handle;
|
||||
int index = 0;
|
||||
|
||||
memset(hash_set, 0, sizeof(*hash_set));
|
||||
|
||||
if (!rc_path_compare_extension(m3u_path, "m3u"))
|
||||
return;
|
||||
|
||||
file_handle = rc_file_open(m3u_path);
|
||||
if (!file_handle)
|
||||
{
|
||||
rc_hash_error("Could not open playlist");
|
||||
return;
|
||||
}
|
||||
|
||||
rc_file_seek(file_handle, 0, SEEK_END);
|
||||
file_len = rc_file_tell(file_handle);
|
||||
rc_file_seek(file_handle, 0, SEEK_SET);
|
||||
|
||||
m3u_contents = (char*)malloc((size_t)file_len + 1);
|
||||
rc_file_read(file_handle, m3u_contents, (int)file_len);
|
||||
m3u_contents[file_len] = '\0';
|
||||
|
||||
rc_file_close(file_handle);
|
||||
|
||||
ptr = m3u_contents;
|
||||
do
|
||||
{
|
||||
/* ignore whitespace */
|
||||
while (isspace((int)*ptr))
|
||||
++ptr;
|
||||
|
||||
if (*ptr == '#')
|
||||
{
|
||||
/* ignore comment unless it's the special SAVEDISK extension */
|
||||
if (memcmp(ptr, "#SAVEDISK:", 10) == 0)
|
||||
{
|
||||
/* get the path to the save disk from the frontend, assign it a bogus hash so
|
||||
* it doesn't get hashed later */
|
||||
if (get_image_path(index, image_path, sizeof(image_path)))
|
||||
{
|
||||
const char save_disk_hash[33] = "[SAVE DISK]";
|
||||
rc_libretro_hash_set_add(hash_set, image_path, -1, save_disk_hash);
|
||||
++index;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
/* non-empty line, tally a file */
|
||||
++index;
|
||||
}
|
||||
|
||||
/* find the end of the line */
|
||||
while (*ptr && *ptr != '\n')
|
||||
++ptr;
|
||||
|
||||
} while (*ptr);
|
||||
|
||||
free(m3u_contents);
|
||||
|
||||
if (hash_set->entries_count > 0)
|
||||
{
|
||||
/* at least one save disk was found. make sure the core supports the #SAVEDISK: extension by
|
||||
* asking for the last expected disk. if it's not found, assume no #SAVEDISK: support */
|
||||
if (!get_image_path(index - 1, image_path, sizeof(image_path)))
|
||||
hash_set->entries_count = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void rc_libretro_hash_set_destroy(struct rc_libretro_hash_set_t* hash_set) {
|
||||
if (hash_set->entries)
|
||||
free(hash_set->entries);
|
||||
memset(hash_set, 0, sizeof(*hash_set));
|
||||
}
|
||||
|
||||
static unsigned rc_libretro_djb2(const char* input)
|
||||
{
|
||||
unsigned result = 5381;
|
||||
char c;
|
||||
|
||||
while ((c = *input++) != '\0')
|
||||
result = ((result << 5) + result) + c; /* result = result * 33 + c */
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void rc_libretro_hash_set_add(struct rc_libretro_hash_set_t* hash_set,
|
||||
const char* path, int game_id, const char hash[33]) {
|
||||
const unsigned path_djb2 = (path != NULL) ? rc_libretro_djb2(path) : 0;
|
||||
struct rc_libretro_hash_entry_t* entry = NULL;
|
||||
struct rc_libretro_hash_entry_t* scan;
|
||||
struct rc_libretro_hash_entry_t* stop = hash_set->entries + hash_set->entries_count;;
|
||||
|
||||
if (path_djb2)
|
||||
{
|
||||
/* attempt to match the path */
|
||||
for (scan = hash_set->entries; scan < stop; ++scan)
|
||||
{
|
||||
if (scan->path_djb2 == path_djb2)
|
||||
{
|
||||
entry = scan;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!entry)
|
||||
{
|
||||
/* entry not found, allocate a new one */
|
||||
if (hash_set->entries_size == 0)
|
||||
{
|
||||
hash_set->entries_size = 4;
|
||||
hash_set->entries = (struct rc_libretro_hash_entry_t*)
|
||||
malloc(hash_set->entries_size * sizeof(struct rc_libretro_hash_entry_t));
|
||||
}
|
||||
else if (hash_set->entries_count == hash_set->entries_size)
|
||||
{
|
||||
hash_set->entries_size += 4;
|
||||
hash_set->entries = (struct rc_libretro_hash_entry_t*)realloc(hash_set->entries,
|
||||
hash_set->entries_size * sizeof(struct rc_libretro_hash_entry_t));
|
||||
}
|
||||
|
||||
entry = hash_set->entries + hash_set->entries_count++;
|
||||
}
|
||||
|
||||
/* update the entry */
|
||||
entry->path_djb2 = path_djb2;
|
||||
entry->game_id = game_id;
|
||||
memcpy(entry->hash, hash, sizeof(entry->hash));
|
||||
}
|
||||
|
||||
const char* rc_libretro_hash_set_get_hash(const struct rc_libretro_hash_set_t* hash_set, const char* path)
|
||||
{
|
||||
const unsigned path_djb2 = rc_libretro_djb2(path);
|
||||
struct rc_libretro_hash_entry_t* scan = hash_set->entries;
|
||||
struct rc_libretro_hash_entry_t* stop = scan + hash_set->entries_count;
|
||||
for (; scan < stop; ++scan)
|
||||
{
|
||||
if (scan->path_djb2 == path_djb2)
|
||||
return scan->hash;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int rc_libretro_hash_set_get_game_id(const struct rc_libretro_hash_set_t* hash_set, const char* hash)
|
||||
{
|
||||
struct rc_libretro_hash_entry_t* scan = hash_set->entries;
|
||||
struct rc_libretro_hash_entry_t* stop = scan + hash_set->entries_count;
|
||||
for (; scan < stop; ++scan)
|
||||
{
|
||||
if (memcmp(scan->hash, hash, sizeof(scan->hash)) == 0)
|
||||
return scan->game_id;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,92 @@
|
|||
#ifndef RC_LIBRETRO_H
|
||||
#define RC_LIBRETRO_H
|
||||
|
||||
/* this file comes from the libretro repository, which is not an explicit submodule.
|
||||
* the integration must set up paths appropriately to find it. */
|
||||
#include <libretro.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/*****************************************************************************\
|
||||
| Disallowed Settings |
|
||||
\*****************************************************************************/
|
||||
|
||||
typedef struct rc_disallowed_setting_t
|
||||
{
|
||||
const char* setting;
|
||||
const char* value;
|
||||
} rc_disallowed_setting_t;
|
||||
|
||||
const rc_disallowed_setting_t* rc_libretro_get_disallowed_settings(const char* library_name);
|
||||
int rc_libretro_is_setting_allowed(const rc_disallowed_setting_t* disallowed_settings, const char* setting, const char* value);
|
||||
int rc_libretro_is_system_allowed(const char* library_name, int console_id);
|
||||
|
||||
/*****************************************************************************\
|
||||
| Memory Mapping |
|
||||
\*****************************************************************************/
|
||||
|
||||
/* specifies a function to call for verbose logging */
|
||||
typedef void (*rc_libretro_message_callback)(const char*);
|
||||
void rc_libretro_init_verbose_message_callback(rc_libretro_message_callback callback);
|
||||
|
||||
#define RC_LIBRETRO_MAX_MEMORY_REGIONS 32
|
||||
typedef struct rc_libretro_memory_regions_t
|
||||
{
|
||||
unsigned char* data[RC_LIBRETRO_MAX_MEMORY_REGIONS];
|
||||
size_t size[RC_LIBRETRO_MAX_MEMORY_REGIONS];
|
||||
size_t total_size;
|
||||
unsigned count;
|
||||
} rc_libretro_memory_regions_t;
|
||||
|
||||
typedef struct rc_libretro_core_memory_info_t
|
||||
{
|
||||
unsigned char* data;
|
||||
size_t size;
|
||||
} rc_libretro_core_memory_info_t;
|
||||
|
||||
typedef void (*rc_libretro_get_core_memory_info_func)(unsigned id, rc_libretro_core_memory_info_t* info);
|
||||
|
||||
int rc_libretro_memory_init(rc_libretro_memory_regions_t* regions, const struct retro_memory_map* mmap,
|
||||
rc_libretro_get_core_memory_info_func get_core_memory_info, int console_id);
|
||||
void rc_libretro_memory_destroy(rc_libretro_memory_regions_t* regions);
|
||||
|
||||
unsigned char* rc_libretro_memory_find(const rc_libretro_memory_regions_t* regions, unsigned address);
|
||||
unsigned char* rc_libretro_memory_find_avail(const rc_libretro_memory_regions_t* regions, unsigned address, unsigned* avail);
|
||||
uint32_t rc_libretro_memory_read(const rc_libretro_memory_regions_t* regions, unsigned address, uint8_t* buffer, uint32_t num_bytes);
|
||||
|
||||
/*****************************************************************************\
|
||||
| Disk Identification |
|
||||
\*****************************************************************************/
|
||||
|
||||
typedef struct rc_libretro_hash_entry_t
|
||||
{
|
||||
uint32_t path_djb2;
|
||||
int game_id;
|
||||
char hash[33];
|
||||
} rc_libretro_hash_entry_t;
|
||||
|
||||
typedef struct rc_libretro_hash_set_t
|
||||
{
|
||||
struct rc_libretro_hash_entry_t* entries;
|
||||
uint16_t entries_count;
|
||||
uint16_t entries_size;
|
||||
} rc_libretro_hash_set_t;
|
||||
|
||||
typedef int (*rc_libretro_get_image_path_func)(unsigned index, char* buffer, size_t buffer_size);
|
||||
|
||||
void rc_libretro_hash_set_init(struct rc_libretro_hash_set_t* hash_set,
|
||||
const char* m3u_path, rc_libretro_get_image_path_func get_image_path);
|
||||
void rc_libretro_hash_set_destroy(struct rc_libretro_hash_set_t* hash_set);
|
||||
|
||||
void rc_libretro_hash_set_add(struct rc_libretro_hash_set_t* hash_set,
|
||||
const char* path, int game_id, const char hash[33]);
|
||||
const char* rc_libretro_hash_set_get_hash(const struct rc_libretro_hash_set_t* hash_set, const char* path);
|
||||
int rc_libretro_hash_set_get_game_id(const struct rc_libretro_hash_set_t* hash_set, const char* hash);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* RC_LIBRETRO_H */
|
|
@ -0,0 +1,847 @@
|
|||
#include "rc_validate.h"
|
||||
|
||||
#include "rc_compat.h"
|
||||
#include "rc_consoles.h"
|
||||
#include "rc_internal.h"
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
static int rc_validate_memref(const rc_memref_t* memref, char result[], const size_t result_size, int console_id, unsigned max_address)
|
||||
{
|
||||
if (memref->address > max_address) {
|
||||
snprintf(result, result_size, "Address %04X out of range (max %04X)", memref->address, max_address);
|
||||
return 0;
|
||||
}
|
||||
|
||||
switch (console_id) {
|
||||
case RC_CONSOLE_NINTENDO:
|
||||
if (memref->address >= 0x0800 && memref->address <= 0x1FFF) {
|
||||
snprintf(result, result_size, "Mirror RAM may not be exposed by emulator (address %04X)", memref->address);
|
||||
return 0;
|
||||
}
|
||||
break;
|
||||
|
||||
case RC_CONSOLE_GAMEBOY:
|
||||
case RC_CONSOLE_GAMEBOY_COLOR:
|
||||
if (memref->address >= 0xE000 && memref->address <= 0xFDFF) {
|
||||
snprintf(result, result_size, "Echo RAM may not be exposed by emulator (address %04X)", memref->address);
|
||||
return 0;
|
||||
}
|
||||
break;
|
||||
|
||||
case RC_CONSOLE_PLAYSTATION:
|
||||
if (memref->address <= 0xFFFF) {
|
||||
snprintf(result, result_size, "Kernel RAM may not be initialized without real BIOS (address %04X)", memref->address);
|
||||
return 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
int rc_validate_memrefs(const rc_memref_t* memref, char result[], const size_t result_size, unsigned max_address)
|
||||
{
|
||||
while (memref) {
|
||||
if (!rc_validate_memref(memref, result, result_size, 0, max_address))
|
||||
return 0;
|
||||
|
||||
memref = memref->next;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static unsigned rc_console_max_address(int console_id)
|
||||
{
|
||||
const rc_memory_regions_t* memory_regions;
|
||||
memory_regions = rc_console_memory_regions(console_id);
|
||||
if (memory_regions && memory_regions->num_regions > 0)
|
||||
return memory_regions->region[memory_regions->num_regions - 1].end_address;
|
||||
|
||||
return 0xFFFFFFFF;
|
||||
}
|
||||
|
||||
int rc_validate_memrefs_for_console(const rc_memref_t* memref, char result[], const size_t result_size, int console_id)
|
||||
{
|
||||
const unsigned max_address = rc_console_max_address(console_id);
|
||||
while (memref) {
|
||||
if (!rc_validate_memref(memref, result, result_size, console_id, max_address))
|
||||
return 0;
|
||||
|
||||
memref = memref->next;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static unsigned rc_max_value(const rc_operand_t* operand)
|
||||
{
|
||||
if (operand->type == RC_OPERAND_CONST)
|
||||
return operand->value.num;
|
||||
|
||||
if (!rc_operand_is_memref(operand))
|
||||
return 0xFFFFFFFF;
|
||||
|
||||
switch (operand->size) {
|
||||
case RC_MEMSIZE_BIT_0:
|
||||
case RC_MEMSIZE_BIT_1:
|
||||
case RC_MEMSIZE_BIT_2:
|
||||
case RC_MEMSIZE_BIT_3:
|
||||
case RC_MEMSIZE_BIT_4:
|
||||
case RC_MEMSIZE_BIT_5:
|
||||
case RC_MEMSIZE_BIT_6:
|
||||
case RC_MEMSIZE_BIT_7:
|
||||
return 1;
|
||||
|
||||
case RC_MEMSIZE_LOW:
|
||||
case RC_MEMSIZE_HIGH:
|
||||
return 0xF;
|
||||
|
||||
case RC_MEMSIZE_BITCOUNT:
|
||||
return 8;
|
||||
|
||||
case RC_MEMSIZE_8_BITS:
|
||||
return (operand->type == RC_OPERAND_BCD) ? 165 : 0xFF;
|
||||
|
||||
case RC_MEMSIZE_16_BITS:
|
||||
case RC_MEMSIZE_16_BITS_BE:
|
||||
return (operand->type == RC_OPERAND_BCD) ? 16665 : 0xFFFF;
|
||||
|
||||
case RC_MEMSIZE_24_BITS:
|
||||
case RC_MEMSIZE_24_BITS_BE:
|
||||
return (operand->type == RC_OPERAND_BCD) ? 1666665 : 0xFFFFFF;
|
||||
|
||||
default:
|
||||
return (operand->type == RC_OPERAND_BCD) ? 166666665 : 0xFFFFFFFF;
|
||||
}
|
||||
}
|
||||
|
||||
static unsigned rc_scale_value(unsigned value, char oper, const rc_operand_t* operand)
|
||||
{
|
||||
switch (oper) {
|
||||
case RC_OPERATOR_MULT:
|
||||
{
|
||||
unsigned long long scaled = ((unsigned long long)value) * rc_max_value(operand);
|
||||
if (scaled > 0xFFFFFFFF)
|
||||
return 0xFFFFFFFF;
|
||||
|
||||
return (unsigned)scaled;
|
||||
}
|
||||
|
||||
case RC_OPERATOR_DIV:
|
||||
{
|
||||
const unsigned min_val = (operand->type == RC_OPERAND_CONST) ? operand->value.num : 1;
|
||||
return value / min_val;
|
||||
}
|
||||
|
||||
case RC_OPERATOR_AND:
|
||||
return rc_max_value(operand);
|
||||
|
||||
case RC_OPERATOR_XOR:
|
||||
return value | rc_max_value(operand);
|
||||
|
||||
default:
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
static int rc_validate_get_condition_index(const rc_condset_t* condset, const rc_condition_t* condition)
|
||||
{
|
||||
int index = 1;
|
||||
const rc_condition_t* scan;
|
||||
for (scan = condset->conditions; scan != NULL; scan = scan->next)
|
||||
{
|
||||
if (scan == condition)
|
||||
return index;
|
||||
|
||||
++index;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rc_validate_range(unsigned min_val, unsigned max_val, char oper, unsigned max, char result[], const size_t result_size)
|
||||
{
|
||||
switch (oper) {
|
||||
case RC_OPERATOR_AND:
|
||||
if (min_val > max) {
|
||||
snprintf(result, result_size, "Mask has more bits than source");
|
||||
return 0;
|
||||
}
|
||||
else if (min_val == 0 && max_val == 0) {
|
||||
snprintf(result, result_size, "Result of mask always 0");
|
||||
return 0;
|
||||
}
|
||||
break;
|
||||
|
||||
case RC_OPERATOR_EQ:
|
||||
if (min_val > max) {
|
||||
snprintf(result, result_size, "Comparison is never true");
|
||||
return 0;
|
||||
}
|
||||
break;
|
||||
|
||||
case RC_OPERATOR_NE:
|
||||
if (min_val > max) {
|
||||
snprintf(result, result_size, "Comparison is always true");
|
||||
return 0;
|
||||
}
|
||||
break;
|
||||
|
||||
case RC_OPERATOR_GE:
|
||||
if (min_val > max) {
|
||||
snprintf(result, result_size, "Comparison is never true");
|
||||
return 0;
|
||||
}
|
||||
if (max_val == 0) {
|
||||
snprintf(result, result_size, "Comparison is always true");
|
||||
return 0;
|
||||
}
|
||||
break;
|
||||
|
||||
case RC_OPERATOR_GT:
|
||||
if (min_val >= max) {
|
||||
snprintf(result, result_size, "Comparison is never true");
|
||||
return 0;
|
||||
}
|
||||
break;
|
||||
|
||||
case RC_OPERATOR_LE:
|
||||
if (min_val >= max) {
|
||||
snprintf(result, result_size, "Comparison is always true");
|
||||
return 0;
|
||||
}
|
||||
break;
|
||||
|
||||
case RC_OPERATOR_LT:
|
||||
if (min_val > max) {
|
||||
snprintf(result, result_size, "Comparison is always true");
|
||||
return 0;
|
||||
}
|
||||
if (max_val == 0) {
|
||||
snprintf(result, result_size, "Comparison is never true");
|
||||
return 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
int rc_validate_condset_internal(const rc_condset_t* condset, char result[], const size_t result_size, int console_id, unsigned max_address)
|
||||
{
|
||||
const rc_condition_t* cond;
|
||||
char buffer[128];
|
||||
unsigned max_val;
|
||||
int index = 1;
|
||||
unsigned long long add_source_max = 0;
|
||||
int in_add_hits = 0;
|
||||
int in_add_address = 0;
|
||||
int is_combining = 0;
|
||||
|
||||
if (!condset) {
|
||||
*result = '\0';
|
||||
return 1;
|
||||
}
|
||||
|
||||
for (cond = condset->conditions; cond; cond = cond->next, ++index) {
|
||||
unsigned max = rc_max_value(&cond->operand1);
|
||||
const int is_memref1 = rc_operand_is_memref(&cond->operand1);
|
||||
const int is_memref2 = rc_operand_is_memref(&cond->operand2);
|
||||
|
||||
if (!in_add_address) {
|
||||
if (is_memref1 && !rc_validate_memref(cond->operand1.value.memref, buffer, sizeof(buffer), console_id, max_address)) {
|
||||
snprintf(result, result_size, "Condition %d: %s", index, buffer);
|
||||
return 0;
|
||||
}
|
||||
if (is_memref2 && !rc_validate_memref(cond->operand2.value.memref, buffer, sizeof(buffer), console_id, max_address)) {
|
||||
snprintf(result, result_size, "Condition %d: %s", index, buffer);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
else {
|
||||
in_add_address = 0;
|
||||
}
|
||||
|
||||
switch (cond->type) {
|
||||
case RC_CONDITION_ADD_SOURCE:
|
||||
max = rc_scale_value(max, cond->oper, &cond->operand2);
|
||||
add_source_max += max;
|
||||
is_combining = 1;
|
||||
continue;
|
||||
|
||||
case RC_CONDITION_SUB_SOURCE:
|
||||
max = rc_scale_value(max, cond->oper, &cond->operand2);
|
||||
if (add_source_max < max) /* potential underflow - may be expected */
|
||||
add_source_max = 0xFFFFFFFF;
|
||||
is_combining = 1;
|
||||
continue;
|
||||
|
||||
case RC_CONDITION_ADD_ADDRESS:
|
||||
if (cond->operand1.type == RC_OPERAND_DELTA || cond->operand1.type == RC_OPERAND_PRIOR) {
|
||||
snprintf(result, result_size, "Condition %d: Using pointer from previous frame", index);
|
||||
return 0;
|
||||
}
|
||||
in_add_address = 1;
|
||||
is_combining = 1;
|
||||
continue;
|
||||
|
||||
case RC_CONDITION_ADD_HITS:
|
||||
case RC_CONDITION_SUB_HITS:
|
||||
in_add_hits = 1;
|
||||
is_combining = 1;
|
||||
break;
|
||||
|
||||
case RC_CONDITION_AND_NEXT:
|
||||
case RC_CONDITION_OR_NEXT:
|
||||
case RC_CONDITION_RESET_NEXT_IF:
|
||||
is_combining = 1;
|
||||
break;
|
||||
|
||||
default:
|
||||
if (in_add_hits) {
|
||||
if (cond->required_hits == 0) {
|
||||
snprintf(result, result_size, "Condition %d: Final condition in AddHits chain must have a hit target", index);
|
||||
return 0;
|
||||
}
|
||||
|
||||
in_add_hits = 0;
|
||||
}
|
||||
|
||||
is_combining = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
/* if we're in an add source chain, check for overflow */
|
||||
if (add_source_max) {
|
||||
const unsigned long long overflow = add_source_max + max;
|
||||
if (overflow > 0xFFFFFFFFUL)
|
||||
max = 0xFFFFFFFF;
|
||||
else
|
||||
max += (unsigned)add_source_max;
|
||||
}
|
||||
|
||||
/* check for comparing two differently sized memrefs */
|
||||
max_val = rc_max_value(&cond->operand2);
|
||||
if (max_val != max && add_source_max == 0 && is_memref1 && is_memref2) {
|
||||
snprintf(result, result_size, "Condition %d: Comparing different memory sizes", index);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* if either side is a memref, or there's a running add source chain, check for impossible comparisons */
|
||||
if (is_memref1 || is_memref2 || add_source_max) {
|
||||
const size_t prefix_length = snprintf(result, result_size, "Condition %d: ", index);
|
||||
|
||||
unsigned min_val;
|
||||
switch (cond->operand2.type) {
|
||||
case RC_OPERAND_CONST:
|
||||
min_val = cond->operand2.value.num;
|
||||
break;
|
||||
|
||||
case RC_OPERAND_FP:
|
||||
min_val = (int)cond->operand2.value.dbl;
|
||||
|
||||
/* cannot compare an integer memory reference to a non-integral floating point value */
|
||||
/* assert: is_memref1 (because operand2==FP means !is_memref2) */
|
||||
if (!add_source_max && !rc_operand_is_float_memref(&cond->operand1) &&
|
||||
(float)min_val != cond->operand2.value.dbl) {
|
||||
snprintf(result + prefix_length, result_size - prefix_length, "Comparison is never true");
|
||||
return 0;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
min_val = 0;
|
||||
|
||||
/* cannot compare an integer memory reference to a non-integral floating point value */
|
||||
/* assert: is_memref2 (because operand1==FP means !is_memref1) */
|
||||
if (cond->operand1.type == RC_OPERAND_FP && !add_source_max && !rc_operand_is_float_memref(&cond->operand2) &&
|
||||
(float)((int)cond->operand1.value.dbl) != cond->operand1.value.dbl) {
|
||||
snprintf(result + prefix_length, result_size - prefix_length, "Comparison is never true");
|
||||
return 0;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (rc_operand_is_float(&cond->operand2) && rc_operand_is_float(&cond->operand1)) {
|
||||
/* both sides are floats, don't validate range*/
|
||||
} else if (!rc_validate_range(min_val, max_val, cond->oper, max, result + prefix_length, result_size - prefix_length)) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
add_source_max = 0;
|
||||
}
|
||||
|
||||
if (is_combining) {
|
||||
snprintf(result, result_size, "Final condition type expects another condition to follow");
|
||||
return 0;
|
||||
}
|
||||
|
||||
*result = '\0';
|
||||
return 1;
|
||||
}
|
||||
|
||||
int rc_validate_condset(const rc_condset_t* condset, char result[], const size_t result_size, unsigned max_address)
|
||||
{
|
||||
return rc_validate_condset_internal(condset, result, result_size, 0, max_address);
|
||||
}
|
||||
|
||||
int rc_validate_condset_for_console(const rc_condset_t* condset, char result[], const size_t result_size, int console_id)
|
||||
{
|
||||
const unsigned max_address = rc_console_max_address(console_id);
|
||||
return rc_validate_condset_internal(condset, result, result_size, console_id, max_address);
|
||||
}
|
||||
|
||||
static int rc_validate_is_combining_condition(const rc_condition_t* condition)
|
||||
{
|
||||
switch (condition->type)
|
||||
{
|
||||
case RC_CONDITION_ADD_ADDRESS:
|
||||
case RC_CONDITION_ADD_HITS:
|
||||
case RC_CONDITION_ADD_SOURCE:
|
||||
case RC_CONDITION_AND_NEXT:
|
||||
case RC_CONDITION_OR_NEXT:
|
||||
case RC_CONDITION_RESET_NEXT_IF:
|
||||
case RC_CONDITION_SUB_HITS:
|
||||
case RC_CONDITION_SUB_SOURCE:
|
||||
return 1;
|
||||
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static const rc_condition_t* rc_validate_next_non_combining_condition(const rc_condition_t* condition)
|
||||
{
|
||||
int is_combining = rc_validate_is_combining_condition(condition);
|
||||
for (condition = condition->next; condition != NULL; condition = condition->next)
|
||||
{
|
||||
if (rc_validate_is_combining_condition(condition))
|
||||
is_combining = 1;
|
||||
else if (is_combining)
|
||||
is_combining = 0;
|
||||
else
|
||||
return condition;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int rc_validate_get_opposite_comparison(int oper)
|
||||
{
|
||||
switch (oper)
|
||||
{
|
||||
case RC_OPERATOR_EQ: return RC_OPERATOR_NE;
|
||||
case RC_OPERATOR_NE: return RC_OPERATOR_EQ;
|
||||
case RC_OPERATOR_LT: return RC_OPERATOR_GE;
|
||||
case RC_OPERATOR_LE: return RC_OPERATOR_GT;
|
||||
case RC_OPERATOR_GT: return RC_OPERATOR_LE;
|
||||
case RC_OPERATOR_GE: return RC_OPERATOR_LT;
|
||||
default: return oper;
|
||||
}
|
||||
}
|
||||
|
||||
static const rc_operand_t* rc_validate_get_comparison(const rc_condition_t* condition, int* comparison, unsigned* value)
|
||||
{
|
||||
if (rc_operand_is_memref(&condition->operand1))
|
||||
{
|
||||
if (condition->operand2.type != RC_OPERAND_CONST)
|
||||
return NULL;
|
||||
|
||||
*comparison = condition->oper;
|
||||
*value = condition->operand2.value.num;
|
||||
return &condition->operand1;
|
||||
}
|
||||
|
||||
if (condition->operand1.type != RC_OPERAND_CONST)
|
||||
return NULL;
|
||||
|
||||
if (!rc_operand_is_memref(&condition->operand2))
|
||||
return NULL;
|
||||
|
||||
*comparison = rc_validate_get_opposite_comparison(condition->oper);
|
||||
*value = condition->operand1.value.num;
|
||||
return &condition->operand2;
|
||||
}
|
||||
|
||||
enum {
|
||||
RC_OVERLAP_NONE = 0,
|
||||
RC_OVERLAP_CONFLICTING,
|
||||
RC_OVERLAP_REDUNDANT,
|
||||
RC_OVERLAP_DEFER
|
||||
};
|
||||
|
||||
static int rc_validate_comparison_overlap(int comparison1, unsigned value1, int comparison2, unsigned value2)
|
||||
{
|
||||
/* NOTE: this only cares if comp2 conflicts with comp1.
|
||||
* If comp1 conflicts with comp2, we'll catch that later (return RC_OVERLAP_NONE for now) */
|
||||
switch (comparison2)
|
||||
{
|
||||
case RC_OPERATOR_EQ:
|
||||
switch (comparison1) /* comp1 comp2 comp1 comp2 comp1 comp2 */
|
||||
{ /* value1 = value2 value1 < value2 value1 > value2 */
|
||||
case RC_OPERATOR_EQ: /* a == 1 && a == 1 | a == 1 && a == 2 | a == 2 && a == 1 */
|
||||
/* redundant conflict conflict */
|
||||
return (value1 == value2) ? RC_OVERLAP_REDUNDANT : RC_OVERLAP_CONFLICTING;
|
||||
case RC_OPERATOR_LE: /* a <= 1 && a == 1 | a <= 1 && a == 2 | a <= 2 && a == 1 */
|
||||
/* defer conflict defer */
|
||||
return (value1 < value2) ? RC_OVERLAP_CONFLICTING : RC_OVERLAP_DEFER;
|
||||
case RC_OPERATOR_GE: /* a >= 1 && a == 1 | a >= 1 && a == 2 | a >= 2 && a == 1 */
|
||||
/* defer defer conflict */
|
||||
return (value1 > value2) ? RC_OVERLAP_CONFLICTING : RC_OVERLAP_DEFER;
|
||||
case RC_OPERATOR_NE: /* a != 1 && a == 1 | a != 1 && a == 2 | a != 2 && a == 1 */
|
||||
/* conflict defer defer */
|
||||
return (value1 == value2) ? RC_OVERLAP_CONFLICTING : RC_OVERLAP_DEFER;
|
||||
case RC_OPERATOR_LT: /* a < 1 && a == 1 | a < 1 && a == 2 | a < 2 && a == 1 */
|
||||
/* conflict conflict defer */
|
||||
return (value1 <= value2) ? RC_OVERLAP_CONFLICTING : RC_OVERLAP_DEFER;
|
||||
case RC_OPERATOR_GT: /* a > 1 && a == 1 | a > 1 && a == 2 | a > 2 && a == 1 */
|
||||
/* conflict defer conflict */
|
||||
return (value1 >= value2) ? RC_OVERLAP_CONFLICTING : RC_OVERLAP_DEFER;
|
||||
}
|
||||
break;
|
||||
|
||||
case RC_OPERATOR_NE:
|
||||
switch (comparison1) /* comp1 comp2 comp1 comp2 comp1 comp2 */
|
||||
{ /* value1 = value2 value1 < value2 value1 > value2 */
|
||||
case RC_OPERATOR_EQ: /* a == 1 && a != 1 | a == 1 && a != 2 | a == 2 && a != 1 */
|
||||
/* conflict redundant redundant */
|
||||
return (value1 == value2) ? RC_OVERLAP_CONFLICTING : RC_OVERLAP_REDUNDANT;
|
||||
case RC_OPERATOR_LE: /* a <= 1 && a != 1 | a <= 1 && a != 2 | a <= 2 && a != 1 */
|
||||
/* none redundant none */
|
||||
return (value1 < value2) ? RC_OVERLAP_REDUNDANT : RC_OVERLAP_NONE;
|
||||
case RC_OPERATOR_GE: /* a >= 1 && a != 1 | a >= 1 && a != 2 | a >= 2 && a != 1 */
|
||||
/* none none redundant */
|
||||
return (value1 > value2) ? RC_OVERLAP_REDUNDANT : RC_OVERLAP_NONE;
|
||||
case RC_OPERATOR_NE: /* a != 1 && a != 1 | a != 1 && a != 2 | a != 2 && a != 1 */
|
||||
/* redundant none none */
|
||||
return (value1 == value2) ? RC_OVERLAP_REDUNDANT : RC_OVERLAP_NONE;
|
||||
case RC_OPERATOR_LT: /* a < 1 && a != 1 | a < 1 && a != 2 | a < 2 && a != 1 */
|
||||
/* redundant redundant none */
|
||||
return (value1 <= value2) ? RC_OVERLAP_REDUNDANT : RC_OVERLAP_NONE;
|
||||
case RC_OPERATOR_GT: /* a > 1 && a != 1 | a > 1 && a != 2 | a > 2 && a != 1 */
|
||||
/* redundant none redundant */
|
||||
return (value1 >= value2) ? RC_OVERLAP_REDUNDANT : RC_OVERLAP_NONE;
|
||||
}
|
||||
break;
|
||||
|
||||
case RC_OPERATOR_LT:
|
||||
switch (comparison1) /* comp1 comp2 comp1 comp2 comp1 comp2 */
|
||||
{ /* value1 = value2 value1 < value2 value1 > value2 */
|
||||
case RC_OPERATOR_EQ: /* a == 1 && a < 1 | a == 1 && a < 2 | a == 2 && a < 1 */
|
||||
/* conflict redundant conflict */
|
||||
return (value1 < value2) ? RC_OVERLAP_REDUNDANT : RC_OVERLAP_CONFLICTING;
|
||||
case RC_OPERATOR_LE: /* a <= 1 && a < 1 | a <= 1 && a < 2 | a <= 2 && a < 1 */
|
||||
/* defer redundant defer */
|
||||
return (value1 < value2) ? RC_OVERLAP_REDUNDANT : RC_OVERLAP_DEFER;
|
||||
case RC_OPERATOR_GE: /* a >= 1 && a < 1 | a >= 1 && a < 2 | a >= 2 && a < 1 */
|
||||
/* conflict none conflict */
|
||||
return (value1 >= value2) ? RC_OVERLAP_CONFLICTING : RC_OVERLAP_NONE;
|
||||
case RC_OPERATOR_NE: /* a != 1 && a < 1 | a != 1 && a < 2 | a != 2 && a < 1 */
|
||||
/* defer none defer */
|
||||
return (value1 >= value2) ? RC_OVERLAP_DEFER : RC_OVERLAP_NONE;
|
||||
case RC_OPERATOR_LT: /* a < 1 && a < 1 | a < 1 && a < 2 | a < 2 && a < 1 */
|
||||
/* redundant redundant defer */
|
||||
return (value1 <= value2) ? RC_OVERLAP_REDUNDANT : RC_OVERLAP_DEFER;
|
||||
case RC_OPERATOR_GT: /* a > 1 && a < 1 | a > 1 && a < 2 | a > 2 && a < 1 */
|
||||
/* conflict none conflict */
|
||||
return (value1 >= value2) ? RC_OVERLAP_CONFLICTING : RC_OVERLAP_NONE;
|
||||
}
|
||||
break;
|
||||
|
||||
case RC_OPERATOR_LE:
|
||||
switch (comparison1) /* comp1 comp2 comp1 comp2 comp1 comp2 */
|
||||
{ /* value1 = value2 value1 < value2 value1 > value2 */
|
||||
case RC_OPERATOR_EQ: /* a == 1 && a <= 1 | a == 1 && a <= 2 | a == 2 && a <= 1 */
|
||||
/* redundant redundant conflict */
|
||||
return (value1 <= value2) ? RC_OVERLAP_REDUNDANT : RC_OVERLAP_CONFLICTING;
|
||||
case RC_OPERATOR_LE: /* a <= 1 && a <= 1 | a <= 1 && a <= 2 | a <= 2 && a <= 1 */
|
||||
/* redundant redundant defer */
|
||||
return (value1 <= value2) ? RC_OVERLAP_REDUNDANT : RC_OVERLAP_DEFER;
|
||||
case RC_OPERATOR_GE: /* a >= 1 && a <= 1 | a >= 1 && a <= 2 | a >= 2 && a <= 1 */
|
||||
/* none none conflict */
|
||||
return (value1 > value2) ? RC_OVERLAP_CONFLICTING : RC_OVERLAP_NONE;
|
||||
case RC_OPERATOR_NE: /* a != 1 && a <= 1 | a != 1 && a <= 2 | a != 2 && a <= 1 */
|
||||
/* none none defer */
|
||||
return (value1 > value2) ? RC_OVERLAP_DEFER : RC_OVERLAP_NONE;
|
||||
case RC_OPERATOR_LT: /* a < 1 && a <= 1 | a < 1 && a <= 2 | a < 2 && a <= 1 */
|
||||
/* redundant redundant defer */
|
||||
return (value1 <= value2) ? RC_OVERLAP_REDUNDANT : RC_OVERLAP_DEFER;
|
||||
case RC_OPERATOR_GT: /* a > 1 && a <= 1 | a > 1 && a <= 2 | a > 2 && a <= 1 */
|
||||
/* conflict none conflict */
|
||||
return (value1 >= value2) ? RC_OVERLAP_CONFLICTING : RC_OVERLAP_NONE;
|
||||
}
|
||||
break;
|
||||
|
||||
case RC_OPERATOR_GT:
|
||||
switch (comparison1) /* comp1 comp2 comp1 comp2 comp1 comp2 */
|
||||
{ /* value1 = value2 value1 < value2 value1 > value2 */
|
||||
case RC_OPERATOR_EQ: /* a == 1 && a > 1 | a == 1 && a > 2 | a == 2 && a > 1 */
|
||||
/* conflict conflict redundant */
|
||||
return (value1 > value2) ? RC_OVERLAP_REDUNDANT : RC_OVERLAP_CONFLICTING;
|
||||
case RC_OPERATOR_LE: /* a <= 1 && a > 1 | a <= 1 && a > 2 | a <= 2 && a > 1 */
|
||||
/* conflict conflict defer */
|
||||
return (value1 <= value2) ? RC_OVERLAP_CONFLICTING : RC_OVERLAP_DEFER;
|
||||
case RC_OPERATOR_GE: /* a >= 1 && a > 1 | a >= 1 && a > 2 | a >= 2 && a > 1 */
|
||||
/* defer defer redundant */
|
||||
return (value1 > value2) ? RC_OVERLAP_REDUNDANT : RC_OVERLAP_DEFER;
|
||||
case RC_OPERATOR_NE: /* a != 1 && a > 1 | a != 1 && a > 2 | a != 2 && a > 1 */
|
||||
/* defer defer none */
|
||||
return (value1 <= value2) ? RC_OVERLAP_DEFER : RC_OVERLAP_NONE;
|
||||
case RC_OPERATOR_LT: /* a < 1 && a > 1 | a < 1 && a > 2 | a < 2 && a > 1 */
|
||||
/* conflict conflict none */
|
||||
return (value1 <= value2) ? RC_OVERLAP_CONFLICTING : RC_OVERLAP_NONE;
|
||||
case RC_OPERATOR_GT: /* a > 1 && a > 1 | a > 1 && a > 2 | a > 2 && a > 1 */
|
||||
/* redundant defer redundant */
|
||||
return (value1 >= value2) ? RC_OVERLAP_REDUNDANT : RC_OVERLAP_DEFER;
|
||||
}
|
||||
break;
|
||||
|
||||
case RC_OPERATOR_GE:
|
||||
switch (comparison1) /* comp1 comp2 comp1 comp2 comp1 comp2 */
|
||||
{ /* value1 = value2 value1 < value2 value1 > value2 */
|
||||
case RC_OPERATOR_EQ: /* a == 1 && a >= 1 | a == 1 && a >= 2 | a == 2 && a >= 1 */
|
||||
/* redundant conflict redundant */
|
||||
return (value1 >= value2) ? RC_OVERLAP_REDUNDANT : RC_OVERLAP_CONFLICTING;
|
||||
case RC_OPERATOR_LE: /* a <= 1 && a >= 1 | a <= 1 && a >= 2 | a <= 2 && a >= 1 */
|
||||
/* none conflict none */
|
||||
return (value1 < value2) ? RC_OVERLAP_CONFLICTING : RC_OVERLAP_NONE;
|
||||
case RC_OPERATOR_GE: /* a >= 1 && a >= 1 | a >= 1 && a >= 2 | a >= 2 && a >= 1 */
|
||||
/* redundant redundant defer */
|
||||
return (value1 <= value2) ? RC_OVERLAP_REDUNDANT : RC_OVERLAP_DEFER;
|
||||
case RC_OPERATOR_NE: /* a != 1 && a >= 1 | a != 1 && a >= 2 | a != 2 && a >= 1 */
|
||||
/* none defer none */
|
||||
return (value1 < value2) ? RC_OVERLAP_DEFER : RC_OVERLAP_NONE;
|
||||
case RC_OPERATOR_LT: /* a < 1 && a >= 1 | a < 1 && a >= 2 | a < 2 && a >= 1 */
|
||||
/* conflict conflict none */
|
||||
return (value1 <= value2) ? RC_OVERLAP_CONFLICTING : RC_OVERLAP_NONE;
|
||||
case RC_OPERATOR_GT: /* a > 1 && a >= 1 | a > 1 && a >= 2 | a > 2 && a >= 1 */
|
||||
/* redundant defer redundant */
|
||||
return (value1 >= value2) ? RC_OVERLAP_REDUNDANT : RC_OVERLAP_DEFER;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return RC_OVERLAP_NONE;
|
||||
}
|
||||
|
||||
static int rc_validate_conflicting_conditions(const rc_condset_t* conditions, const rc_condset_t* compare_conditions,
|
||||
const char* prefix, const char* compare_prefix, char result[], const size_t result_size)
|
||||
{
|
||||
int comparison1, comparison2;
|
||||
unsigned value1, value2;
|
||||
const rc_operand_t* operand1;
|
||||
const rc_operand_t* operand2;
|
||||
const rc_condition_t* compare_condition;
|
||||
const rc_condition_t* condition;
|
||||
int overlap;
|
||||
|
||||
/* empty group */
|
||||
if (conditions == NULL || compare_conditions == NULL)
|
||||
return 1;
|
||||
|
||||
/* outer loop is the source conditions */
|
||||
for (condition = conditions->conditions; condition != NULL;
|
||||
condition = rc_validate_next_non_combining_condition(condition))
|
||||
{
|
||||
/* hits can be captured at any time, so any potential conflict will not be conflicting at another time */
|
||||
if (condition->required_hits)
|
||||
continue;
|
||||
|
||||
operand1 = rc_validate_get_comparison(condition, &comparison1, &value1);
|
||||
if (!operand1)
|
||||
continue;
|
||||
|
||||
switch (condition->type)
|
||||
{
|
||||
case RC_CONDITION_PAUSE_IF:
|
||||
if (conditions != compare_conditions)
|
||||
break;
|
||||
/* fallthrough */
|
||||
case RC_CONDITION_RESET_IF:
|
||||
comparison1 = rc_validate_get_opposite_comparison(comparison1);
|
||||
break;
|
||||
default:
|
||||
if (rc_validate_is_combining_condition(condition))
|
||||
continue;
|
||||
break;
|
||||
}
|
||||
|
||||
/* inner loop is the potentially conflicting conditions */
|
||||
for (compare_condition = compare_conditions->conditions; compare_condition != NULL;
|
||||
compare_condition = rc_validate_next_non_combining_condition(compare_condition))
|
||||
{
|
||||
if (compare_condition == condition)
|
||||
continue;
|
||||
|
||||
if (compare_condition->required_hits)
|
||||
continue;
|
||||
|
||||
operand2 = rc_validate_get_comparison(compare_condition, &comparison2, &value2);
|
||||
if (!operand2 || operand2->value.memref->address != operand1->value.memref->address ||
|
||||
operand2->size != operand1->size || operand2->type != operand1->type)
|
||||
continue;
|
||||
|
||||
switch (compare_condition->type)
|
||||
{
|
||||
case RC_CONDITION_PAUSE_IF:
|
||||
if (conditions != compare_conditions)
|
||||
break;
|
||||
/* fallthrough */
|
||||
case RC_CONDITION_RESET_IF:
|
||||
comparison2 = rc_validate_get_opposite_comparison(comparison2);
|
||||
break;
|
||||
default:
|
||||
if (rc_validate_is_combining_condition(compare_condition))
|
||||
continue;
|
||||
break;
|
||||
}
|
||||
|
||||
overlap = rc_validate_comparison_overlap(comparison1, value1, comparison2, value2);
|
||||
switch (overlap)
|
||||
{
|
||||
case RC_OVERLAP_CONFLICTING:
|
||||
if (compare_condition->type == RC_CONDITION_PAUSE_IF || condition->type == RC_CONDITION_PAUSE_IF)
|
||||
{
|
||||
/* ignore PauseIf conflicts between groups, unless both conditions are PauseIfs */
|
||||
if (conditions != compare_conditions && compare_condition->type != condition->type)
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
|
||||
case RC_OVERLAP_REDUNDANT:
|
||||
if (prefix != compare_prefix && strcmp(compare_prefix, "Core") == 0)
|
||||
{
|
||||
/* if the alt condition is more restrictive than the core condition, ignore it */
|
||||
if (rc_validate_comparison_overlap(comparison2, value2, comparison1, value1) != RC_OVERLAP_REDUNDANT)
|
||||
continue;
|
||||
}
|
||||
|
||||
if (compare_condition->type == RC_CONDITION_PAUSE_IF || condition->type == RC_CONDITION_PAUSE_IF)
|
||||
{
|
||||
/* ignore PauseIf redundancies between groups */
|
||||
if (conditions != compare_conditions)
|
||||
continue;
|
||||
|
||||
/* if the PauseIf is less restrictive than the other condition, it's just a guard. ignore it */
|
||||
if (rc_validate_comparison_overlap(comparison2, value2, comparison1, value1) != RC_OVERLAP_REDUNDANT)
|
||||
continue;
|
||||
|
||||
/* PauseIf redundant with ResetIf is a conflict (both are inverted comparisons) */
|
||||
if (compare_condition->type == RC_CONDITION_RESET_IF || condition->type == RC_CONDITION_RESET_IF)
|
||||
overlap = RC_OVERLAP_CONFLICTING;
|
||||
}
|
||||
else if (compare_condition->type == RC_CONDITION_RESET_IF && condition->type != RC_CONDITION_RESET_IF)
|
||||
{
|
||||
/* only ever report the redundancy on the non-ResetIf condition. The ResetIf is allowed to
|
||||
* fire when the non-ResetIf condition is not true. */
|
||||
continue;
|
||||
}
|
||||
else if (compare_condition->type == RC_CONDITION_MEASURED_IF || condition->type == RC_CONDITION_MEASURED_IF)
|
||||
{
|
||||
/* ignore MeasuredIf redundancies between groups */
|
||||
if (conditions != compare_conditions)
|
||||
continue;
|
||||
|
||||
if (compare_condition->type == RC_CONDITION_MEASURED_IF && condition->type != RC_CONDITION_MEASURED_IF)
|
||||
{
|
||||
/* only ever report the redundancy on the non-MeasuredIf condition. The MeasuredIf provides
|
||||
* additional functionality. */
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else if (compare_condition->type == RC_CONDITION_TRIGGER || condition->type == RC_CONDITION_TRIGGER)
|
||||
{
|
||||
/* Trigger is allowed to be redundant with non-trigger conditions as there may be limits that start a
|
||||
* challenge that are furhter reduced for the completion of the challenge */
|
||||
if (compare_condition->type != condition->type)
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
|
||||
if (compare_prefix && *compare_prefix)
|
||||
{
|
||||
snprintf(result, result_size, "%s Condition %d: %s with %s Condition %d",
|
||||
compare_prefix, rc_validate_get_condition_index(compare_conditions, compare_condition),
|
||||
(overlap == RC_OVERLAP_REDUNDANT) ? "Redundant" : "Conflicts",
|
||||
prefix, rc_validate_get_condition_index(conditions, condition));
|
||||
}
|
||||
else
|
||||
{
|
||||
snprintf(result, result_size, "Condition %d: %s with Condition %d",
|
||||
rc_validate_get_condition_index(compare_conditions, compare_condition),
|
||||
(overlap == RC_OVERLAP_REDUNDANT) ? "Redundant" : "Conflicts",
|
||||
rc_validate_get_condition_index(conditions, condition));
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int rc_validate_trigger_internal(const rc_trigger_t* trigger, char result[], const size_t result_size, int console_id, unsigned max_address)
|
||||
{
|
||||
const rc_condset_t* alt;
|
||||
int index;
|
||||
|
||||
if (!trigger->alternative)
|
||||
{
|
||||
if (!rc_validate_condset_internal(trigger->requirement, result, result_size, console_id, max_address))
|
||||
return 0;
|
||||
|
||||
return rc_validate_conflicting_conditions(trigger->requirement, trigger->requirement, "", "", result, result_size);
|
||||
}
|
||||
|
||||
snprintf(result, result_size, "Core ");
|
||||
if (!rc_validate_condset_internal(trigger->requirement, result + 5, result_size - 5, console_id, max_address))
|
||||
return 0;
|
||||
|
||||
/* compare core to itself */
|
||||
if (!rc_validate_conflicting_conditions(trigger->requirement, trigger->requirement, "Core", "Core", result, result_size))
|
||||
return 0;
|
||||
|
||||
index = 1;
|
||||
for (alt = trigger->alternative; alt; alt = alt->next, ++index) {
|
||||
char altname[16];
|
||||
const size_t prefix_length = snprintf(result, result_size, "Alt%d ", index);
|
||||
if (!rc_validate_condset_internal(alt, result + prefix_length, result_size - prefix_length, console_id, max_address))
|
||||
return 0;
|
||||
|
||||
/* compare alt to itself */
|
||||
snprintf(altname, sizeof(altname), "Alt%d", index);
|
||||
if (!rc_validate_conflicting_conditions(alt, alt, altname, altname, result, result_size))
|
||||
return 0;
|
||||
|
||||
/* compare alt to core */
|
||||
if (!rc_validate_conflicting_conditions(trigger->requirement, alt, "Core", altname, result, result_size))
|
||||
return 0;
|
||||
|
||||
/* compare core to alt */
|
||||
if (!rc_validate_conflicting_conditions(alt, trigger->requirement, altname, "Core", result, result_size))
|
||||
return 0;
|
||||
}
|
||||
|
||||
*result = '\0';
|
||||
return 1;
|
||||
}
|
||||
|
||||
int rc_validate_trigger(const rc_trigger_t* trigger, char result[], const size_t result_size, unsigned max_address)
|
||||
{
|
||||
return rc_validate_trigger_internal(trigger, result, result_size, 0, max_address);
|
||||
}
|
||||
|
||||
int rc_validate_trigger_for_console(const rc_trigger_t* trigger, char result[], const size_t result_size, int console_id)
|
||||
{
|
||||
const unsigned max_address = rc_console_max_address(console_id);
|
||||
return rc_validate_trigger_internal(trigger, result, result_size, console_id, max_address);
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
#ifndef RC_VALIDATE_H
|
||||
#define RC_VALIDATE_H
|
||||
|
||||
#include "rc_runtime_types.h"
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
int rc_validate_memrefs(const rc_memref_t* memref, char result[], const size_t result_size, unsigned max_address);
|
||||
|
||||
int rc_validate_condset(const rc_condset_t* condset, char result[], const size_t result_size, unsigned max_address);
|
||||
int rc_validate_trigger(const rc_trigger_t* trigger, char result[], const size_t result_size, unsigned max_address);
|
||||
|
||||
int rc_validate_memrefs_for_console(const rc_memref_t* memref, char result[], const size_t result_size, int console_id);
|
||||
|
||||
int rc_validate_condset_for_console(const rc_condset_t* condset, char result[], const size_t result_size, int console_id);
|
||||
int rc_validate_trigger_for_console(const rc_trigger_t* trigger, char result[], const size_t result_size, int console_id);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* RC_VALIDATE_H */
|
|
@ -0,0 +1,29 @@
|
|||
#ifndef RC_VERSION_H
|
||||
#define RC_VERSION_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define RCHEEVOS_VERSION_MAJOR 10
|
||||
#define RCHEEVOS_VERSION_MINOR 7
|
||||
#define RCHEEVOS_VERSION_PATCH 0
|
||||
|
||||
#define RCHEEVOS_MAKE_VERSION(major, minor, patch) (major * 1000000 + minor * 1000 + patch)
|
||||
#define RCHEEVOS_VERSION RCHEEVOS_MAKE_VERSION(RCHEEVOS_VERSION_MAJOR, RCHEEVOS_VERSION_MINOR, RCHEEVOS_VERSION_PATCH)
|
||||
|
||||
#define RCHEEVOS_MAKE_STRING(num) #num
|
||||
#define RCHEEVOS_MAKE_VERSION_STRING(major, minor, patch) RCHEEVOS_MAKE_STRING(major) "." RCHEEVOS_MAKE_STRING(minor) "." RCHEEVOS_MAKE_STRING(patch)
|
||||
#define RCHEEVOS_MAKE_VERSION_STRING_SHORT(major, minor) RCHEEVOS_MAKE_STRING(major) "." RCHEEVOS_MAKE_STRING(minor)
|
||||
|
||||
#if RCHEEVOS_VERSION_PATCH > 0
|
||||
#define RCHEEVOS_VERSION_STRING RCHEEVOS_MAKE_VERSION_STRING(RCHEEVOS_VERSION_MAJOR, RCHEEVOS_VERSION_MINOR, RCHEEVOS_VERSION_PATCH)
|
||||
#else
|
||||
#define RCHEEVOS_VERSION_STRING RCHEEVOS_MAKE_VERSION_STRING_SHORT(RCHEEVOS_VERSION_MAJOR, RCHEEVOS_VERSION_MINOR)
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* RC_VERSION_H */
|
|
@ -280,7 +280,6 @@ static void rc_rebalance_richpresence_lookup(rc_richpresence_lookup_item_t** roo
|
|||
{
|
||||
rc_richpresence_lookup_item_t** items;
|
||||
rc_scratch_buffer_t* buffer;
|
||||
const int alignment = sizeof(rc_richpresence_lookup_item_t*);
|
||||
int index;
|
||||
int size;
|
||||
|
||||
|
@ -293,7 +292,7 @@ static void rc_rebalance_richpresence_lookup(rc_richpresence_lookup_item_t** roo
|
|||
size = count * sizeof(rc_richpresence_lookup_item_t*);
|
||||
buffer = &parse->scratch.buffer;
|
||||
do {
|
||||
const int aligned_offset = (buffer->offset + alignment - 1) & ~(alignment - 1);
|
||||
const int aligned_offset = RC_ALIGN(buffer->offset);
|
||||
const int remaining = sizeof(buffer->buffer) - aligned_offset;
|
||||
|
||||
if (remaining >= size) {
|
||||
|
@ -534,10 +533,11 @@ void rc_parse_richpresence_internal(rc_richpresence_t* self, const char* script,
|
|||
display = nextline;
|
||||
display_line = parse->lines_read;
|
||||
|
||||
/* scan as long as we find conditional lines or full line comments */
|
||||
do {
|
||||
line = nextline;
|
||||
nextline = rc_parse_line(line, &endline, parse);
|
||||
} while (*line == '?');
|
||||
} while (*line == '?' || (line[0] == '/' && line[1] == '/'));
|
||||
}
|
||||
|
||||
line = nextline;
|
||||
|
@ -557,38 +557,48 @@ void rc_parse_richpresence_internal(rc_richpresence_t* self, const char* script,
|
|||
|
||||
nextline = rc_parse_line(line, &endline, parse);
|
||||
|
||||
while (*line == '?') {
|
||||
/* conditional display: ?trigger?string */
|
||||
ptr = ++line;
|
||||
while (ptr < endline && *ptr != '?')
|
||||
++ptr;
|
||||
do {
|
||||
if (line[0] == '?') {
|
||||
/* conditional display: ?trigger?string */
|
||||
ptr = ++line;
|
||||
while (ptr < endline && *ptr != '?')
|
||||
++ptr;
|
||||
|
||||
if (ptr < endline) {
|
||||
*nextdisplay = rc_parse_richpresence_display_internal(ptr + 1, endline, parse, firstlookup);
|
||||
if (parse->offset < 0)
|
||||
return;
|
||||
trigger = &((*nextdisplay)->trigger);
|
||||
rc_parse_trigger_internal(trigger, &line, parse);
|
||||
trigger->memrefs = 0;
|
||||
if (parse->offset < 0)
|
||||
return;
|
||||
if (parse->buffer)
|
||||
nextdisplay = &((*nextdisplay)->next);
|
||||
if (ptr < endline) {
|
||||
*nextdisplay = rc_parse_richpresence_display_internal(ptr + 1, endline, parse, firstlookup);
|
||||
if (parse->offset < 0)
|
||||
return;
|
||||
trigger = &((*nextdisplay)->trigger);
|
||||
rc_parse_trigger_internal(trigger, &line, parse);
|
||||
trigger->memrefs = 0;
|
||||
if (parse->offset < 0)
|
||||
return;
|
||||
if (parse->buffer)
|
||||
nextdisplay = &((*nextdisplay)->next);
|
||||
}
|
||||
}
|
||||
else if (line[0] != '/' || line[1] != '/') {
|
||||
break;
|
||||
}
|
||||
|
||||
line = nextline;
|
||||
nextline = rc_parse_line(line, &endline, parse);
|
||||
}
|
||||
} while (1);
|
||||
|
||||
/* non-conditional display: string */
|
||||
*nextdisplay = rc_parse_richpresence_display_internal(line, endline, parse, firstlookup);
|
||||
if (*nextdisplay) {
|
||||
hasdisplay = 1;
|
||||
nextdisplay = &((*nextdisplay)->next);
|
||||
}
|
||||
|
||||
/* restore the parser state */
|
||||
parse->lines_read = lines_read;
|
||||
/* restore the parser state */
|
||||
parse->lines_read = lines_read;
|
||||
}
|
||||
else {
|
||||
/* this should only happen if the line is blank.
|
||||
* expect parse->offset to be RC_MISSING_DISPLAY_STRING and leave parse->lines_read
|
||||
* on the current line for error tracking. */
|
||||
}
|
||||
}
|
||||
|
||||
/* finalize */
|
||||
|
|
|
@ -9,6 +9,17 @@
|
|||
|
||||
#define RC_RICHPRESENCE_DISPLAY_BUFFER_SIZE 256
|
||||
|
||||
rc_runtime_t* rc_runtime_alloc(void) {
|
||||
rc_runtime_t* self = malloc(sizeof(rc_runtime_t));
|
||||
|
||||
if (self) {
|
||||
rc_runtime_init(self);
|
||||
self->owns_self = 1;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
void rc_runtime_init(rc_runtime_t* self) {
|
||||
memset(self, 0, sizeof(rc_runtime_t));
|
||||
self->next_memref = &self->memrefs;
|
||||
|
@ -48,9 +59,13 @@ void rc_runtime_destroy(rc_runtime_t* self) {
|
|||
|
||||
self->next_memref = 0;
|
||||
self->memrefs = 0;
|
||||
|
||||
if (self->owns_self) {
|
||||
free(self);
|
||||
}
|
||||
}
|
||||
|
||||
static void rc_runtime_checksum(const char* memaddr, unsigned char* md5) {
|
||||
void rc_runtime_checksum(const char* memaddr, unsigned char* md5) {
|
||||
md5_state_t state;
|
||||
md5_init(&state);
|
||||
md5_append(&state, (unsigned char*)memaddr, (int)strlen(memaddr));
|
||||
|
@ -231,7 +246,7 @@ int rc_runtime_get_achievement_measured(const rc_runtime_t* runtime, unsigned id
|
|||
}
|
||||
|
||||
if (rc_trigger_state_active(trigger->state)) {
|
||||
*measured_value = trigger->measured_value;
|
||||
*measured_value = (trigger->measured_value == RC_MEASURED_UNKNOWN) ? 0 : trigger->measured_value;
|
||||
*measured_target = trigger->measured_target;
|
||||
}
|
||||
else {
|
||||
|
@ -257,7 +272,7 @@ int rc_runtime_format_achievement_measured(const rc_runtime_t* runtime, unsigned
|
|||
}
|
||||
|
||||
/* cap the value at the target so we can count past the target: "107 >= 100" */
|
||||
value = trigger->measured_value;
|
||||
value = (trigger->measured_value == RC_MEASURED_UNKNOWN) ? 0 : trigger->measured_value;
|
||||
if (value > trigger->measured_target)
|
||||
value = trigger->measured_target;
|
||||
|
||||
|
@ -534,6 +549,7 @@ void rc_runtime_do_frame(rc_runtime_t* self, rc_runtime_event_handler_t event_ha
|
|||
for (i = self->trigger_count - 1; i >= 0; --i) {
|
||||
rc_trigger_t* trigger = self->triggers[i].trigger;
|
||||
int old_state, new_state;
|
||||
unsigned old_measured_value;
|
||||
|
||||
if (!trigger)
|
||||
continue;
|
||||
|
@ -552,13 +568,13 @@ void rc_runtime_do_frame(rc_runtime_t* self, rc_runtime_event_handler_t event_ha
|
|||
continue;
|
||||
}
|
||||
|
||||
old_measured_value = trigger->measured_value;
|
||||
old_state = trigger->state;
|
||||
new_state = rc_evaluate_trigger(trigger, peek, ud, L);
|
||||
|
||||
/* the trigger state doesn't actually change to RESET, RESET just serves as a notification.
|
||||
/* trigger->state doesn't actually change to RESET, RESET just serves as a notification.
|
||||
* handle the notification, then look at the actual state */
|
||||
if (new_state == RC_TRIGGER_STATE_RESET)
|
||||
{
|
||||
if (new_state == RC_TRIGGER_STATE_RESET) {
|
||||
runtime_event.type = RC_RUNTIME_EVENT_ACHIEVEMENT_RESET;
|
||||
runtime_event.id = self->triggers[i].id;
|
||||
event_handler(&runtime_event);
|
||||
|
@ -566,6 +582,32 @@ void rc_runtime_do_frame(rc_runtime_t* self, rc_runtime_event_handler_t event_ha
|
|||
new_state = trigger->state;
|
||||
}
|
||||
|
||||
/* if the measured value changed and the achievement hasn't triggered, send a notification */
|
||||
if (trigger->measured_value != old_measured_value && old_measured_value != RC_MEASURED_UNKNOWN &&
|
||||
trigger->measured_target != 0 && trigger->measured_value <= trigger->measured_target &&
|
||||
new_state != RC_TRIGGER_STATE_TRIGGERED &&
|
||||
new_state != RC_TRIGGER_STATE_INACTIVE && new_state != RC_TRIGGER_STATE_WAITING) {
|
||||
|
||||
runtime_event.type = RC_RUNTIME_EVENT_ACHIEVEMENT_PROGRESS_UPDATED;
|
||||
runtime_event.id = self->triggers[i].id;
|
||||
|
||||
if (trigger->measured_as_percent) {
|
||||
/* if reporting measured value as a percentage, only send the notification if the percentage changes */
|
||||
unsigned old_percent = (unsigned)(((unsigned long long)old_measured_value * 100) / trigger->measured_target);
|
||||
unsigned new_percent = (unsigned)(((unsigned long long)trigger->measured_value * 100) / trigger->measured_target);
|
||||
if (old_percent != new_percent) {
|
||||
runtime_event.value = new_percent;
|
||||
event_handler(&runtime_event);
|
||||
}
|
||||
}
|
||||
else {
|
||||
runtime_event.value = trigger->measured_value;
|
||||
event_handler(&runtime_event);
|
||||
}
|
||||
|
||||
runtime_event.value = 0; /* achievement loop expects this to stay at 0 */
|
||||
}
|
||||
|
||||
/* if the state hasn't changed, there won't be any events raised */
|
||||
if (new_state == old_state)
|
||||
continue;
|
||||
|
@ -685,13 +727,8 @@ void rc_runtime_reset(rc_runtime_t* self) {
|
|||
rc_reset_lboard(self->lboards[i].lboard);
|
||||
}
|
||||
|
||||
if (self->richpresence && self->richpresence->richpresence) {
|
||||
rc_richpresence_display_t* display = self->richpresence->richpresence->first_display;
|
||||
while (display != 0) {
|
||||
rc_reset_trigger(&display->trigger);
|
||||
display = display->next;
|
||||
}
|
||||
}
|
||||
if (self->richpresence && self->richpresence->richpresence)
|
||||
rc_reset_richpresence(self->richpresence->richpresence);
|
||||
|
||||
for (variable = self->variables; variable; variable = variable->next)
|
||||
rc_reset_value(variable);
|
||||
|
@ -712,7 +749,7 @@ static int rc_condset_contains_memref(const rc_condset_t* condset, const rc_memr
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int rc_value_contains_memref(const rc_value_t* value, const rc_memref_t* memref) {
|
||||
int rc_value_contains_memref(const rc_value_t* value, const rc_memref_t* memref) {
|
||||
rc_condset_t* condset;
|
||||
if (!value)
|
||||
return 0;
|
||||
|
@ -725,7 +762,7 @@ static int rc_value_contains_memref(const rc_value_t* value, const rc_memref_t*
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int rc_trigger_contains_memref(const rc_trigger_t* trigger, const rc_memref_t* memref) {
|
||||
int rc_trigger_contains_memref(const rc_trigger_t* trigger, const rc_memref_t* memref) {
|
||||
rc_condset_t* condset;
|
||||
if (!trigger)
|
||||
return 0;
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
#define RC_RUNTIME_CHUNK_DONE 0x454E4F44 /* DONE */
|
||||
|
||||
typedef struct rc_runtime_progress_t {
|
||||
rc_runtime_t* runtime;
|
||||
const rc_runtime_t* runtime;
|
||||
|
||||
int offset;
|
||||
unsigned char* buffer;
|
||||
|
@ -81,17 +81,6 @@ static int rc_runtime_progress_match_md5(rc_runtime_progress_t* progress, unsign
|
|||
return result;
|
||||
}
|
||||
|
||||
static unsigned rc_runtime_progress_djb2(const char* input)
|
||||
{
|
||||
unsigned result = 5381;
|
||||
char c;
|
||||
|
||||
while ((c = *input++) != '\0')
|
||||
result = ((result << 5) + result) + c; /* result = result * 33 + c */
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static void rc_runtime_progress_start_chunk(rc_runtime_progress_t* progress, unsigned chunk_id)
|
||||
{
|
||||
rc_runtime_progress_write_uint(progress, chunk_id);
|
||||
|
@ -120,7 +109,7 @@ static void rc_runtime_progress_end_chunk(rc_runtime_progress_t* progress)
|
|||
}
|
||||
}
|
||||
|
||||
static void rc_runtime_progress_init(rc_runtime_progress_t* progress, rc_runtime_t* runtime, lua_State* L)
|
||||
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;
|
||||
|
@ -352,7 +341,7 @@ static int rc_runtime_progress_write_variables(rc_runtime_progress_t* progress)
|
|||
|
||||
for (variable = progress->runtime->variables; variable; variable = variable->next)
|
||||
{
|
||||
unsigned djb2 = rc_runtime_progress_djb2(variable->name);
|
||||
unsigned djb2 = rc_djb2(variable->name);
|
||||
rc_runtime_progress_write_uint(progress, djb2);
|
||||
|
||||
rc_runtime_progress_write_variable(progress, variable);
|
||||
|
@ -418,7 +407,7 @@ static int rc_runtime_progress_read_variables(rc_runtime_progress_t* progress)
|
|||
count = 0;
|
||||
for (variable = progress->runtime->variables; variable; variable = variable->next) {
|
||||
pending_variables[count].variable = variable;
|
||||
pending_variables[count].djb2 = rc_runtime_progress_djb2(variable->name);
|
||||
pending_variables[count].djb2 = rc_djb2(variable->name);
|
||||
++count;
|
||||
}
|
||||
|
||||
|
@ -751,7 +740,7 @@ int rc_runtime_progress_size(const rc_runtime_t* runtime, lua_State* L)
|
|||
rc_runtime_progress_t progress;
|
||||
int result;
|
||||
|
||||
rc_runtime_progress_init(&progress, (rc_runtime_t*)runtime, L);
|
||||
rc_runtime_progress_init(&progress, runtime, L);
|
||||
|
||||
result = rc_runtime_progress_serialize_internal(&progress);
|
||||
if (result != RC_OK)
|
||||
|
@ -764,7 +753,10 @@ int rc_runtime_serialize_progress(void* buffer, const rc_runtime_t* runtime, lua
|
|||
{
|
||||
rc_runtime_progress_t progress;
|
||||
|
||||
rc_runtime_progress_init(&progress, (rc_runtime_t*)runtime, L);
|
||||
if (!buffer)
|
||||
return RC_INVALID_STATE;
|
||||
|
||||
rc_runtime_progress_init(&progress, runtime, L);
|
||||
progress.buffer = (unsigned char*)buffer;
|
||||
|
||||
return rc_runtime_progress_serialize_internal(&progress);
|
||||
|
@ -782,6 +774,11 @@ int rc_runtime_deserialize_progress(rc_runtime_t* runtime, const unsigned char*
|
|||
int seen_rich_presence = 0;
|
||||
int result = RC_OK;
|
||||
|
||||
if (!serialized) {
|
||||
rc_runtime_reset(runtime);
|
||||
return RC_INVALID_STATE;
|
||||
}
|
||||
|
||||
rc_runtime_progress_init(&progress, runtime, L);
|
||||
progress.buffer = (unsigned char*)serialized;
|
||||
|
||||
|
|
|
@ -42,8 +42,8 @@ void rc_parse_trigger_internal(rc_trigger_t* self, const char** memaddr, rc_pars
|
|||
*next = 0;
|
||||
*memaddr = aux;
|
||||
|
||||
self->measured_value = 0;
|
||||
self->measured_target = parse->measured_target;
|
||||
self->measured_value = parse->measured_target ? RC_MEASURED_UNKNOWN : 0;
|
||||
self->measured_as_percent = parse->measured_as_percent;
|
||||
self->state = RC_TRIGGER_STATE_WAITING;
|
||||
self->has_hits = 0;
|
||||
|
@ -197,14 +197,6 @@ int rc_evaluate_trigger(rc_trigger_t* self, rc_peek_t peek, void* ud, lua_State*
|
|||
self->measured_value = eval_state.measured_value.value.u32;
|
||||
}
|
||||
|
||||
/* if the state is WAITING and the trigger is ready to fire, ignore it and reset the hit counts */
|
||||
/* otherwise, if the state is WAITING, proceed to activating the trigger */
|
||||
if (self->state == RC_TRIGGER_STATE_WAITING && ret) {
|
||||
rc_reset_trigger(self);
|
||||
self->has_hits = 0;
|
||||
return RC_TRIGGER_STATE_WAITING;
|
||||
}
|
||||
|
||||
/* if any ResetIf condition was true, reset the hit counts */
|
||||
if (eval_state.was_reset) {
|
||||
/* if the measured value came from a hit count, reset it. do this before calling
|
||||
|
@ -247,6 +239,13 @@ int rc_evaluate_trigger(rc_trigger_t* self, rc_peek_t peek, void* ud, lua_State*
|
|||
is_primed = 0;
|
||||
}
|
||||
else if (ret) {
|
||||
/* if the state is WAITING and the trigger is ready to fire, ignore it and reset the hit counts */
|
||||
if (self->state == RC_TRIGGER_STATE_WAITING) {
|
||||
rc_reset_trigger(self);
|
||||
self->has_hits = 0;
|
||||
return RC_TRIGGER_STATE_WAITING;
|
||||
}
|
||||
|
||||
/* trigger was triggered */
|
||||
self->state = RC_TRIGGER_STATE_TRIGGERED;
|
||||
return RC_TRIGGER_STATE_TRIGGERED;
|
||||
|
@ -284,6 +283,9 @@ void rc_reset_trigger(rc_trigger_t* self) {
|
|||
rc_reset_trigger_hitcounts(self);
|
||||
|
||||
self->state = RC_TRIGGER_STATE_WAITING;
|
||||
self->measured_value = 0;
|
||||
|
||||
if (self->measured_target)
|
||||
self->measured_value = RC_MEASURED_UNKNOWN;
|
||||
|
||||
self->has_hits = 0;
|
||||
}
|
||||
|
|
|
@ -111,6 +111,7 @@ void rc_parse_legacy_value(rc_value_t* self, const char** memaddr, rc_parse_stat
|
|||
case RC_OPERATOR_MULT:
|
||||
case RC_OPERATOR_DIV:
|
||||
case RC_OPERATOR_AND:
|
||||
case RC_OPERATOR_XOR:
|
||||
case RC_OPERATOR_NONE:
|
||||
break;
|
||||
|
||||
|
@ -284,6 +285,20 @@ void rc_reset_value(rc_value_t* self) {
|
|||
self->value.changed = 0;
|
||||
}
|
||||
|
||||
int rc_value_from_hits(rc_value_t* self)
|
||||
{
|
||||
rc_condset_t* condset = self->conditions;
|
||||
for (; condset != NULL; condset = condset->next) {
|
||||
rc_condition_t* condition = condset->conditions;
|
||||
for (; condition != NULL; condition = condition->next) {
|
||||
if (condition->type == RC_CONDITION_MEASURED)
|
||||
return (condition->required_hits != 0);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void rc_init_parse_state_variables(rc_parse_state_t* parse, rc_value_t** variables) {
|
||||
parse->variables = variables;
|
||||
*variables = 0;
|
||||
|
@ -427,6 +442,26 @@ static rc_typed_value_t* rc_typed_value_convert_into(rc_typed_value_t* dest, con
|
|||
return dest;
|
||||
}
|
||||
|
||||
void rc_typed_value_negate(rc_typed_value_t* value) {
|
||||
switch (value->type)
|
||||
{
|
||||
case RC_VALUE_TYPE_UNSIGNED:
|
||||
rc_typed_value_convert(value, RC_VALUE_TYPE_SIGNED);
|
||||
/* fallthrough to RC_VALUE_TYPE_SIGNED */
|
||||
|
||||
case RC_VALUE_TYPE_SIGNED:
|
||||
value->value.i32 = -(value->value.i32);
|
||||
break;
|
||||
|
||||
case RC_VALUE_TYPE_FLOAT:
|
||||
value->value.f32 = -(value->value.f32);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void rc_typed_value_add(rc_typed_value_t* value, const rc_typed_value_t* amount) {
|
||||
rc_typed_value_t converted;
|
||||
|
||||
|
|
|
@ -22,6 +22,7 @@ struct cdrom_t
|
|||
void* file_handle; /* the file handle for reading the track data */
|
||||
int sector_size; /* the size of each sector in the track data */
|
||||
int sector_header_size; /* the offset to the raw data within a sector block */
|
||||
int raw_data_size; /* the amount of raw data within a sector block */
|
||||
int64_t file_track_offset;/* the offset of the track data within the file */
|
||||
int track_first_sector; /* the first absolute sector associated to the track (includes pregap) */
|
||||
int track_pregap_sectors; /* the number of pregap sectors */
|
||||
|
@ -58,6 +59,7 @@ static void cdreader_determine_sector_size(struct cdrom_t* cdrom)
|
|||
|
||||
cdrom->sector_size = 0;
|
||||
cdrom->sector_header_size = 0;
|
||||
cdrom->raw_data_size = 2048;
|
||||
|
||||
rc_file_seek(cdrom->file_handle, toc_sector * 2352 + cdrom->file_track_offset, SEEK_SET);
|
||||
if (rc_file_read(cdrom->file_handle, header, sizeof(header)) < sizeof(header))
|
||||
|
@ -122,6 +124,8 @@ static void* cdreader_open_bin_track(const char* path, uint32_t track)
|
|||
return NULL;
|
||||
|
||||
cdrom = (struct cdrom_t*)calloc(1, sizeof(*cdrom));
|
||||
if (!cdrom)
|
||||
return NULL;
|
||||
cdrom->file_handle = file_handle;
|
||||
#ifndef NDEBUG
|
||||
cdrom->track_id = track;
|
||||
|
@ -219,6 +223,7 @@ static int cdreader_open_bin(struct cdrom_t* cdrom, const char* path, const char
|
|||
{
|
||||
cdrom->sector_size = 2352;
|
||||
cdrom->sector_header_size = 0;
|
||||
cdrom->raw_data_size = 2352; /* no header or footer data on audio tracks */
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -282,6 +287,7 @@ static void* cdreader_open_cue_track(const char* path, uint32_t track)
|
|||
char* bin_filename = NULL;
|
||||
char *ptr, *ptr2, *end;
|
||||
int done = 0;
|
||||
int session = 1;
|
||||
size_t num_read = 0;
|
||||
struct cdrom_t* cdrom = NULL;
|
||||
|
||||
|
@ -339,7 +345,7 @@ static void* cdreader_open_cue_track(const char* path, uint32_t track)
|
|||
++ptr;
|
||||
|
||||
/* convert mm:ss:ff to sector count */
|
||||
sscanf(ptr, "%d:%d:%d", &m, &s, &f);
|
||||
sscanf_s(ptr, "%d:%d:%d", &m, &s, &f);
|
||||
sector_offset = ((m * 60) + s) * 75 + f;
|
||||
|
||||
if (current_track.first_sector == -1)
|
||||
|
@ -389,6 +395,13 @@ static void* cdreader_open_cue_track(const char* path, uint32_t track)
|
|||
done = 1;
|
||||
break;
|
||||
}
|
||||
|
||||
if (track == RC_HASH_CDTRACK_FIRST_OF_SECOND_SESSION && session == 2)
|
||||
{
|
||||
track = current_track.id;
|
||||
done = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (strncasecmp(ptr, "TRACK ", 6) == 0)
|
||||
|
@ -465,6 +478,20 @@ static void* cdreader_open_cue_track(const char* path, uint32_t track)
|
|||
if (ptr2 - ptr < (int)sizeof(current_track.filename))
|
||||
memcpy(current_track.filename, ptr, ptr2 - ptr);
|
||||
}
|
||||
else if (strncasecmp(ptr, "REM ", 4) == 0)
|
||||
{
|
||||
ptr += 4;
|
||||
while (*ptr == ' ')
|
||||
++ptr;
|
||||
|
||||
if (strncasecmp(ptr, "SESSION ", 8) == 0)
|
||||
{
|
||||
ptr += 8;
|
||||
while (*ptr == ' ')
|
||||
++ptr;
|
||||
session = atoi(ptr);
|
||||
}
|
||||
}
|
||||
|
||||
while (*ptr && *ptr != '\n')
|
||||
++ptr;
|
||||
|
@ -694,8 +721,8 @@ static void* cdreader_open_gdi_track(const char* path, uint32_t track)
|
|||
largest_track_size = track_size;
|
||||
largest_track = current_track;
|
||||
largest_track_lba = lba;
|
||||
strcpy(largest_track_file, file);
|
||||
strcpy(largest_track_sector_size, sector_size);
|
||||
strcpy_s(largest_track_file, sizeof(largest_track_file), file);
|
||||
strcpy_s(largest_track_sector_size, sizeof(largest_track_sector_size), sector_size);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -723,8 +750,8 @@ static void* cdreader_open_gdi_track(const char* path, uint32_t track)
|
|||
if (largest_track != 0 && largest_track != current_track)
|
||||
{
|
||||
current_track = largest_track;
|
||||
strcpy(file, largest_track_file);
|
||||
strcpy(sector_size, largest_track_sector_size);
|
||||
strcpy_s(file, sizeof(file), largest_track_file);
|
||||
strcpy_s(sector_size, sizeof(sector_size), largest_track_sector_size);
|
||||
lba = largest_track_lba;
|
||||
}
|
||||
|
||||
|
@ -794,18 +821,18 @@ static size_t cdreader_read_sector(void* track_handle, uint32_t sector, void* bu
|
|||
sector_start = (int64_t)(sector - cdrom->track_first_sector) * cdrom->sector_size +
|
||||
cdrom->sector_header_size + cdrom->file_track_offset;
|
||||
|
||||
while (requested_bytes > 2048)
|
||||
while (requested_bytes > (size_t)cdrom->raw_data_size)
|
||||
{
|
||||
rc_file_seek(cdrom->file_handle, sector_start, SEEK_SET);
|
||||
num_read = rc_file_read(cdrom->file_handle, buffer_ptr, 2048);
|
||||
num_read = rc_file_read(cdrom->file_handle, buffer_ptr, cdrom->raw_data_size);
|
||||
total_read += num_read;
|
||||
|
||||
if (num_read < 2048)
|
||||
if (num_read < (size_t)cdrom->raw_data_size)
|
||||
return total_read;
|
||||
|
||||
buffer_ptr += 2048;
|
||||
buffer_ptr += cdrom->raw_data_size;
|
||||
sector_start += cdrom->sector_size;
|
||||
requested_bytes -= 2048;
|
||||
requested_bytes -= cdrom->raw_data_size;
|
||||
}
|
||||
|
||||
rc_file_seek(cdrom->file_handle, sector_start, SEEK_SET);
|
||||
|
@ -844,7 +871,7 @@ void rc_hash_get_default_cdreader(struct rc_hash_cdreader* cdreader)
|
|||
cdreader->first_track_sector = cdreader_first_track_sector;
|
||||
}
|
||||
|
||||
void rc_hash_init_default_cdreader()
|
||||
void rc_hash_init_default_cdreader(void)
|
||||
{
|
||||
struct rc_hash_cdreader cdreader;
|
||||
rc_hash_get_default_cdreader(&cdreader);
|
||||
|
|
|
@ -48,7 +48,13 @@ static struct rc_hash_filereader* filereader = NULL;
|
|||
|
||||
static void* filereader_open(const char* path)
|
||||
{
|
||||
#if defined(__STDC_WANT_SECURE_LIB__)
|
||||
FILE* fp;
|
||||
fopen_s(&fp, path, "rb");
|
||||
return fp;
|
||||
#else
|
||||
return fopen(path, "rb");
|
||||
#endif
|
||||
}
|
||||
|
||||
static void filereader_seek(void* file_handle, int64_t offset, int origin)
|
||||
|
@ -223,12 +229,17 @@ static uint32_t rc_cd_find_file_sector(void* track_handle, const char* path, uns
|
|||
{
|
||||
uint8_t buffer[2048], *tmp;
|
||||
int sector;
|
||||
unsigned num_sectors = 0;
|
||||
size_t filename_length;
|
||||
const char* slash;
|
||||
|
||||
if (!track_handle)
|
||||
return 0;
|
||||
|
||||
/* we start at the root. don't need to explicitly find it */
|
||||
if (*path == '\\')
|
||||
++path;
|
||||
|
||||
filename_length = strlen(path);
|
||||
slash = strrchr(path, '\\');
|
||||
if (slash)
|
||||
|
@ -247,6 +258,8 @@ static uint32_t rc_cd_find_file_sector(void* track_handle, const char* path, uns
|
|||
}
|
||||
else
|
||||
{
|
||||
unsigned logical_block_size;
|
||||
|
||||
/* find the cd information */
|
||||
if (!rc_cd_read_sector(track_handle, rc_cd_first_track_sector(track_handle) + 16, buffer, 256))
|
||||
return 0;
|
||||
|
@ -255,6 +268,15 @@ static uint32_t rc_cd_find_file_sector(void* track_handle, const char* path, uns
|
|||
* https://www.cdroller.com/htm/readdata.html
|
||||
*/
|
||||
sector = buffer[156 + 2] | (buffer[156 + 3] << 8) | (buffer[156 + 4] << 16);
|
||||
|
||||
/* if the table of contents spans more than one sector, it's length of section will exceed it's logical block size */
|
||||
logical_block_size = (buffer[128] | (buffer[128 + 1] << 8)); /* logical block size */
|
||||
if (logical_block_size == 0) {
|
||||
num_sectors = 1;
|
||||
} else {
|
||||
num_sectors = (buffer[156 + 10] | (buffer[156 + 11] << 8) | (buffer[156 + 12] << 16) | (buffer[156 + 13] << 24)); /* length of section */
|
||||
num_sectors /= logical_block_size;
|
||||
}
|
||||
}
|
||||
|
||||
/* fetch and process the directory record */
|
||||
|
@ -262,21 +284,34 @@ static uint32_t rc_cd_find_file_sector(void* track_handle, const char* path, uns
|
|||
return 0;
|
||||
|
||||
tmp = buffer;
|
||||
while (tmp < buffer + sizeof(buffer))
|
||||
do
|
||||
{
|
||||
if (!*tmp)
|
||||
return 0;
|
||||
if (tmp >= buffer + sizeof(buffer) || !*tmp)
|
||||
{
|
||||
/* end of this path table block. if the path table spans multiple sectors, keep scanning */
|
||||
if (num_sectors > 1)
|
||||
{
|
||||
--num_sectors;
|
||||
if (rc_cd_read_sector(track_handle, ++sector, buffer, sizeof(buffer)))
|
||||
{
|
||||
tmp = buffer;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
/* filename is 33 bytes into the record and the format is "FILENAME;version" or "DIRECTORY" */
|
||||
if ((tmp[33 + filename_length] == ';' || tmp[33 + filename_length] == '\0') &&
|
||||
if ((tmp[32] == filename_length || tmp[33 + filename_length] == ';') &&
|
||||
strncasecmp((const char*)(tmp + 33), path, filename_length) == 0)
|
||||
{
|
||||
sector = tmp[2] | (tmp[3] << 8) | (tmp[4] << 16);
|
||||
|
||||
if (verbose_message_callback)
|
||||
{
|
||||
snprintf((char*)buffer, sizeof(buffer), "Found %s at sector %d", path, sector);
|
||||
verbose_message_callback((const char*)buffer);
|
||||
char message[128];
|
||||
snprintf(message, sizeof(message), "Found %s at sector %d", path, sector);
|
||||
verbose_message_callback(message);
|
||||
}
|
||||
|
||||
if (size)
|
||||
|
@ -287,7 +322,7 @@ static uint32_t rc_cd_find_file_sector(void* track_handle, const char* path, uns
|
|||
|
||||
/* the first byte of the record is the length of the record */
|
||||
tmp += *tmp;
|
||||
}
|
||||
} while (1);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -347,6 +382,34 @@ int rc_path_compare_extension(const char* path, const char* ext)
|
|||
|
||||
/* ===================================================== */
|
||||
|
||||
static void rc_hash_byteswap16(uint8_t* buffer, const uint8_t* stop)
|
||||
{
|
||||
uint32_t* ptr = (uint32_t*)buffer;
|
||||
const uint32_t* stop32 = (const uint32_t*)stop;
|
||||
while (ptr < stop32)
|
||||
{
|
||||
uint32_t temp = *ptr;
|
||||
temp = (temp & 0xFF00FF00) >> 8 |
|
||||
(temp & 0x00FF00FF) << 8;
|
||||
*ptr++ = temp;
|
||||
}
|
||||
}
|
||||
|
||||
static void rc_hash_byteswap32(uint8_t* buffer, const uint8_t* stop)
|
||||
{
|
||||
uint32_t* ptr = (uint32_t*)buffer;
|
||||
const uint32_t* stop32 = (const uint32_t*)stop;
|
||||
while (ptr < stop32)
|
||||
{
|
||||
uint32_t temp = *ptr;
|
||||
temp = (temp & 0xFF000000) >> 24 |
|
||||
(temp & 0x00FF0000) >> 8 |
|
||||
(temp & 0x0000FF00) << 8 |
|
||||
(temp & 0x000000FF) << 24;
|
||||
*ptr++ = temp;
|
||||
}
|
||||
}
|
||||
|
||||
static int rc_hash_finalize(md5_state_t* md5, char hash[33])
|
||||
{
|
||||
md5_byte_t digest[16];
|
||||
|
@ -415,6 +478,9 @@ static int rc_hash_cd_file(md5_state_t* md5, void* track_handle, uint32_t sector
|
|||
verbose_message_callback(message);
|
||||
}
|
||||
|
||||
if (size < (unsigned)num_read)
|
||||
size = (unsigned)num_read;
|
||||
|
||||
do
|
||||
{
|
||||
md5_append(md5, buffer, (int)num_read);
|
||||
|
@ -684,6 +750,142 @@ static int rc_hash_text(char hash[33], const uint8_t* buffer, size_t buffer_size
|
|||
return rc_hash_finalize(&md5, hash);
|
||||
}
|
||||
|
||||
/* helper variable only used for testing */
|
||||
const char* _rc_hash_jaguar_cd_homebrew_hash = NULL;
|
||||
|
||||
static int rc_hash_jaguar_cd(char hash[33], const char* path)
|
||||
{
|
||||
uint8_t buffer[2352];
|
||||
char message[128];
|
||||
void* track_handle;
|
||||
md5_state_t md5;
|
||||
int byteswapped = 0;
|
||||
unsigned size = 0;
|
||||
unsigned offset = 0;
|
||||
unsigned sector = 0;
|
||||
unsigned remaining;
|
||||
unsigned i;
|
||||
|
||||
/* Jaguar CD header is in the first sector of the first data track OF THE SECOND SESSION.
|
||||
* The first track must be an audio track, but may be a warning message or actual game audio */
|
||||
track_handle = rc_cd_open_track(path, RC_HASH_CDTRACK_FIRST_OF_SECOND_SESSION);
|
||||
if (!track_handle)
|
||||
return rc_hash_error("Could not open track");
|
||||
|
||||
/* The header is an unspecified distance into the first sector, but usually two bytes in.
|
||||
* It consists of 64 bytes of "TAIR" or "ATRI" repeating, depending on whether or not the data
|
||||
* is byteswapped. Then another 32 byte that reads "ATARI APPROVED DATA HEADER ATRI "
|
||||
* (possibly byteswapped). Then a big-endian 32-bit value for the address where the boot code
|
||||
* should be loaded, and a second big-endian 32-bit value for the size of the boot code. */
|
||||
sector = rc_cd_first_track_sector(track_handle);
|
||||
rc_cd_read_sector(track_handle, sector, buffer, sizeof(buffer));
|
||||
|
||||
for (i = 64; i < sizeof(buffer) - 32 - 4 * 3; i++)
|
||||
{
|
||||
if (memcmp(&buffer[i], "TARA IPARPVODED TA AEHDAREA RT I", 32) == 0)
|
||||
{
|
||||
byteswapped = 1;
|
||||
offset = i + 32 + 4;
|
||||
size = (buffer[offset] << 16) | (buffer[offset + 1] << 24) | (buffer[offset + 2]) | (buffer[offset + 3] << 8);
|
||||
break;
|
||||
}
|
||||
else if (memcmp(&buffer[i], "ATARI APPROVED DATA HEADER ATRI ", 32) == 0)
|
||||
{
|
||||
byteswapped = 0;
|
||||
offset = i + 32 + 4;
|
||||
size = (buffer[offset] << 24) | (buffer[offset + 1] << 16) | (buffer[offset + 2] << 8) | (buffer[offset + 3]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (size == 0) /* did not see ATARI APPROVED DATA HEADER */
|
||||
{
|
||||
rc_cd_close_track(track_handle);
|
||||
return rc_hash_error("Not a Jaguar CD");
|
||||
}
|
||||
|
||||
i = 0; /* only loop once */
|
||||
do
|
||||
{
|
||||
md5_init(&md5);
|
||||
|
||||
offset += 4;
|
||||
|
||||
if (verbose_message_callback)
|
||||
{
|
||||
snprintf(message, sizeof(message), "Hashing boot executable (%u bytes starting at %u bytes into sector %u)", size, offset, sector);
|
||||
rc_hash_verbose(message);
|
||||
}
|
||||
|
||||
if (size > MAX_BUFFER_SIZE)
|
||||
size = MAX_BUFFER_SIZE;
|
||||
|
||||
do
|
||||
{
|
||||
if (byteswapped)
|
||||
rc_hash_byteswap16(buffer, &buffer[sizeof(buffer)]);
|
||||
|
||||
remaining = sizeof(buffer) - offset;
|
||||
if (remaining >= size)
|
||||
{
|
||||
md5_append(&md5, &buffer[offset], size);
|
||||
size = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
md5_append(&md5, &buffer[offset], remaining);
|
||||
size -= remaining;
|
||||
offset = 0;
|
||||
} while (rc_cd_read_sector(track_handle, ++sector, buffer, sizeof(buffer)) == sizeof(buffer));
|
||||
|
||||
rc_cd_close_track(track_handle);
|
||||
|
||||
if (size > 0)
|
||||
return rc_hash_error("Not enough data");
|
||||
|
||||
rc_hash_finalize(&md5, hash);
|
||||
|
||||
/* homebrew games all seem to have the same boot executable and store the actual game code in track 2.
|
||||
* if we generated something other than the homebrew hash, return it. assume all homebrews are byteswapped. */
|
||||
if (strcmp(hash, "254487b59ab21bc005338e85cbf9fd2f") != 0 || !byteswapped) {
|
||||
if (_rc_hash_jaguar_cd_homebrew_hash == NULL || strcmp(hash, _rc_hash_jaguar_cd_homebrew_hash) != 0)
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* if we've already been through the loop a second time, just return the hash */
|
||||
if (i == 1)
|
||||
return 1;
|
||||
++i;
|
||||
|
||||
if (verbose_message_callback)
|
||||
{
|
||||
snprintf(message, sizeof(message), "Potential homebrew at sector %u, checking for KART data in track 2", sector);
|
||||
rc_hash_verbose(message);
|
||||
}
|
||||
|
||||
track_handle = rc_cd_open_track(path, 2);
|
||||
if (!track_handle)
|
||||
return rc_hash_error("Could not open track");
|
||||
|
||||
/* track 2 of the homebrew code has the 64 bytes or ATRI followed by 32 bytes of "ATARI APPROVED DATA HEADER ATRI!",
|
||||
* then 64 bytes of KART repeating. */
|
||||
sector = rc_cd_first_track_sector(track_handle);
|
||||
rc_cd_read_sector(track_handle, sector, buffer, sizeof(buffer));
|
||||
if (memcmp(&buffer[0x5E], "RT!IRTKA", 8) != 0)
|
||||
return rc_hash_error("Homebrew executable not found in track 2");
|
||||
|
||||
/* found KART data*/
|
||||
if (verbose_message_callback)
|
||||
{
|
||||
snprintf(message, sizeof(message), "Found KART data in track 2");
|
||||
rc_hash_verbose(message);
|
||||
}
|
||||
|
||||
offset = 0xA6;
|
||||
size = (buffer[offset] << 16) | (buffer[offset + 1] << 24) | (buffer[offset + 2]) | (buffer[offset + 3] << 8);
|
||||
} while (1);
|
||||
}
|
||||
|
||||
static int rc_hash_lynx(char hash[33], const uint8_t* buffer, size_t buffer_size)
|
||||
{
|
||||
/* if the file contains a header, ignore it */
|
||||
|
@ -698,6 +900,70 @@ static int rc_hash_lynx(char hash[33], const uint8_t* buffer, size_t buffer_size
|
|||
return rc_hash_buffer(hash, buffer, buffer_size);
|
||||
}
|
||||
|
||||
static int rc_hash_neogeo_cd(char hash[33], const char* path)
|
||||
{
|
||||
char buffer[1024], *ptr;
|
||||
void* track_handle;
|
||||
uint32_t sector;
|
||||
unsigned size;
|
||||
md5_state_t md5;
|
||||
|
||||
track_handle = rc_cd_open_track(path, 1);
|
||||
if (!track_handle)
|
||||
return rc_hash_error("Could not open track");
|
||||
|
||||
/* https://wiki.neogeodev.org/index.php?title=IPL_file, https://wiki.neogeodev.org/index.php?title=PRG_file
|
||||
* IPL file specifies data to be loaded before the game starts. PRG files are the executable code
|
||||
*/
|
||||
sector = rc_cd_find_file_sector(track_handle, "IPL.TXT", &size);
|
||||
if (!sector)
|
||||
{
|
||||
rc_cd_close_track(track_handle);
|
||||
return rc_hash_error("Not a NeoGeo CD game disc");
|
||||
}
|
||||
|
||||
if (rc_cd_read_sector(track_handle, sector, buffer, sizeof(buffer)) == 0)
|
||||
{
|
||||
rc_cd_close_track(track_handle);
|
||||
return 0;
|
||||
}
|
||||
|
||||
md5_init(&md5);
|
||||
|
||||
buffer[sizeof(buffer) - 1] = '\0';
|
||||
ptr = &buffer[0];
|
||||
do
|
||||
{
|
||||
char* start = ptr;
|
||||
while (*ptr && *ptr != '.')
|
||||
++ptr;
|
||||
|
||||
if (strncasecmp(ptr, ".PRG", 4) == 0)
|
||||
{
|
||||
ptr += 4;
|
||||
*ptr++ = '\0';
|
||||
|
||||
sector = rc_cd_find_file_sector(track_handle, start, &size);
|
||||
if (!sector || !rc_hash_cd_file(&md5, track_handle, sector, NULL, size, start))
|
||||
{
|
||||
char error[128];
|
||||
rc_cd_close_track(track_handle);
|
||||
snprintf(error, sizeof(error), "Could not read %.16s", start);
|
||||
return rc_hash_error(error);
|
||||
}
|
||||
}
|
||||
|
||||
while (*ptr && *ptr != '\n')
|
||||
++ptr;
|
||||
if (*ptr != '\n')
|
||||
break;
|
||||
++ptr;
|
||||
} while (*ptr != '\0' && *ptr != '\x1a');
|
||||
|
||||
rc_cd_close_track(track_handle);
|
||||
return rc_hash_finalize(&md5, hash);
|
||||
}
|
||||
|
||||
static int rc_hash_nes(char hash[33], const uint8_t* buffer, size_t buffer_size)
|
||||
{
|
||||
/* if the file contains a header, ignore it */
|
||||
|
@ -719,34 +985,6 @@ static int rc_hash_nes(char hash[33], const uint8_t* buffer, size_t buffer_size)
|
|||
return rc_hash_buffer(hash, buffer, buffer_size);
|
||||
}
|
||||
|
||||
static void rc_hash_v64_to_z64(uint8_t* buffer, const uint8_t* stop)
|
||||
{
|
||||
uint32_t* ptr = (uint32_t*)buffer;
|
||||
const uint32_t* stop32 = (const uint32_t*)stop;
|
||||
while (ptr < stop32)
|
||||
{
|
||||
uint32_t temp = *ptr;
|
||||
temp = (temp & 0xFF00FF00) >> 8 |
|
||||
(temp & 0x00FF00FF) << 8;
|
||||
*ptr++ = temp;
|
||||
}
|
||||
}
|
||||
|
||||
static void rc_hash_n64_to_z64(uint8_t* buffer, const uint8_t* stop)
|
||||
{
|
||||
uint32_t* ptr = (uint32_t*)buffer;
|
||||
const uint32_t* stop32 = (const uint32_t*)stop;
|
||||
while (ptr < stop32)
|
||||
{
|
||||
uint32_t temp = *ptr;
|
||||
temp = (temp & 0xFF000000) >> 24 |
|
||||
(temp & 0x00FF0000) >> 8 |
|
||||
(temp & 0x0000FF00) << 8 |
|
||||
(temp & 0x000000FF) << 24;
|
||||
*ptr++ = temp;
|
||||
}
|
||||
}
|
||||
|
||||
static int rc_hash_n64(char hash[33], const char* path)
|
||||
{
|
||||
uint8_t* buffer;
|
||||
|
@ -787,6 +1025,9 @@ static int rc_hash_n64(char hash[33], const char* path)
|
|||
rc_hash_verbose("converting n64 to z64");
|
||||
is_n64 = 1;
|
||||
}
|
||||
else if (buffer[0] == 0xE8 || buffer[0] == 0x22) /* ndd format (don't byteswap) */
|
||||
{
|
||||
}
|
||||
else
|
||||
{
|
||||
free(buffer);
|
||||
|
@ -818,9 +1059,9 @@ static int rc_hash_n64(char hash[33], const char* path)
|
|||
rc_file_read(file_handle, buffer, (int)buffer_size);
|
||||
|
||||
if (is_v64)
|
||||
rc_hash_v64_to_z64(buffer, stop);
|
||||
rc_hash_byteswap16(buffer, stop);
|
||||
else if (is_n64)
|
||||
rc_hash_n64_to_z64(buffer, stop);
|
||||
rc_hash_byteswap32(buffer, stop);
|
||||
|
||||
md5_append(&md5, buffer, (int)buffer_size);
|
||||
remaining -= buffer_size;
|
||||
|
@ -832,9 +1073,9 @@ static int rc_hash_n64(char hash[33], const char* path)
|
|||
|
||||
stop = buffer + remaining;
|
||||
if (is_v64)
|
||||
rc_hash_v64_to_z64(buffer, stop);
|
||||
rc_hash_byteswap16(buffer, stop);
|
||||
else if (is_n64)
|
||||
rc_hash_n64_to_z64(buffer, stop);
|
||||
rc_hash_byteswap32(buffer, stop);
|
||||
|
||||
md5_append(&md5, buffer, (int)remaining);
|
||||
}
|
||||
|
@ -957,6 +1198,121 @@ static int rc_hash_nintendo_ds(char hash[33], const char* path)
|
|||
return rc_hash_finalize(&md5, hash);
|
||||
}
|
||||
|
||||
static int rc_hash_gamecube(char hash[33], const char* path)
|
||||
{
|
||||
md5_state_t md5;
|
||||
void* file_handle;
|
||||
const uint32_t BASE_HEADER_SIZE = 0x2440;
|
||||
const uint32_t MAX_HEADER_SIZE = 1024 * 1024;
|
||||
uint32_t apploader_header_size, apploader_body_size, apploader_trailer_size, header_size;
|
||||
uint8_t quad_buffer[4];
|
||||
uint8_t addr_buffer[0xD8];
|
||||
uint8_t* buffer;
|
||||
uint32_t dol_offset;
|
||||
uint32_t dol_offsets[18];
|
||||
uint32_t dol_sizes[18];
|
||||
uint32_t dol_buf_size = 0;
|
||||
uint32_t ix;
|
||||
|
||||
file_handle = rc_file_open(path);
|
||||
if (!file_handle)
|
||||
return rc_hash_error("Could not open file");
|
||||
|
||||
/* Verify Gamecube */
|
||||
rc_file_seek(file_handle, 0x1c, SEEK_SET);
|
||||
rc_file_read(file_handle, quad_buffer, 4);
|
||||
if (quad_buffer[0] != 0xC2|| quad_buffer[1] != 0x33 || quad_buffer[2] != 0x9F || quad_buffer[3] != 0x3D)
|
||||
{
|
||||
rc_file_close(file_handle);
|
||||
return rc_hash_error("Not a Gamecube disc");
|
||||
}
|
||||
|
||||
/* GetApploaderSize */
|
||||
rc_file_seek(file_handle, BASE_HEADER_SIZE + 0x14, SEEK_SET);
|
||||
apploader_header_size = 0x20;
|
||||
rc_file_read(file_handle, quad_buffer, 4);
|
||||
apploader_body_size =
|
||||
(quad_buffer[0] << 24) | (quad_buffer[1] << 16) | (quad_buffer[2] << 8) | quad_buffer[3];
|
||||
rc_file_read(file_handle, quad_buffer, 4);
|
||||
apploader_trailer_size =
|
||||
(quad_buffer[0] << 24) | (quad_buffer[1] << 16) | (quad_buffer[2] << 8) | quad_buffer[3];
|
||||
header_size = BASE_HEADER_SIZE + apploader_header_size + apploader_body_size + apploader_trailer_size;
|
||||
if (header_size > MAX_HEADER_SIZE) header_size = MAX_HEADER_SIZE;
|
||||
|
||||
/* Hash headers */
|
||||
buffer = (uint8_t*)malloc(header_size);
|
||||
if (!buffer)
|
||||
{
|
||||
rc_file_close(file_handle);
|
||||
return rc_hash_error("Could not allocate temporary buffer");
|
||||
}
|
||||
rc_file_seek(file_handle, 0, SEEK_SET);
|
||||
rc_file_read(file_handle, buffer, header_size);
|
||||
md5_init(&md5);
|
||||
if (verbose_message_callback)
|
||||
{
|
||||
char message[128];
|
||||
snprintf(message, sizeof(message), "Hashing %u byte header", header_size);
|
||||
verbose_message_callback(message);
|
||||
}
|
||||
md5_append(&md5, buffer, header_size);
|
||||
|
||||
/* GetBootDOLOffset
|
||||
* Base header size is guaranteed larger than 0x423 therefore buffer contains dol_offset right now
|
||||
*/
|
||||
dol_offset = (buffer[0x420] << 24) | (buffer[0x421] << 16) | (buffer[0x422] << 8) | buffer[0x423];
|
||||
free(buffer);
|
||||
|
||||
/* Find offsetsand sizes for the 7 main.dol code segments and 11 main.dol data segments */
|
||||
rc_file_seek(file_handle, dol_offset, SEEK_SET);
|
||||
rc_file_read(file_handle, addr_buffer, 0xD8);
|
||||
for (ix = 0; ix < 18; ix++)
|
||||
{
|
||||
dol_offsets[ix] =
|
||||
(addr_buffer[0x0 + ix * 4] << 24) |
|
||||
(addr_buffer[0x1 + ix * 4] << 16) |
|
||||
(addr_buffer[0x2 + ix * 4] << 8) |
|
||||
addr_buffer[0x3 + ix * 4];
|
||||
dol_sizes[ix] =
|
||||
(addr_buffer[0x90 + ix * 4] << 24) |
|
||||
(addr_buffer[0x91 + ix * 4] << 16) |
|
||||
(addr_buffer[0x92 + ix * 4] << 8) |
|
||||
addr_buffer[0x93 + ix * 4];
|
||||
dol_buf_size = (dol_sizes[ix] > dol_buf_size) ? dol_sizes[ix] : dol_buf_size;
|
||||
}
|
||||
|
||||
/* Iterate through the 18 main.dol segments and hash each */
|
||||
buffer = (uint8_t*)malloc(dol_buf_size);
|
||||
if (!buffer)
|
||||
{
|
||||
rc_file_close(file_handle);
|
||||
return rc_hash_error("Could not allocate temporary buffer");
|
||||
}
|
||||
for (ix = 0; ix < 18; ix++)
|
||||
{
|
||||
if (dol_sizes[ix] == 0)
|
||||
continue;
|
||||
rc_file_seek(file_handle, dol_offsets[ix], SEEK_SET);
|
||||
rc_file_read(file_handle, buffer, dol_sizes[ix]);
|
||||
if (verbose_message_callback)
|
||||
{
|
||||
char message[128];
|
||||
if (ix < 7)
|
||||
snprintf(message, sizeof(message), "Hashing %u byte main.dol code segment %u", dol_sizes[ix], ix);
|
||||
else
|
||||
snprintf(message, sizeof(message), "Hashing %u byte main.dol data segment %u", dol_sizes[ix], ix - 7);
|
||||
verbose_message_callback(message);
|
||||
}
|
||||
md5_append(&md5, buffer, dol_sizes[ix]);
|
||||
}
|
||||
|
||||
/* Finalize */
|
||||
rc_file_close(file_handle);
|
||||
free(buffer);
|
||||
|
||||
return rc_hash_finalize(&md5, hash);
|
||||
}
|
||||
|
||||
static int rc_hash_pce(char hash[33], const uint8_t* buffer, size_t buffer_size)
|
||||
{
|
||||
/* if the file contains a header, ignore it (expect ROM data to be multiple of 128KB) */
|
||||
|
@ -1290,7 +1646,7 @@ static int rc_hash_find_playstation_executable(void* track_handle, const char* b
|
|||
|
||||
if (strncmp(ptr, cdrom_prefix, cdrom_prefix_len) == 0)
|
||||
ptr += cdrom_prefix_len;
|
||||
if (*ptr == '\\')
|
||||
while (*ptr == '\\')
|
||||
++ptr;
|
||||
|
||||
start = ptr;
|
||||
|
@ -1454,18 +1810,30 @@ static int rc_hash_psp(char hash[33], const char* path)
|
|||
*/
|
||||
sector = rc_cd_find_file_sector(track_handle, "PSP_GAME\\PARAM.SFO", &size);
|
||||
if (!sector)
|
||||
{
|
||||
rc_cd_close_track(track_handle);
|
||||
return rc_hash_error("Not a PSP game disc");
|
||||
}
|
||||
|
||||
md5_init(&md5);
|
||||
if (!rc_hash_cd_file(&md5, track_handle, sector, NULL, size, "PSP_GAME\\PARAM.SFO"))
|
||||
{
|
||||
rc_cd_close_track(track_handle);
|
||||
return 0;
|
||||
}
|
||||
|
||||
sector = rc_cd_find_file_sector(track_handle, "PSP_GAME\\SYSDIR\\EBOOT.BIN", &size);
|
||||
if (!sector)
|
||||
{
|
||||
rc_cd_close_track(track_handle);
|
||||
return rc_hash_error("Could not find primary executable");
|
||||
}
|
||||
|
||||
if (!rc_hash_cd_file(&md5, track_handle, sector, NULL, size, "PSP_GAME\\SYSDIR\\EBOOT.BIN"))
|
||||
{
|
||||
rc_cd_close_track(track_handle);
|
||||
return 0;
|
||||
}
|
||||
|
||||
rc_cd_close_track(track_handle);
|
||||
return rc_hash_finalize(&md5, hash);
|
||||
|
@ -1527,7 +1895,11 @@ static struct rc_buffered_file rc_buffered_file;
|
|||
static void* rc_file_open_buffered_file(const char* path)
|
||||
{
|
||||
struct rc_buffered_file* handle = (struct rc_buffered_file*)malloc(sizeof(struct rc_buffered_file));
|
||||
memcpy(handle, &rc_buffered_file, sizeof(rc_buffered_file));
|
||||
(void)path;
|
||||
|
||||
if (handle)
|
||||
memcpy(handle, &rc_buffered_file, sizeof(rc_buffered_file));
|
||||
|
||||
return handle;
|
||||
}
|
||||
|
||||
|
@ -1631,7 +2003,9 @@ int rc_hash_generate_from_buffer(char hash[33], int console_id, const uint8_t* b
|
|||
case RC_CONSOLE_SEGA_32X:
|
||||
case RC_CONSOLE_SG1000:
|
||||
case RC_CONSOLE_SUPERVISION:
|
||||
case RC_CONSOLE_TI83:
|
||||
case RC_CONSOLE_TIC80:
|
||||
case RC_CONSOLE_UZEBOX:
|
||||
case RC_CONSOLE_VECTREX:
|
||||
case RC_CONSOLE_VIRTUAL_BOY:
|
||||
case RC_CONSOLE_WASM4:
|
||||
|
@ -1659,6 +2033,7 @@ int rc_hash_generate_from_buffer(char hash[33], int console_id, const uint8_t* b
|
|||
|
||||
case RC_CONSOLE_NINTENDO_64:
|
||||
case RC_CONSOLE_NINTENDO_DS:
|
||||
case RC_CONSOLE_NINTENDO_DSI:
|
||||
return rc_hash_file_from_buffer(hash, console_id, buffer, buffer_size);
|
||||
}
|
||||
}
|
||||
|
@ -1922,7 +2297,9 @@ int rc_hash_generate_from_file(char hash[33], int console_id, const char* path)
|
|||
case RC_CONSOLE_SEGA_32X:
|
||||
case RC_CONSOLE_SG1000:
|
||||
case RC_CONSOLE_SUPERVISION:
|
||||
case RC_CONSOLE_TI83:
|
||||
case RC_CONSOLE_TIC80:
|
||||
case RC_CONSOLE_UZEBOX:
|
||||
case RC_CONSOLE_VECTREX:
|
||||
case RC_CONSOLE_VIRTUAL_BOY:
|
||||
case RC_CONSOLE_WASM4:
|
||||
|
@ -1946,6 +2323,7 @@ int rc_hash_generate_from_file(char hash[33], int console_id, const char* path)
|
|||
case RC_CONSOLE_ATARI_7800:
|
||||
case RC_CONSOLE_ATARI_LYNX:
|
||||
case RC_CONSOLE_NINTENDO:
|
||||
case RC_CONSOLE_PC_ENGINE:
|
||||
case RC_CONSOLE_SUPER_NINTENDO:
|
||||
/* additional logic whole-file hash - buffer then call rc_hash_generate_from_buffer */
|
||||
return rc_hash_buffered_file(hash, console_id, path);
|
||||
|
@ -1959,13 +2337,29 @@ int rc_hash_generate_from_file(char hash[33], int console_id, const char* path)
|
|||
case RC_CONSOLE_ARCADE:
|
||||
return rc_hash_arcade(hash, path);
|
||||
|
||||
case RC_CONSOLE_ATARI_JAGUAR_CD:
|
||||
return rc_hash_jaguar_cd(hash, path);
|
||||
|
||||
case RC_CONSOLE_DREAMCAST:
|
||||
if (rc_path_compare_extension(path, "m3u"))
|
||||
return rc_hash_generate_from_playlist(hash, console_id, path);
|
||||
|
||||
return rc_hash_dreamcast(hash, path);
|
||||
|
||||
case RC_CONSOLE_GAMECUBE:
|
||||
return rc_hash_gamecube(hash, path);
|
||||
|
||||
case RC_CONSOLE_NEO_GEO_CD:
|
||||
return rc_hash_neogeo_cd(hash, path);
|
||||
|
||||
case RC_CONSOLE_NINTENDO_64:
|
||||
return rc_hash_n64(hash, path);
|
||||
|
||||
case RC_CONSOLE_NINTENDO_DS:
|
||||
case RC_CONSOLE_NINTENDO_DSI:
|
||||
return rc_hash_nintendo_ds(hash, path);
|
||||
|
||||
case RC_CONSOLE_PC_ENGINE:
|
||||
case RC_CONSOLE_PC_ENGINE_CD:
|
||||
if (rc_path_compare_extension(path, "cue") || rc_path_compare_extension(path, "chd"))
|
||||
return rc_hash_pce_cd(hash, path);
|
||||
|
||||
|
@ -1995,12 +2389,6 @@ int rc_hash_generate_from_file(char hash[33], int console_id, const char* path)
|
|||
case RC_CONSOLE_PSP:
|
||||
return rc_hash_psp(hash, path);
|
||||
|
||||
case RC_CONSOLE_DREAMCAST:
|
||||
if (rc_path_compare_extension(path, "m3u"))
|
||||
return rc_hash_generate_from_playlist(hash, console_id, path);
|
||||
|
||||
return rc_hash_dreamcast(hash, path);
|
||||
|
||||
case RC_CONSOLE_SEGA_CD:
|
||||
case RC_CONSOLE_SATURN:
|
||||
if (rc_path_compare_extension(path, "m3u"))
|
||||
|
@ -2077,7 +2465,7 @@ static void rc_hash_initialize_dsk_iterator(struct rc_hash_iterator* iterator, c
|
|||
rc_hash_iterator_append_console(iterator, RC_CONSOLE_APPLE_II);
|
||||
}
|
||||
|
||||
void rc_hash_initialize_iterator(struct rc_hash_iterator* iterator, const char* path, uint8_t* buffer, size_t buffer_size)
|
||||
void rc_hash_initialize_iterator(struct rc_hash_iterator* iterator, const char* path, const uint8_t* buffer, size_t buffer_size)
|
||||
{
|
||||
int need_path = !buffer;
|
||||
|
||||
|
@ -2108,6 +2496,15 @@ void rc_hash_initialize_iterator(struct rc_hash_iterator* iterator, const char*
|
|||
}
|
||||
break;
|
||||
|
||||
case '8':
|
||||
/* http://tibasicdev.wikidot.com/file-extensions */
|
||||
if (rc_path_compare_extension(ext, "83g") ||
|
||||
rc_path_compare_extension(ext, "83p"))
|
||||
{
|
||||
iterator->consoles[0] = RC_CONSOLE_TI83;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'a':
|
||||
if (rc_path_compare_extension(ext, "a78"))
|
||||
{
|
||||
|
@ -2162,9 +2559,11 @@ void rc_hash_initialize_iterator(struct rc_hash_iterator* iterator, const char*
|
|||
iterator->consoles[1] = RC_CONSOLE_PLAYSTATION_2;
|
||||
iterator->consoles[2] = RC_CONSOLE_DREAMCAST;
|
||||
iterator->consoles[3] = RC_CONSOLE_SEGA_CD; /* ASSERT: handles both Sega CD and Saturn */
|
||||
iterator->consoles[4] = RC_CONSOLE_PC_ENGINE;
|
||||
iterator->consoles[4] = RC_CONSOLE_PC_ENGINE_CD;
|
||||
iterator->consoles[5] = RC_CONSOLE_3DO;
|
||||
iterator->consoles[6] = RC_CONSOLE_PCFX;
|
||||
iterator->consoles[7] = RC_CONSOLE_NEO_GEO_CD;
|
||||
iterator->consoles[8] = RC_CONSOLE_ATARI_JAGUAR_CD;
|
||||
need_path = 1;
|
||||
}
|
||||
else if (rc_path_compare_extension(ext, "chd"))
|
||||
|
@ -2173,7 +2572,7 @@ void rc_hash_initialize_iterator(struct rc_hash_iterator* iterator, const char*
|
|||
iterator->consoles[1] = RC_CONSOLE_PLAYSTATION_2;
|
||||
iterator->consoles[2] = RC_CONSOLE_DREAMCAST;
|
||||
iterator->consoles[3] = RC_CONSOLE_SEGA_CD; /* ASSERT: handles both Sega CD and Saturn */
|
||||
iterator->consoles[4] = RC_CONSOLE_PC_ENGINE;
|
||||
iterator->consoles[4] = RC_CONSOLE_PC_ENGINE_CD;
|
||||
iterator->consoles[5] = RC_CONSOLE_3DO;
|
||||
iterator->consoles[6] = RC_CONSOLE_PCFX;
|
||||
need_path = 1;
|
||||
|
@ -2199,7 +2598,7 @@ void rc_hash_initialize_iterator(struct rc_hash_iterator* iterator, const char*
|
|||
}
|
||||
else if (rc_path_compare_extension(ext, "d64"))
|
||||
{
|
||||
iterator->consoles[0] = RC_CONSOLE_COMMODORE_64;
|
||||
iterator->consoles[0] = RC_CONSOLE_COMMODORE_64;
|
||||
}
|
||||
else if (rc_path_compare_extension(ext, "d88"))
|
||||
{
|
||||
|
@ -2219,7 +2618,7 @@ void rc_hash_initialize_iterator(struct rc_hash_iterator* iterator, const char*
|
|||
}
|
||||
else if (rc_path_compare_extension(ext, "fd"))
|
||||
{
|
||||
iterator->consoles[0] = RC_CONSOLE_THOMSONTO8; /* disk */
|
||||
iterator->consoles[0] = RC_CONSOLE_THOMSONTO8; /* disk */
|
||||
}
|
||||
break;
|
||||
|
||||
|
@ -2330,7 +2729,7 @@ void rc_hash_initialize_iterator(struct rc_hash_iterator* iterator, const char*
|
|||
}
|
||||
else if (rc_path_compare_extension(ext, "nds"))
|
||||
{
|
||||
iterator->consoles[0] = RC_CONSOLE_NINTENDO_DS;
|
||||
iterator->consoles[0] = RC_CONSOLE_NINTENDO_DS; /* ASSERT: handles both DS and DSi */
|
||||
}
|
||||
else if (rc_path_compare_extension(ext, "n64") ||
|
||||
rc_path_compare_extension(ext, "ndd"))
|
||||
|
@ -2412,6 +2811,13 @@ void rc_hash_initialize_iterator(struct rc_hash_iterator* iterator, const char*
|
|||
}
|
||||
break;
|
||||
|
||||
case 'u':
|
||||
if (rc_path_compare_extension(ext, "uze"))
|
||||
{
|
||||
iterator->consoles[0] = RC_CONSOLE_UZEBOX;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'v':
|
||||
if (rc_path_compare_extension(ext, "vb"))
|
||||
{
|
||||
|
|
|
@ -52,6 +52,7 @@
|
|||
*/
|
||||
|
||||
#include "md5.h"
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
|
||||
#undef BYTE_ORDER /* 1 = big-endian, -1 = little-endian, 0 = unknown */
|
||||
|
@ -161,7 +162,7 @@ md5_process(md5_state_t *pms, const md5_byte_t *data /*[64]*/)
|
|||
* On little-endian machines, we can process properly aligned
|
||||
* data without copying it.
|
||||
*/
|
||||
if (!((data - (const md5_byte_t *)0) & 3)) {
|
||||
if (!((ptrdiff_t)data & 3)) {
|
||||
/* data are properly aligned */
|
||||
X = (const md5_word_t *)data;
|
||||
} else {
|
||||
|
|
|
@ -675,7 +675,7 @@ bool HostDisplay::WriteScreenshotToFile(std::string filename, bool internal_reso
|
|||
u32 pixels_stride;
|
||||
GPUTexture::Format pixels_format;
|
||||
if (!RenderScreenshot(width, height,
|
||||
Common::Rectangle<s32>::FromExtents(draw_top, draw_left, draw_width, draw_height), &pixels,
|
||||
Common::Rectangle<s32>::FromExtents(draw_left, draw_top, draw_width, draw_height), &pixels,
|
||||
&pixels_stride, &pixels_format))
|
||||
{
|
||||
Log_ErrorPrintf("Failed to render %ux%u screenshot", width, height);
|
||||
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -104,7 +104,7 @@ static void SendPingCallback(s32 status_code, std::string content_type, Common::
|
|||
static void UnlockAchievementCallback(s32 status_code, std::string content_type,
|
||||
Common::HTTPDownloader::Request::Data data);
|
||||
static void SubmitLeaderboardCallback(s32 status_code, std::string content_type,
|
||||
Common::HTTPDownloader::Request::Data data);
|
||||
Common::HTTPDownloader::Request::Data data, u32 lboard_id);
|
||||
|
||||
static bool s_active = false;
|
||||
static bool s_logged_in = false;
|
||||
|
@ -137,7 +137,6 @@ static std::string s_rich_presence_string;
|
|||
static Common::Timer s_last_ping_time;
|
||||
|
||||
static u32 s_last_queried_lboard = 0;
|
||||
static u32 s_submitting_lboard_id = 0;
|
||||
static std::optional<std::vector<Achievements::LeaderboardEntry>> s_lboard_entries;
|
||||
|
||||
template<typename T>
|
||||
|
@ -356,7 +355,6 @@ void Achievements::ClearGameInfo(bool clear_achievements, bool clear_leaderboard
|
|||
}
|
||||
|
||||
s_last_queried_lboard = 0;
|
||||
s_submitting_lboard_id = 0;
|
||||
s_lboard_entries.reset();
|
||||
}
|
||||
|
||||
|
@ -682,7 +680,8 @@ void Achievements::FrameUpdate()
|
|||
#ifdef WITH_RAINTEGRATION
|
||||
if (IsUsingRAIntegration())
|
||||
{
|
||||
RA_DoAchievementsFrame();
|
||||
if (!System::IsPaused())
|
||||
RA_DoAchievementsFrame();
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
@ -692,7 +691,8 @@ void Achievements::FrameUpdate()
|
|||
if (HasActiveGame())
|
||||
{
|
||||
std::unique_lock lock(s_achievements_mutex);
|
||||
rc_runtime_do_frame(&s_rcheevos_runtime, &CheevosEventHandler, &PeekMemory, nullptr, nullptr);
|
||||
if (!System::IsPaused())
|
||||
rc_runtime_do_frame(&s_rcheevos_runtime, &CheevosEventHandler, &PeekMemory, nullptr, nullptr);
|
||||
UpdateRichPresence();
|
||||
|
||||
if (!IsTestModeActive())
|
||||
|
@ -1741,7 +1741,7 @@ void Achievements::UnlockAchievementCallback(s32 status_code, std::string conten
|
|||
}
|
||||
|
||||
void Achievements::SubmitLeaderboardCallback(s32 status_code, std::string content_type,
|
||||
Common::HTTPDownloader::Request::Data data)
|
||||
Common::HTTPDownloader::Request::Data data, u32 lboard_id)
|
||||
{
|
||||
if (!System::IsValid())
|
||||
return;
|
||||
|
@ -1755,11 +1755,7 @@ void Achievements::SubmitLeaderboardCallback(s32 status_code, std::string conten
|
|||
// Force the next leaderboard query to repopulate everything, just in case the user wants to see their new score
|
||||
s_last_queried_lboard = 0;
|
||||
|
||||
// RA API doesn't send us the leaderboard ID back.. hopefully we don't submit two at once :/
|
||||
if (s_submitting_lboard_id == 0)
|
||||
return;
|
||||
|
||||
const Leaderboard* lb = GetLeaderboardByID(std::exchange(s_submitting_lboard_id, 0u));
|
||||
const Leaderboard* lb = GetLeaderboardByID(lboard_id);
|
||||
if (!lb || !FullscreenUI::IsInitialized() || !g_settings.achievements_notifications)
|
||||
return;
|
||||
|
||||
|
@ -1870,17 +1866,16 @@ void Achievements::SubmitLeaderboard(u32 leaderboard_id, int value)
|
|||
return;
|
||||
}
|
||||
|
||||
std::unique_lock lock(s_achievements_mutex);
|
||||
|
||||
s_submitting_lboard_id = leaderboard_id;
|
||||
|
||||
RAPIRequest<rc_api_submit_lboard_entry_request_t, rc_api_init_submit_lboard_entry_request> request;
|
||||
request.username = s_username.c_str();
|
||||
request.api_token = s_api_token.c_str();
|
||||
request.game_hash = s_game_hash.c_str();
|
||||
request.leaderboard_id = leaderboard_id;
|
||||
request.score = value;
|
||||
request.Send(SubmitLeaderboardCallback);
|
||||
request.Send(
|
||||
[leaderboard_id](s32 status_code, const std::string& content_type, Common::HTTPDownloader::Request::Data data) {
|
||||
SubmitLeaderboardCallback(status_code, content_type, std::move(data), leaderboard_id);
|
||||
});
|
||||
}
|
||||
|
||||
void Achievements::AchievementPrimed(u32 achievement_id)
|
||||
|
|
|
@ -678,11 +678,16 @@ DEFINE_HOTKEY("OpenPauseMenu", TRANSLATABLE("Hotkeys", "General"), TRANSLATABLE(
|
|||
if (!pressed)
|
||||
FullscreenUI::OpenPauseMenu();
|
||||
})
|
||||
DEFINE_HOTKEY("OpenNetplayChat", TRANSLATABLE("Hotkeys", "General"), TRANSLATABLE("Hotkeys", "Open Netplay Chat"),
|
||||
DEFINE_HOTKEY("OpenNetplayChat", TRANSLATABLE("Hotkeys", "Netplay"), TRANSLATABLE("Hotkeys", "Open Netplay Chat"),
|
||||
[](s32 pressed) {
|
||||
if (!pressed)
|
||||
ImGuiManager::OpenNetplayChat();
|
||||
})
|
||||
DEFINE_HOTKEY("ToggleDesyncNotifications", TRANSLATABLE("Hotkeys", "Netplay"), TRANSLATABLE("Hotkeys", "Toggle Desync Notifications"),
|
||||
[](s32 pressed) {
|
||||
if (!pressed)
|
||||
Netplay::ToggleDesyncNotifications();
|
||||
})
|
||||
#endif
|
||||
|
||||
DEFINE_HOTKEY("FastForward", TRANSLATABLE("Hotkeys", "General"), TRANSLATABLE("Hotkeys", "Fast Forward"),
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
|
||||
|
||||
#include "common/log.h"
|
||||
#include "common/scoped_guard.h"
|
||||
#include "common/string.h"
|
||||
#include "input_manager.h"
|
||||
#include "platform_misc.h"
|
||||
|
@ -41,68 +42,77 @@ static bool SetScreensaverInhibitX11(bool inhibit, const WindowInfo& wi)
|
|||
|
||||
#elif defined(USE_DBUS)
|
||||
#include <dbus/dbus.h>
|
||||
bool ChangeScreenSaverStateDBus(const bool inhibit_requested, const char* program_name, const char* reason)
|
||||
static bool SetScreensaverInhibitDBus(const bool inhibit_requested, const char* program_name, const char* reason)
|
||||
{
|
||||
static dbus_uint32_t s_cookie;
|
||||
// "error_dbus" doesn't need to be cleared in the end with "dbus_message_unref" at least if there is
|
||||
// no error set, since calling "dbus_error_free" reinitializes it like "dbus_error_init" after freeing.
|
||||
DBusError error_dbus;
|
||||
dbus_error_init(&error_dbus);
|
||||
const char* bus_method = (inhibit_requested) ? "Inhibit" : "UnInhibit";
|
||||
DBusError error;
|
||||
DBusConnection* connection = nullptr;
|
||||
static DBusConnection* s_comparison_connection;
|
||||
DBusMessage* message = nullptr;
|
||||
DBusMessage* response = nullptr;
|
||||
// Initialized here because initializations should be before "goto" statements.
|
||||
const char* bus_method = (inhibit_requested) ? "Inhibit" : "UnInhibit";
|
||||
// "dbus_bus_get" gets a pointer to the same connection in libdbus, if exists, without creating a new connection.
|
||||
// this doesn't need to be deleted, except if there's an error then calling "dbus_connection_unref", to free it,
|
||||
// might be better so a new connection is established on the next try.
|
||||
if (!(connection = dbus_bus_get(DBUS_BUS_SESSION, &error_dbus)) || (dbus_error_is_set(&error_dbus)))
|
||||
goto cleanup;
|
||||
if (!(message = dbus_message_new_method_call("org.freedesktop.ScreenSaver", "/org/freedesktop/ScreenSaver",
|
||||
"org.freedesktop.ScreenSaver", bus_method)))
|
||||
goto cleanup;
|
||||
// Initialize an append iterator for the message, gets freed with the message.
|
||||
DBusMessageIter message_itr;
|
||||
|
||||
ScopedGuard cleanup = [&]() {
|
||||
if (dbus_error_is_set(&error))
|
||||
{
|
||||
Log_ErrorPrintf("SetScreensaverInhibitDBus error: %s", error.message);
|
||||
dbus_error_free(&error);
|
||||
}
|
||||
if (message)
|
||||
dbus_message_unref(message);
|
||||
if (response)
|
||||
dbus_message_unref(response);
|
||||
};
|
||||
|
||||
dbus_error_init(&error);
|
||||
// Calling dbus_bus_get() after the first time returns a pointer to the existing connection.
|
||||
connection = dbus_bus_get(DBUS_BUS_SESSION, &error);
|
||||
if (!connection || (dbus_error_is_set(&error)))
|
||||
return false;
|
||||
if (s_comparison_connection != connection)
|
||||
{
|
||||
dbus_connection_set_exit_on_disconnect(connection, false);
|
||||
s_cookie = 0;
|
||||
s_comparison_connection = connection;
|
||||
}
|
||||
message = dbus_message_new_method_call("org.freedesktop.ScreenSaver", "/org/freedesktop/ScreenSaver",
|
||||
"org.freedesktop.ScreenSaver", bus_method);
|
||||
if (!message)
|
||||
return false;
|
||||
// Initialize an append iterator for the message, gets freed with the message.
|
||||
dbus_message_iter_init_append(message, &message_itr);
|
||||
if (inhibit_requested)
|
||||
{
|
||||
// Guard against repeat inhibitions which would add extra inhibitors each generating a different cookie.
|
||||
if (s_cookie)
|
||||
return false;
|
||||
// Append process/window name.
|
||||
if (!dbus_message_iter_append_basic(&message_itr, DBUS_TYPE_STRING, &program_name))
|
||||
goto cleanup;
|
||||
return false;
|
||||
// Append reason for inhibiting the screensaver.
|
||||
if (!dbus_message_iter_append_basic(&message_itr, DBUS_TYPE_STRING, &reason))
|
||||
goto cleanup;
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Only Append the cookie.
|
||||
if (!dbus_message_iter_append_basic(&message_itr, DBUS_TYPE_UINT32, &s_cookie))
|
||||
goto cleanup;
|
||||
return false;
|
||||
}
|
||||
// Send message and get response.
|
||||
if (!(response =
|
||||
dbus_connection_send_with_reply_and_block(connection, message, DBUS_TIMEOUT_USE_DEFAULT, &error_dbus)) ||
|
||||
dbus_error_is_set(&error_dbus))
|
||||
goto cleanup;
|
||||
response = dbus_connection_send_with_reply_and_block(connection, message, DBUS_TIMEOUT_USE_DEFAULT, &error);
|
||||
if (!response || dbus_error_is_set(&error))
|
||||
return false;
|
||||
s_cookie = 0;
|
||||
if (inhibit_requested)
|
||||
{
|
||||
// Get the cookie from the response message.
|
||||
if (!dbus_message_get_args(response, &error_dbus, DBUS_TYPE_UINT32, &s_cookie, DBUS_TYPE_INVALID))
|
||||
goto cleanup;
|
||||
if (!dbus_message_get_args(response, &error, DBUS_TYPE_UINT32, &s_cookie, DBUS_TYPE_INVALID) ||
|
||||
dbus_error_is_set(&error))
|
||||
return false;
|
||||
}
|
||||
dbus_message_unref(message);
|
||||
dbus_message_unref(response);
|
||||
return true;
|
||||
cleanup:
|
||||
if (dbus_error_is_set(&error_dbus))
|
||||
dbus_error_free(&error_dbus);
|
||||
if (connection)
|
||||
dbus_connection_unref(connection);
|
||||
if (message)
|
||||
dbus_message_unref(message);
|
||||
if (response)
|
||||
dbus_message_unref(response);
|
||||
return false;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
@ -110,7 +120,7 @@ cleanup:
|
|||
static bool SetScreensaverInhibit(bool inhibit)
|
||||
{
|
||||
#ifdef USE_DBUS
|
||||
return ChangeScreenSaverStateDBus(inhibit, "DuckStation", "DuckStation VM is running.");
|
||||
return SetScreensaverInhibitDBus(inhibit, "DuckStation", "DuckStation VM is running.");
|
||||
#else
|
||||
|
||||
std::optional<WindowInfo> wi(Host::GetTopLevelWindowInfo());
|
||||
|
|
|
@ -7,14 +7,19 @@
|
|||
|
||||
#include "cd_image.h"
|
||||
#include "cd_subchannel_replacement.h"
|
||||
|
||||
#include "common/align.h"
|
||||
#include "common/assert.h"
|
||||
#include "common/error.h"
|
||||
#include "common/file_system.h"
|
||||
#include "common/log.h"
|
||||
#include "common/path.h"
|
||||
#include "common/platform.h"
|
||||
#include "common/string_util.h"
|
||||
|
||||
#include "fmt/format.h"
|
||||
#include "libchdr/chd.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cerrno>
|
||||
#include <cstdio>
|
||||
|
@ -22,6 +27,7 @@
|
|||
#include <limits>
|
||||
#include <map>
|
||||
#include <optional>
|
||||
|
||||
Log_SetChannel(CDImageCHD);
|
||||
|
||||
static std::optional<CDImage::TrackMode> ParseTrackModeString(const char* str)
|
||||
|
@ -46,6 +52,7 @@ static std::optional<CDImage::TrackMode> ParseTrackModeString(const char* str)
|
|||
return std::nullopt;
|
||||
}
|
||||
|
||||
namespace {
|
||||
class CDImageCHD : public CDImage
|
||||
{
|
||||
public:
|
||||
|
@ -66,12 +73,13 @@ private:
|
|||
enum : u32
|
||||
{
|
||||
CHD_CD_SECTOR_DATA_SIZE = 2352 + 96,
|
||||
CHD_CD_TRACK_ALIGNMENT = 4
|
||||
CHD_CD_TRACK_ALIGNMENT = 4,
|
||||
MAX_PARENTS = 32 // Surely someone wouldn't be insane enough to go beyond this...
|
||||
};
|
||||
|
||||
chd_file* OpenCHD(const char* filename, FileSystem::ManagedCFilePtr fp, Common::Error* error, u32 recursion_level);
|
||||
bool ReadHunk(u32 hunk_index);
|
||||
|
||||
std::FILE* m_fp = nullptr;
|
||||
chd_file* m_chd = nullptr;
|
||||
u32 m_hunk_size = 0;
|
||||
u32 m_sectors_per_hunk = 0;
|
||||
|
@ -82,6 +90,7 @@ private:
|
|||
|
||||
CDSubChannelReplacement m_sbi;
|
||||
};
|
||||
} // namespace
|
||||
|
||||
CDImageCHD::CDImageCHD() = default;
|
||||
|
||||
|
@ -89,15 +98,102 @@ CDImageCHD::~CDImageCHD()
|
|||
{
|
||||
if (m_chd)
|
||||
chd_close(m_chd);
|
||||
if (m_fp)
|
||||
std::fclose(m_fp);
|
||||
}
|
||||
|
||||
chd_file* CDImageCHD::OpenCHD(const char* filename, FileSystem::ManagedCFilePtr fp, Common::Error* error,
|
||||
u32 recursion_level)
|
||||
{
|
||||
chd_file* chd;
|
||||
chd_error err = chd_open_file(fp.get(), CHD_OPEN_READ | CHD_OPEN_TRANSFER_FILE, nullptr, &chd);
|
||||
if (err == CHDERR_NONE)
|
||||
{
|
||||
// fp is now managed by libchdr
|
||||
fp.release();
|
||||
return chd;
|
||||
}
|
||||
else if (err != CHDERR_REQUIRES_PARENT)
|
||||
{
|
||||
Log_ErrorPrintf("Failed to open CHD '%s': %s", filename, chd_error_string(err));
|
||||
if (error)
|
||||
error->SetMessage(chd_error_string(err));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (recursion_level >= MAX_PARENTS)
|
||||
{
|
||||
Log_ErrorPrintf("Failed to open CHD '%s': Too many parent files", filename);
|
||||
if (error)
|
||||
error->SetMessage("Too many parent files");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Need to get the sha1 to look for.
|
||||
chd_header header;
|
||||
err = chd_read_header_file(fp.get(), &header);
|
||||
if (err != CHDERR_NONE)
|
||||
{
|
||||
Log_ErrorPrintf("Failed to read CHD header '%s': %s", filename, chd_error_string(err));
|
||||
if (error)
|
||||
error->SetMessage(chd_error_string(err));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Find a chd with a matching sha1 in the same directory.
|
||||
// Have to do *.* and filter on the extension manually because Linux is case sensitive.
|
||||
// We _could_ memoize the CHD headers here, but is anyone actually going to nest CHDs that deep?
|
||||
chd_file* parent_chd = nullptr;
|
||||
const std::string parent_dir(Path::GetDirectory(filename));
|
||||
FileSystem::FindResultsArray parent_files;
|
||||
FileSystem::FindFiles(parent_dir.c_str(), "*.*", FILESYSTEM_FIND_FILES | FILESYSTEM_FIND_HIDDEN_FILES, &parent_files);
|
||||
for (FILESYSTEM_FIND_DATA& fd : parent_files)
|
||||
{
|
||||
if (StringUtil::EndsWithNoCase(Path::GetExtension(fd.FileName), ".chd"))
|
||||
continue;
|
||||
|
||||
auto parent_fp =
|
||||
FileSystem::OpenManagedSharedCFile(fd.FileName.c_str(), "rb", FileSystem::FileShareMode::DenyWrite);
|
||||
if (!parent_fp)
|
||||
continue;
|
||||
|
||||
chd_header parent_header;
|
||||
err = chd_read_header_file(parent_fp.get(), &parent_header);
|
||||
if (err != CHDERR_NONE || !chd_is_matching_parent(&header, &parent_header))
|
||||
continue;
|
||||
|
||||
// Match! Open this one.
|
||||
if ((parent_chd = OpenCHD(fd.FileName.c_str(), std::move(parent_fp), error, recursion_level + 1)) != nullptr)
|
||||
{
|
||||
Log_DevPrintf(fmt::format("Found parent CHD '{}' for '{}'.", Path::GetFileName(fd.FileName), Path::GetFileName(filename)).c_str());
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!parent_chd)
|
||||
{
|
||||
Log_ErrorPrintf("Failed to open CHD '%s': Failed to find parent CHD, it must be in the same directory.", filename);
|
||||
if (error)
|
||||
error->SetMessage("Failed to find parent CHD, it must be in the same directory.");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Now try re-opening with the parent.
|
||||
err = chd_open_file(fp.get(), CHD_OPEN_READ | CHD_OPEN_TRANSFER_FILE, parent_chd, &chd);
|
||||
if (err != CHDERR_NONE)
|
||||
{
|
||||
Log_ErrorPrintf("Failed to open CHD '%s': %s", filename, chd_error_string(err));
|
||||
if (error)
|
||||
error->SetMessage(chd_error_string(err));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// fp now owned by libchdr
|
||||
fp.release();
|
||||
return chd;
|
||||
}
|
||||
|
||||
bool CDImageCHD::Open(const char* filename, Common::Error* error)
|
||||
{
|
||||
Assert(!m_fp);
|
||||
m_fp = FileSystem::OpenCFile(filename, "rb");
|
||||
if (!m_fp)
|
||||
auto fp = FileSystem::OpenManagedSharedCFile(filename, "rb", FileSystem::FileShareMode::DenyWrite);
|
||||
if (!fp)
|
||||
{
|
||||
Log_ErrorPrintf("Failed to open CHD '%s': errno %d", filename, errno);
|
||||
if (error)
|
||||
|
@ -106,15 +202,9 @@ bool CDImageCHD::Open(const char* filename, Common::Error* error)
|
|||
return false;
|
||||
}
|
||||
|
||||
chd_error err = chd_open_file(m_fp, CHD_OPEN_READ, nullptr, &m_chd);
|
||||
if (err != CHDERR_NONE)
|
||||
{
|
||||
Log_ErrorPrintf("Failed to open CHD '%s': %s", filename, chd_error_string(err));
|
||||
if (error)
|
||||
error->SetMessage(chd_error_string(err));
|
||||
|
||||
m_chd = OpenCHD(filename, std::move(fp), error, 0);
|
||||
if (!m_chd)
|
||||
return false;
|
||||
}
|
||||
|
||||
const chd_header* header = chd_get_header(m_chd);
|
||||
m_hunk_size = header->hunkbytes;
|
||||
|
@ -146,8 +236,8 @@ bool CDImageCHD::Open(const char* filename, Common::Error* error)
|
|||
u32 metadata_length;
|
||||
|
||||
int track_num = 0, frames = 0, pregap_frames = 0, postgap_frames = 0;
|
||||
err = chd_get_metadata(m_chd, CDROM_TRACK_METADATA2_TAG, num_tracks, metadata_str, sizeof(metadata_str),
|
||||
&metadata_length, nullptr, nullptr);
|
||||
chd_error err = chd_get_metadata(m_chd, CDROM_TRACK_METADATA2_TAG, num_tracks, metadata_str, sizeof(metadata_str),
|
||||
&metadata_length, nullptr, nullptr);
|
||||
if (err == CHDERR_NONE)
|
||||
{
|
||||
if (std::sscanf(metadata_str, CDROM_TRACK_METADATA2_FORMAT, &track_num, type_str, subtype_str, &frames,
|
||||
|
|
Loading…
Reference in New Issue