Merge pull request #2993 from HeatXD/nat_traversal

Sync with master
This commit is contained in:
Connor McLaughlin 2023-08-15 13:12:08 +10:00 committed by GitHub
commit cf89fe4845
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
54 changed files with 10853 additions and 1320 deletions

View File

@ -1,6 +1,6 @@
/* /*
FLAC audio decoder. Choice of public domain or MIT-0. See license statements at the end of this file. 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 David Reid - mackron@gmail.com
@ -210,6 +210,9 @@ Build Options
#define DR_FLAC_NO_SIMD #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. 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 Notes
@ -232,7 +235,7 @@ extern "C" {
#define DRFLAC_VERSION_MAJOR 0 #define DRFLAC_VERSION_MAJOR 0
#define DRFLAC_VERSION_MINOR 12 #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) #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. */ #include <stddef.h> /* For size_t. */
@ -383,15 +386,13 @@ typedef enum
drflac_seek_origin_current drflac_seek_origin_current
} drflac_seek_origin; } drflac_seek_origin;
/* Packing is important on this structure because we map this directly to the raw data within the SEEKTABLE metadata block. */ /* The order of members in this structure is important because we map this directly to the raw data within the SEEKTABLE metadata block. */
#pragma pack(2)
typedef struct typedef struct
{ {
drflac_uint64 firstPCMFrame; drflac_uint64 firstPCMFrame;
drflac_uint64 flacFrameOffset; /* The offset from the first byte of the header of the first frame. */ drflac_uint64 flacFrameOffset; /* The offset from the first byte of the header of the first frame. */
drflac_uint16 pcmFrameCount; drflac_uint16 pcmFrameCount;
} drflac_seekpoint; } drflac_seekpoint;
#pragma pack()
typedef struct typedef struct
{ {
@ -1280,15 +1281,13 @@ typedef struct
const char* pRunningData; const char* pRunningData;
} drflac_cuesheet_track_iterator; } drflac_cuesheet_track_iterator;
/* Packing is important on this structure because we map this directly to the raw data within the CUESHEET metadata block. */ /* The order of members here is important because we map this directly to the raw data within the CUESHEET metadata block. */
#pragma pack(4)
typedef struct typedef struct
{ {
drflac_uint64 offset; drflac_uint64 offset;
drflac_uint8 index; drflac_uint8 index;
drflac_uint8 reserved[3]; drflac_uint8 reserved[3];
} drflac_cuesheet_track_index; } drflac_cuesheet_track_index;
#pragma pack()
typedef struct 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. I am using "__inline__" only when we're compiling in strict ANSI mode.
*/ */
#if defined(__STRICT_ANSI__) #if defined(__STRICT_ANSI__)
#define DRFLAC_INLINE __inline__ __attribute__((always_inline)) #define DRFLAC_GNUC_INLINE_HINT __inline__
#else #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 #endif
#elif defined(__WATCOMC__) #elif defined(__WATCOMC__)
#define DRFLAC_INLINE __inline #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_SUPPORT_SSE41)
#if (defined(DRFLAC_X64) || defined(DRFLAC_X86)) && !defined(DRFLAC_NO_SSE41) #if (defined(DRFLAC_X64) || defined(DRFLAC_X86)) && !defined(DRFLAC_NO_SSE41)
#if defined(DRFLAC_X64) #if defined(__SSE4_1__) || defined(__AVX__)
return DRFLAC_TRUE; /* 64-bit targets always support SSE4.1. */
#elif (defined(_M_IX86_FP) && _M_IX86_FP == 2) || defined(__SSE4_1__)
return DRFLAC_TRUE; /* If the compiler is allowed to freely generate SSE41 code we can assume support. */ return DRFLAC_TRUE; /* If the compiler is allowed to freely generate SSE41 code we can assume support. */
#else #else
#if defined(DRFLAC_NO_CPUID) #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); extern __inline drflac_uint64 _watcom_bswap64(drflac_uint64);
#pragma aux _watcom_bswap16 = \ #pragma aux _watcom_bswap16 = \
"xchg al, ah" \ "xchg al, ah" \
parm [ax] \ parm [ax] \
modify [ax]; value [ax] \
modify nomemory;
#pragma aux _watcom_bswap32 = \ #pragma aux _watcom_bswap32 = \
"bswap eax" \ "bswap eax" \
parm [eax] \ parm [eax] \
modify [eax]; value [eax] \
modify nomemory;
#pragma aux _watcom_bswap64 = \ #pragma aux _watcom_bswap64 = \
"bswap eax" \ "bswap eax" \
"bswap edx" \ "bswap edx" \
"xchg eax,edx" \ "xchg eax,edx" \
parm [eax edx] \ parm [eax edx] \
modify [eax edx]; value [eax edx] \
modify nomemory;
#endif #endif
@ -1688,6 +1694,10 @@ typedef drflac_int32 drflac_result;
#define DRFLAC_CHANNEL_ASSIGNMENT_RIGHT_SIDE 9 #define DRFLAC_CHANNEL_ASSIGNMENT_RIGHT_SIDE 9
#define DRFLAC_CHANNEL_ASSIGNMENT_MID_SIDE 10 #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)) #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__) #if defined(__WATCOMC__) && defined(__386__)
#define DRFLAC_IMPLEMENT_CLZ_WATCOM #define DRFLAC_IMPLEMENT_CLZ_WATCOM
#endif #endif
#ifdef __MRC__
#include <intrinsics.h>
#define DRFLAC_IMPLEMENT_CLZ_MRC
#endif
static DRFLAC_INLINE drflac_uint32 drflac__clz_software(drflac_cache_t x) 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. */ /* Fast compile time check for ARM. */
#if defined(DRFLAC_HAS_LZCNT_INTRINSIC) && defined(DRFLAC_ARM) && (defined(__ARM_ARCH) && __ARM_ARCH >= 5) #if defined(DRFLAC_HAS_LZCNT_INTRINSIC) && defined(DRFLAC_ARM) && (defined(__ARM_ARCH) && __ARM_ARCH >= 5)
return DRFLAC_TRUE; return DRFLAC_TRUE;
#elif defined(__MRC__)
return DRFLAC_TRUE;
#else #else
/* If the compiler itself does not support the intrinsic then we'll need to return false. */ /* If the compiler itself does not support the intrinsic then we'll need to return false. */
#ifdef DRFLAC_HAS_LZCNT_INTRINSIC #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 #ifdef DRFLAC_IMPLEMENT_CLZ_WATCOM
static __inline drflac_uint32 drflac__clz_watcom (drflac_uint32); 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 = \ #pragma aux drflac__clz_watcom = \
"bsr eax, eax" \ "bsr eax, eax" \
"xor eax, 31" \ "xor eax, 31" \
@ -2846,6 +2871,7 @@ static __inline drflac_uint32 drflac__clz_watcom (drflac_uint32);
value [eax] \ value [eax] \
modify exact [eax] nomemory; modify exact [eax] nomemory;
#endif #endif
#endif
static DRFLAC_INLINE drflac_uint32 drflac__clz(drflac_cache_t x) 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 #ifdef DRFLAC_IMPLEMENT_CLZ_MSVC
return drflac__clz_msvc(x); return drflac__clz_msvc(x);
#elif defined(DRFLAC_IMPLEMENT_CLZ_WATCOM_LZCNT)
return drflac__clz_watcom_lzcnt(x);
#elif defined(DRFLAC_IMPLEMENT_CLZ_WATCOM) #elif defined(DRFLAC_IMPLEMENT_CLZ_WATCOM)
return (x == 0) ? sizeof(x)*8 : drflac__clz_watcom(x); return (x == 0) ? sizeof(x)*8 : drflac__clz_watcom(x);
#elif defined(__MRC__)
return __cntlzw(x);
#else #else
return drflac__clz_software(x); return drflac__clz_software(x);
#endif #endif
@ -4420,7 +4450,7 @@ static drflac_bool32 drflac__decode_samples_with_residual__rice__neon_32(drflac_
const drflac_uint32 t[2] = {0x00000000, 0xFFFFFFFF}; const drflac_uint32 t[2] = {0x00000000, 0xFFFFFFFF};
riceParamMask = ~((~0UL) << riceParam); riceParamMask = (drflac_uint32)~((~0UL) << riceParam);
riceParamMask128 = vdupq_n_u32(riceParamMask); riceParamMask128 = vdupq_n_u32(riceParamMask);
riceParam128 = vdupq_n_s32(riceParam); riceParam128 = vdupq_n_s32(riceParam);
@ -4606,10 +4636,13 @@ static drflac_bool32 drflac__decode_samples_with_residual__rice__neon_64(drflac_
int32x4_t riceParam128; int32x4_t riceParam128;
int64x1_t shift64; int64x1_t shift64;
uint32x4_t one128; uint32x4_t one128;
int64x2_t prediction128 = { 0 };
uint32x4_t zeroCountPart128;
uint32x4_t riceParamPart128;
const drflac_uint32 t[2] = {0x00000000, 0xFFFFFFFF}; const drflac_uint32 t[2] = {0x00000000, 0xFFFFFFFF};
riceParamMask = ~((~0UL) << riceParam); riceParamMask = (drflac_uint32)~((~0UL) << riceParam);
riceParamMask128 = vdupq_n_u32(riceParamMask); riceParamMask128 = vdupq_n_u32(riceParamMask);
riceParam128 = vdupq_n_s32(riceParam); 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. */ /* For this version we are doing one sample at a time. */
while (pDecodedSamples < pDecodedSamplesEnd) { 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]) || 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[1], &riceParamParts[1]) ||
!drflac__read_rice_parts_x1(bs, riceParam, &zeroCountParts[2], &riceParamParts[2]) || !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 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; seektableSize = blockSize;
if (onMeta) { if (onMeta) {
drflac_uint32 seekpointCount;
drflac_uint32 iSeekpoint; drflac_uint32 iSeekpoint;
void* pRawData; 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) { if (pRawData == NULL) {
return DRFLAC_FALSE; return DRFLAC_FALSE;
} }
if (onRead(pUserData, pRawData, blockSize) != blockSize) { /* We need to read seekpoint by seekpoint and do some processing. */
drflac__free_from_callbacks(pRawData, pAllocationCallbacks); for (iSeekpoint = 0; iSeekpoint < seekpointCount; ++iSeekpoint) {
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) {
drflac_seekpoint* pSeekpoint = (drflac_seekpoint*)pRawData + 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->firstPCMFrame = drflac__be2host_64(pSeekpoint->firstPCMFrame);
pSeekpoint->flacFrameOffset = drflac__be2host_64(pSeekpoint->flacFrameOffset); pSeekpoint->flacFrameOffset = drflac__be2host_64(pSeekpoint->flacFrameOffset);
pSeekpoint->pcmFrameCount = drflac__be2host_16(pSeekpoint->pcmFrameCount); 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); onMeta(pUserDataMD, &metadata);
drflac__free_from_callbacks(pRawData, pAllocationCallbacks); 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; void* pRawData;
const char* pRunningData; const char* pRunningData;
const char* pRunningDataEnd; const char* pRunningDataEnd;
size_t bufferSize;
drflac_uint8 iTrack; drflac_uint8 iTrack;
drflac_uint8 iIndex; 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); pRawData = drflac__malloc_from_callbacks(blockSize, pAllocationCallbacks);
if (pRawData == NULL) { if (pRawData == NULL) {
return DRFLAC_FALSE; 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.leadInSampleCount = drflac__be2host_64(*(const drflac_uint64*)pRunningData); pRunningData += 8;
metadata.data.cuesheet.isCD = (pRunningData[0] & 0x80) != 0; pRunningData += 259; metadata.data.cuesheet.isCD = (pRunningData[0] & 0x80) != 0; pRunningData += 259;
metadata.data.cuesheet.trackCount = pRunningData[0]; pRunningData += 1; 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 */ /* Pass 1: Calculate the size of the buffer for the track data. */
for (iTrack = 0; iTrack < metadata.data.cuesheet.trackCount; ++iTrack) { {
drflac_uint8 indexCount; const char* pRunningDataSaved = pRunningData; /* Will be restored at the end in preparation for the second pass. */
drflac_uint32 indexPointSize;
if (pRunningDataEnd - pRunningData < 36) { bufferSize = metadata.data.cuesheet.trackCount * DRFLAC_CUESHEET_TRACK_SIZE_IN_BYTES;
drflac__free_from_callbacks(pRawData, pAllocationCallbacks);
return DRFLAC_FALSE; 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 = pRunningDataSaved;
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);
}
} }
/* 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); onMeta(pUserDataMD, &metadata);
drflac__free_from_callbacks(pRawData, pAllocationCallbacks); drflac__free_from_callbacks(pTrackData, pAllocationCallbacks);
pTrackData = NULL;
} }
} break; } break;
@ -6700,7 +6793,7 @@ static drflac_bool32 drflac__read_and_decode_metadata(drflac_read_proc onRead, d
drflac__free_from_callbacks(pRawData, pAllocationCallbacks); drflac__free_from_callbacks(pRawData, pAllocationCallbacks);
return DRFLAC_FALSE; 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; metadata.data.picture.descriptionLength = drflac__be2host_32_ptr_unaligned(pRunningData); pRunningData += 4;
/* Need space for the rest of the block */ /* 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); drflac__free_from_callbacks(pRawData, pAllocationCallbacks);
return DRFLAC_FALSE; 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.width = drflac__be2host_32_ptr_unaligned(pRunningData); pRunningData += 4;
metadata.data.picture.height = 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; 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; *pSeektablePos = seektablePos;
*pSeektableSize = seektableSize; *pSeekpointCount = seektableSize / DRFLAC_SEEKPOINT_SIZE_IN_BYTES;
*pFirstFramePos = runningFilePos; *pFirstFramePos = runningFilePos;
return DRFLAC_TRUE; return DRFLAC_TRUE;
} }
@ -7823,11 +7916,11 @@ static drflac* drflac_open_with_metadata_private(drflac_read_proc onRead, drflac
drflac_uint32 wholeSIMDVectorCountPerChannel; drflac_uint32 wholeSIMDVectorCountPerChannel;
drflac_uint32 decodedSamplesAllocationSize; drflac_uint32 decodedSamplesAllocationSize;
#ifndef DR_FLAC_NO_OGG #ifndef DR_FLAC_NO_OGG
drflac_oggbs oggbs; drflac_oggbs* pOggbs = NULL;
#endif #endif
drflac_uint64 firstFramePos; drflac_uint64 firstFramePos;
drflac_uint64 seektablePos; drflac_uint64 seektablePos;
drflac_uint32 seektableSize; drflac_uint32 seekpointCount;
drflac_allocation_callbacks allocationCallbacks; drflac_allocation_callbacks allocationCallbacks;
drflac* pFlac; 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. */ /* There's additional data required for Ogg streams. */
if (init.container == drflac_container_ogg) { if (init.container == drflac_container_ogg) {
allocationSize += sizeof(drflac_oggbs); allocationSize += sizeof(drflac_oggbs);
}
DRFLAC_ZERO_MEMORY(&oggbs, sizeof(oggbs)); pOggbs = (drflac_oggbs*)drflac__malloc_from_callbacks(sizeof(*pOggbs), &allocationCallbacks);
if (init.container == drflac_container_ogg) { if (pOggbs == NULL) {
oggbs.onRead = onRead; return NULL; /*DRFLAC_OUT_OF_MEMORY;*/
oggbs.onSeek = onSeek; }
oggbs.pUserData = pUserData;
oggbs.currentBytePos = init.oggFirstBytePos; DRFLAC_ZERO_MEMORY(pOggbs, sizeof(*pOggbs));
oggbs.firstBytePos = init.oggFirstBytePos; pOggbs->onRead = onRead;
oggbs.serialNumber = init.oggSerial; pOggbs->onSeek = onSeek;
oggbs.bosPageHeader = init.oggBosHeader; pOggbs->pUserData = pUserData;
oggbs.bytesRemainingInPage = 0; pOggbs->currentBytePos = init.oggFirstBytePos;
pOggbs->firstBytePos = init.oggFirstBytePos;
pOggbs->serialNumber = init.oggSerial;
pOggbs->bosPageHeader = init.oggBosHeader;
pOggbs->bytesRemainingInPage = 0;
} }
#endif #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 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. and decoding the metadata.
*/ */
firstFramePos = 42; /* <-- We know we are at byte 42 at this point. */ firstFramePos = 42; /* <-- We know we are at byte 42 at this point. */
seektablePos = 0; seektablePos = 0;
seektableSize = 0; seekpointCount = 0;
if (init.hasMetadataBlocks) { if (init.hasMetadataBlocks) {
drflac_read_proc onReadOverride = onRead; drflac_read_proc onReadOverride = onRead;
drflac_seek_proc onSeekOverride = onSeek; 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) { if (init.container == drflac_container_ogg) {
onReadOverride = drflac__on_read_ogg; onReadOverride = drflac__on_read_ogg;
onSeekOverride = drflac__on_seek_ogg; onSeekOverride = drflac__on_seek_ogg;
pUserDataOverride = (void*)&oggbs; pUserDataOverride = (void*)pOggbs;
} }
#endif #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; return NULL;
} }
allocationSize += seektableSize; allocationSize += seekpointCount * sizeof(drflac_seekpoint);
} }
pFlac = (drflac*)drflac__malloc_from_callbacks(allocationSize, &allocationCallbacks); pFlac = (drflac*)drflac__malloc_from_callbacks(allocationSize, &allocationCallbacks);
if (pFlac == NULL) { if (pFlac == NULL) {
#ifndef DR_FLAC_NO_OGG
drflac__free_from_callbacks(pOggbs, &allocationCallbacks);
#endif
return NULL; return NULL;
} }
@ -7936,8 +8038,12 @@ static drflac* drflac_open_with_metadata_private(drflac_read_proc onRead, drflac
#ifndef DR_FLAC_NO_OGG #ifndef DR_FLAC_NO_OGG
if (init.container == drflac_container_ogg) { if (init.container == drflac_container_ogg) {
drflac_oggbs* pInternalOggbs = (drflac_oggbs*)((drflac_uint8*)pFlac->pDecodedSamples + decodedSamplesAllocationSize + seektableSize); drflac_oggbs* pInternalOggbs = (drflac_oggbs*)((drflac_uint8*)pFlac->pDecodedSamples + decodedSamplesAllocationSize + (seekpointCount * sizeof(drflac_seekpoint)));
*pInternalOggbs = oggbs; 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. */ /* The Ogg bistream needs to be layered on top of the original bitstream. */
pFlac->bs.onRead = drflac__on_read_ogg; 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 we have a seektable we need to load it now, making sure we move back to where we were previously. */
if (seektablePos != 0) { if (seektablePos != 0) {
pFlac->seekpointCount = seektableSize / sizeof(*pFlac->pSeekpoints); pFlac->seekpointCount = seekpointCount;
pFlac->pSeekpoints = (drflac_seekpoint*)((drflac_uint8*)pFlac->pDecodedSamples + decodedSamplesAllocationSize); pFlac->pSeekpoints = (drflac_seekpoint*)((drflac_uint8*)pFlac->pDecodedSamples + decodedSamplesAllocationSize);
DRFLAC_ASSERT(pFlac->bs.onSeek != NULL); 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. */ /* 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.onSeek(pFlac->bs.pUserData, (int)seektablePos, drflac_seek_origin_start)) {
if (pFlac->bs.onRead(pFlac->bs.pUserData, pFlac->pSeekpoints, seektableSize) == seektableSize) { drflac_uint32 iSeekpoint;
/* Endian swap. */
drflac_uint32 iSeekpoint; for (iSeekpoint = 0; iSeekpoint < seekpointCount; iSeekpoint += 1) {
for (iSeekpoint = 0; iSeekpoint < pFlac->seekpointCount; ++iSeekpoint) { 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].firstPCMFrame = drflac__be2host_64(pFlac->pSeekpoints[iSeekpoint].firstPCMFrame);
pFlac->pSeekpoints[iSeekpoint].flacFrameOffset = drflac__be2host_64(pFlac->pSeekpoints[iSeekpoint].flacFrameOffset); pFlac->pSeekpoints[iSeekpoint].flacFrameOffset = drflac__be2host_64(pFlac->pSeekpoints[iSeekpoint].flacFrameOffset);
pFlac->pSeekpoints[iSeekpoint].pcmFrameCount = drflac__be2host_16(pFlac->pSeekpoints[iSeekpoint].pcmFrameCount); 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. */ /* 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 #ifndef DR_FLAC_NO_STDIO
#include <stdio.h> #include <stdio.h>
#ifndef DR_FLAC_NO_WCHAR
#include <wchar.h> /* For wcslen(), wcsrtombs() */ #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. */ /* 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> #include <errno.h>
@ -8495,6 +8605,7 @@ fallback, so if you notice your compiler not detecting this properly I'm happy t
#endif #endif
#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) static drflac_result drflac_wfopen(FILE** ppFile, const wchar_t* pFilePath, const wchar_t* pOpenMode, const drflac_allocation_callbacks* pAllocationCallbacks)
{ {
if (ppFile != NULL) { if (ppFile != NULL) {
@ -8523,10 +8634,23 @@ static drflac_result drflac_wfopen(FILE** ppFile, const wchar_t* pFilePath, cons
} }
#else #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 Use fopen() on anything other than Windows. Requires a conversion. This is annoying because
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 fopen() is locale specific. The only real way I can think of to do this is with wcsrtombs(). Note
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. 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; mbstate_t mbs;
size_t lenMB; 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); drflac__free_from_callbacks(pFilePathMB, pAllocationCallbacks);
} }
#endif
if (*ppFile == NULL) { if (*ppFile == NULL) {
return DRFLAC_ERROR; return DRFLAC_ERROR;
@ -8576,6 +8701,7 @@ static drflac_result drflac_wfopen(FILE** ppFile, const wchar_t* pFilePath, cons
return DRFLAC_SUCCESS; return DRFLAC_SUCCESS;
} }
#endif
static size_t drflac__on_read_stdio(void* pUserData, void* bufferOut, size_t bytesToRead) 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; return pFlac;
} }
#ifndef DR_FLAC_NO_WCHAR
DRFLAC_API drflac* drflac_open_file_w(const wchar_t* pFileName, const drflac_allocation_callbacks* pAllocationCallbacks) DRFLAC_API drflac* drflac_open_file_w(const wchar_t* pFileName, const drflac_allocation_callbacks* pAllocationCallbacks)
{ {
drflac* pFlac; drflac* pFlac;
@ -8625,6 +8752,7 @@ DRFLAC_API drflac* drflac_open_file_w(const wchar_t* pFileName, const drflac_all
return pFlac; 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) 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; 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_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; drflac* pFlac;
@ -8661,6 +8790,7 @@ DRFLAC_API drflac* drflac_open_file_with_metadata_w(const wchar_t* pFileName, dr
return pFlac; return pFlac;
} }
#endif
#endif /* DR_FLAC_NO_STDIO */ #endif /* DR_FLAC_NO_STDIO */
static size_t drflac__on_read_memory(void* pUserData, void* bufferOut, size_t bytesToRead) 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 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 v0.12.37 - 2022-02-12
- Improve ARM detection. - Improve ARM detection.

View File

@ -48,6 +48,7 @@ extern "C" {
#include <libchdr/coretypes.h> #include <libchdr/coretypes.h>
#include <libchdr/chdconfig.h> #include <libchdr/chdconfig.h>
#include <stdbool.h>
/*************************************************************************** /***************************************************************************
@ -204,6 +205,9 @@ extern "C" {
#define CHD_CODEC_NONE 0 #define CHD_CODEC_NONE 0
#define CHD_CODEC_ZLIB CHD_MAKE_TAG('z','l','i','b') #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 */ /* general codecs with CD frontend */
#define CHD_CODEC_CD_ZLIB CHD_MAKE_TAG('c','d','z','l') #define CHD_CODEC_CD_ZLIB CHD_MAKE_TAG('c','d','z','l')
#define CHD_CODEC_CD_LZMA CHD_MAKE_TAG('c','d','l','z') #define CHD_CODEC_CD_LZMA CHD_MAKE_TAG('c','d','l','z')
@ -253,6 +257,7 @@ extern "C" {
/* CHD open values */ /* CHD open values */
#define CHD_OPEN_READ 1 #define CHD_OPEN_READ 1
#define CHD_OPEN_READWRITE 2 #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 */ /* error types */
enum _chd_error 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); */ /* chd_error chd_create_file(core_file *file, UINT64 logicalbytes, UINT32 hunkbytes, UINT32 compression, chd_file *parent); */
/* open an existing CHD file */ /* 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); CHD_EXPORT chd_error chd_open(const char *filename, int mode, chd_file *parent, chd_file **chd);
/* precache underlying file */ /* 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); CHD_EXPORT const chd_header *chd_get_header(chd_file *chd);
/* read CHD header data from file into the pointed struct */ /* 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 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);

View File

@ -4,8 +4,21 @@
#include <stdint.h> #include <stdint.h>
#include <stdio.h> #include <stdio.h>
#ifdef USE_LIBRETRO_VFS
#include <streams/file_stream_transforms.h>
#endif
#define ARRAY_LENGTH(x) (sizeof(x)/sizeof(x[0])) #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 uint64_t UINT64;
typedef uint32_t UINT32; typedef uint32_t UINT32;
typedef uint16_t UINT16; typedef uint16_t UINT16;
@ -16,33 +29,50 @@ typedef int32_t INT32;
typedef int16_t INT16; typedef int16_t INT16;
typedef int8_t INT8; typedef int8_t INT8;
#define core_file FILE typedef struct chd_core_file {
#define core_fopen(file) fopen(file, "rb") /*
* 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 * return the size of a given file as a 64-bit unsigned integer.
#define core_ftell _ftelli64 * the position of the file pointer after calling this function is
#elif defined(_LARGEFILE_SOURCE) && defined(_FILE_OFFSET_BITS) && _FILE_OFFSET_BITS == 64 * undefined because many implementations will seek to the end of the
#define core_fseek fseeko64 * file and call ftell.
#define core_ftell ftello64 *
#elif defined(__PS3__) && !defined(__PSL1GHT__) || defined(__SWITCH__) * on error, (UINT64)-1 is returned.
#define core_fseek(x,y,z) fseek(x,(off_t)y,z) */
#define core_ftell(x) (off_t)ftell(x) UINT64(*fsize)(struct chd_core_file*);
#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
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; return fp->fsize(fp);
UINT64 p = core_ftell(f);
core_fseek(f, 0, SEEK_END);
rv = core_ftell(f);
core_fseek(f, p, SEEK_SET);
return rv;
} }
#endif #endif

View File

@ -42,6 +42,7 @@
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <time.h> #include <time.h>
#include <limits.h>
#include <libchdr/chd.h> #include <libchdr/chd.h>
#include <libchdr/cdrom.h> #include <libchdr/cdrom.h>
@ -227,6 +228,12 @@ struct _lzma_codec_data
lzma_allocator allocator; 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 */ /* codec-private data for the CDZL codec */
typedef struct _cdzl_codec_data cdzl_codec_data; typedef struct _cdzl_codec_data cdzl_codec_data;
struct _cdzl_codec_data { struct _cdzl_codec_data {
@ -249,6 +256,14 @@ struct _cdlz_codec_data {
uint8_t* buffer; 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 */ /* codec-private data for the CDFL codec */
typedef struct _cdfl_codec_data cdfl_codec_data; typedef struct _cdfl_codec_data cdfl_codec_data;
struct _cdfl_codec_data { struct _cdfl_codec_data {
@ -267,7 +282,6 @@ struct _chd_file
UINT32 cookie; /* cookie, should equal COOKIE_VALUE */ UINT32 cookie; /* cookie, should equal COOKIE_VALUE */
core_file * file; /* handle to the open core file */ 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_header header; /* header, extracted from file */
chd_file * parent; /* pointer to parent file, or NULL */ 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 */ const codec_interface * codecintf[4]; /* interface to the codec */
zlib_codec_data zlib_codec_data; /* zlib codec data */ 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 */ cdzl_codec_data cdzl_codec_data; /* cdzl codec data */
cdlz_codec_data cdlz_codec_data; /* cdlz codec data */ cdlz_codec_data cdlz_codec_data; /* cdlz codec data */
cdfl_codec_data cdfl_codec_data; /* cdfl codec data */ cdfl_codec_data cdfl_codec_data; /* cdfl codec data */
@ -309,6 +326,14 @@ static const UINT8 nullsha1[CHD_SHA1_BYTES] = { 0 };
PROTOTYPES 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 */ /* internal header operations */
static chd_error header_validate(const chd_header *header); static chd_error header_validate(const chd_header *header);
static chd_error header_read(chd_file *chd, 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 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); 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 */ /* cdzl compression codec */
static chd_error cdzl_codec_init(void* codec, uint32_t hunkbytes); static chd_error cdzl_codec_init(void* codec, uint32_t hunkbytes);
static void cdzl_codec_free(void* codec); 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; 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 * 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 /* determine FLAC block size, which must be 16-65535
* clamp to 2k since that's supposed to be the sweet spot */ * clamp to 2k since that's supposed to be the sweet spot */
uint32_t hunkbytes = bytes / 4; uint32_t blocksize = bytes / 4;
while (hunkbytes > 2048) while (blocksize > 2048)
hunkbytes /= 2; blocksize /= 2;
return hunkbytes; 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) static chd_error cdfl_codec_init(void *codec, uint32_t hunkbytes)
@ -904,6 +1036,39 @@ static const codec_interface codec_interfaces[] =
NULL 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 */ /* V5 CD zlib compression */
{ {
CHD_CODEC_CD_ZLIB, CHD_CODEC_CD_ZLIB,
@ -1185,6 +1350,8 @@ static chd_error decompress_v5_map(chd_file* chd, chd_header* header)
if (!chd_compressed(header)) if (!chd_compressed(header))
{ {
header->rawmap = (uint8_t*)malloc(rawmapsize); header->rawmap = (uint8_t*)malloc(rawmapsize);
if (header->rawmap == NULL)
return CHDERR_OUT_OF_MEMORY;
core_fseek(chd->file, header->mapoffset, SEEK_SET); core_fseek(chd->file, header->mapoffset, SEEK_SET);
result = core_fread(chd->file, header->rawmap, rawmapsize); result = core_fread(chd->file, header->rawmap, rawmapsize);
return CHDERR_NONE; return CHDERR_NONE;
@ -1202,10 +1369,18 @@ static chd_error decompress_v5_map(chd_file* chd, chd_header* header)
/* now read the map */ /* now read the map */
compressed_ptr = (uint8_t*)malloc(sizeof(uint8_t) * mapbytes); 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); core_fseek(chd->file, header->mapoffset + 16, SEEK_SET);
result = core_fread(chd->file, compressed_ptr, mapbytes); result = core_fread(chd->file, compressed_ptr, mapbytes);
bitbuf = create_bitstream(compressed_ptr, sizeof(uint8_t) * mapbytes); bitbuf = create_bitstream(compressed_ptr, sizeof(uint8_t) * mapbytes);
header->rawmap = (uint8_t*)malloc(rawmapsize); 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 */ /* first decode the compression types */
decoder = create_huffman_decoder(16, 8); 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_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_file *newchd = NULL;
chd_error err; 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; codec = &newchd->zlib_codec_data;
break; 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: case CHD_CODEC_CD_ZLIB:
codec = &newchd->cdzl_codec_data; codec = &newchd->cdzl_codec_data;
break; break;
@ -1532,33 +1742,33 @@ cleanup:
CHD_EXPORT chd_error chd_precache(chd_file* chd) 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 #define PRECACHE_CHUNK_SIZE 16 * 1024 * 1024
#ifdef _MSC_VER size_t count;
size_t size, done, req_count, count, last_update_done, update_interval; UINT64 size, done, req_count, last_update_done, update_interval;
#else
ssize_t size, done, req_count, count, last_update_done, update_interval;
#endif
if (chd->file_cache == NULL) if (chd->file_cache == NULL)
{ {
core_fseek(chd->file, 0, SEEK_END); size = core_fsize(chd->file);
size = core_ftell(chd->file); if ((INT64)size <= 0)
if (size <= 0) return CHDERR_INVALID_DATA;
return CHDERR_INVALID_DATA;
chd->file_cache = malloc(size); if (size > SIZE_MAX)
if (chd->file_cache == NULL)
return CHDERR_OUT_OF_MEMORY; 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; done = 0;
last_update_done = 0; last_update_done = 0;
update_interval = ((size + 99) / 100); update_interval = ((size + 99) / 100);
while (done < size) 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) if (req_count > PRECACHE_CHUNK_SIZE)
req_count = PRECACHE_CHUNK_SIZE; req_count = PRECACHE_CHUNK_SIZE;
count = core_fread(chd->file, chd->file_cache + done, req_count); count = core_fread(chd->file, chd->file_cache + (size_t)done, (size_t)req_count);
if (count != req_count) if (count != (size_t)req_count)
{ {
free(chd->file_cache); free(chd->file_cache);
chd->file_cache = NULL; 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); 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 */ /* open the file */
file = core_fopen(filename); file = core_stdio_fopen(filename);
if (file == 0) if (file == 0)
{ {
err = CHDERR_FILE_NOT_FOUND; 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 */ /* now open the CHD */
err = chd_open_file(file, mode, parent, chd); return chd_open_core_file(file, mode, parent, chd);
if (err != CHDERR_NONE)
goto cleanup;
/* we now own this file */
(*chd)->owns_file = TRUE;
cleanup: cleanup:
if ((err != CHDERR_NONE) && (file != NULL)) if ((err != CHDERR_NONE) && (file != NULL))
@ -1664,18 +1869,30 @@ CHD_EXPORT void chd_close(chd_file *chd)
switch (chd->codecintf[i]->compression) switch (chd->codecintf[i]->compression)
{ {
case CHD_CODEC_CD_LZMA:
codec = &chd->cdlz_codec_data;
break;
case CHD_CODEC_ZLIB: case CHD_CODEC_ZLIB:
codec = &chd->zlib_codec_data; codec = &chd->zlib_codec_data;
break; 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: case CHD_CODEC_CD_ZLIB:
codec = &chd->cdzl_codec_data; codec = &chd->cdzl_codec_data;
break; break;
case CHD_CODEC_CD_LZMA:
codec = &chd->cdlz_codec_data;
break;
case CHD_CODEC_CD_FLAC: case CHD_CODEC_CD_FLAC:
codec = &chd->cdfl_codec_data; codec = &chd->cdfl_codec_data;
break; break;
@ -1709,7 +1926,7 @@ CHD_EXPORT void chd_close(chd_file *chd)
free(chd->map); free(chd->map);
/* close the file */ /* close the file */
if (chd->owns_file && chd->file != NULL) if (chd->file != NULL)
core_fclose(chd->file); core_fclose(chd->file);
#ifdef NEED_CACHE_HUNK #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 chd_read_header - read CHD header data
from file into the pointed struct 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; chd_file chd;
chd.file = file;
/* 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);
/* attempt to read the header */ /* attempt to read the header */
err = header_read(&chd, header); const chd_error err = header_read(&chd, header);
if (err != CHDERR_NONE) if (err != CHDERR_NONE)
EARLY_EXIT(err); return err;
/* validate the header */ /* validate the header */
err = header_validate(header); return header_validate(header);
if (err != CHDERR_NONE) }
EARLY_EXIT(err);
cleanup: CHD_EXPORT chd_error chd_read_header_file(FILE *file, chd_header *header)
if (chd.file != NULL) {
core_fclose(chd.file); 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; 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 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->logicalbytes = (UINT64)header->obsolete_cylinders * (UINT64)header->obsolete_heads * (UINT64)header->obsolete_sectors * (UINT64)seclen;
header->hunkbytes = seclen * header->obsolete_hunksize; header->hunkbytes = seclen * header->obsolete_hunksize;
header->unitbytes = header_guess_unitbytes(chd); header->unitbytes = header_guess_unitbytes(chd);
if (header->unitbytes == 0)
return CHDERR_INVALID_DATA;
header->unitcount = (header->logicalbytes + header->unitbytes - 1) / header->unitbytes; header->unitcount = (header->logicalbytes + header->unitbytes - 1) / header->unitbytes;
header->metaoffset = 0; 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); memcpy(header->parentmd5, &rawheader[60], CHD_MD5_BYTES);
header->hunkbytes = get_bigendian_uint32(&rawheader[76]); header->hunkbytes = get_bigendian_uint32(&rawheader[76]);
header->unitbytes = header_guess_unitbytes(chd); header->unitbytes = header_guess_unitbytes(chd);
if (header->unitbytes == 0)
return CHDERR_INVALID_DATA;
header->unitcount = (header->logicalbytes + header->unitbytes - 1) / header->unitbytes; header->unitcount = (header->logicalbytes + header->unitbytes - 1) / header->unitbytes;
memcpy(header->sha1, &rawheader[80], CHD_SHA1_BYTES); memcpy(header->sha1, &rawheader[80], CHD_SHA1_BYTES);
memcpy(header->parentsha1, &rawheader[100], 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->metaoffset = get_bigendian_uint64(&rawheader[36]);
header->hunkbytes = get_bigendian_uint32(&rawheader[44]); header->hunkbytes = get_bigendian_uint32(&rawheader[44]);
header->unitbytes = header_guess_unitbytes(chd); header->unitbytes = header_guess_unitbytes(chd);
if (header->unitbytes == 0)
return CHDERR_INVALID_DATA;
header->unitcount = (header->logicalbytes + header->unitbytes - 1) / header->unitbytes; header->unitcount = (header->logicalbytes + header->unitbytes - 1) / header->unitbytes;
memcpy(header->sha1, &rawheader[48], CHD_SHA1_BYTES); memcpy(header->sha1, &rawheader[48], CHD_SHA1_BYTES);
memcpy(header->parentsha1, &rawheader[68], 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->mapoffset = get_bigendian_uint64(&rawheader[40]);
header->metaoffset = get_bigendian_uint64(&rawheader[48]); header->metaoffset = get_bigendian_uint64(&rawheader[48]);
header->hunkbytes = get_bigendian_uint32(&rawheader[56]); 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->hunkcount = (header->logicalbytes + header->hunkbytes - 1) / header->hunkbytes;
header->unitbytes = get_bigendian_uint32(&rawheader[60]); header->unitbytes = get_bigendian_uint32(&rawheader[60]);
if (header->unitbytes == 0)
return CHDERR_INVALID_DATA;
header->unitcount = (header->logicalbytes + header->unitbytes - 1) / header->unitbytes; header->unitcount = (header->logicalbytes + header->unitbytes - 1) / header->unitbytes;
memcpy(header->sha1, &rawheader[84], CHD_SHA1_BYTES); memcpy(header->sha1, &rawheader[84], CHD_SHA1_BYTES);
memcpy(header->parentsha1, &rawheader[104], 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; return CHDERR_READ_ERROR;
switch (chd->codecintf[rawmap[0]]->compression) switch (chd->codecintf[rawmap[0]]->compression)
{ {
case CHD_CODEC_CD_LZMA:
codec = &chd->cdlz_codec_data;
break;
case CHD_CODEC_ZLIB: case CHD_CODEC_ZLIB:
codec = &chd->zlib_codec_data; codec = &chd->zlib_codec_data;
break; 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: case CHD_CODEC_CD_ZLIB:
codec = &chd->cdzl_codec_data; codec = &chd->cdzl_codec_data;
break; break;
case CHD_CODEC_CD_LZMA:
codec = &chd->cdlz_codec_data;
break;
case CHD_CODEC_CD_FLAC: case CHD_CODEC_CD_FLAC:
codec = &chd->cdfl_codec_data; codec = &chd->cdfl_codec_data;
break; break;
@ -2625,10 +2895,6 @@ static chd_error zlib_codec_init(void *codec, uint32_t hunkbytes)
else else
err = CHDERR_NONE; err = CHDERR_NONE;
/* handle an error */
if (err != CHDERR_NONE)
free(data);
return err; return err;
} }
@ -2768,3 +3034,87 @@ static void zlib_allocator_free(voidpf opaque)
if (alloc->allocptr[i]) if (alloc->allocptr[i])
free(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);
}

View File

@ -60,9 +60,10 @@ int flac_decoder_init(flac_decoder *decoder)
void flac_decoder_free(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); drflac_close(decoder->decoder);
decoder->decoder = NULL; decoder->decoder = NULL;
}
} }
/*------------------------------------------------- /*-------------------------------------------------

View File

@ -212,6 +212,8 @@ enum huffman_error huffman_import_tree_rle(struct huffman_decoder* decoder, stru
else else
{ {
int repcount = bitstream_read(bitbuf, numbits) + 3; int repcount = bitstream_read(bitbuf, numbits) + 3;
if (repcount + curnode > decoder->numcodes)
return HUFFERR_INVALID_DATA;
while (repcount--) while (repcount--)
decoder->huffnode[curnode++].numbits = nodebits; decoder->huffnode[curnode++].numbits = nodebits;
} }

View File

@ -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_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_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); void rc_api_destroy_fetch_code_notes_response(rc_api_fetch_code_notes_response_t* response);
/* --- Update Code Note --- */ /* --- 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_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_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); void rc_api_destroy_update_code_note_response(rc_api_update_code_note_response_t* response);
/* --- Update Achievement --- */ /* --- 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_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_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); void rc_api_destroy_update_achievement_response(rc_api_update_achievement_response_t* response);
/* --- Update Leaderboard --- */ /* --- 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_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_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); void rc_api_destroy_update_leaderboard_response(rc_api_update_leaderboard_response_t* response);
/* --- Fetch Badge Range --- */ /* --- 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_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_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); void rc_api_destroy_fetch_badge_range_response(rc_api_fetch_badge_range_response_t* response);
/* --- Add Game Hash --- */ /* --- 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_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_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); void rc_api_destroy_add_game_hash_response(rc_api_add_game_hash_response_t* response);
#ifdef __cplusplus #ifdef __cplusplus

View File

@ -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_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_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); void rc_api_destroy_fetch_achievement_info_response(rc_api_fetch_achievement_info_response_t* response);
/* --- Fetch Leaderboard Info --- */ /* --- 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_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_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); void rc_api_destroy_fetch_leaderboard_info_response(rc_api_fetch_leaderboard_info_response_t* response);
/* --- Fetch Games List --- */ /* --- 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_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_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); void rc_api_destroy_fetch_games_list_response(rc_api_fetch_games_list_response_t* response);
#ifdef __cplusplus #ifdef __cplusplus

View File

@ -3,6 +3,8 @@
#include "rc_error.h" #include "rc_error.h"
#include <stddef.h>
#ifdef __cplusplus #ifdef __cplusplus
extern "C" { extern "C" {
#endif #endif
@ -10,15 +12,25 @@ extern "C" {
/** /**
* A block of memory for variable length data (like strings and arrays). * 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 */ /* The current location where data is being written */
char* write; char* write;
/* The first byte past the end of data where writing cannot occur */ /* The first byte past the end of data where writing cannot occur */
char* end; char* end;
/* The first byte of the data */
char* start;
/* The next block in the allocated memory chain */ /* The next block in the allocated memory chain */
struct rc_api_buffer_t* next; struct rc_api_buffer_chunk_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. */ 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]; char data[256];
} }
rc_api_buffer_t; rc_api_buffer_t;
@ -31,6 +43,8 @@ typedef struct rc_api_request_t {
const char* url; const char* url;
/* Additional query args that should be sent via a POST command. If null, GET may be used */ /* Additional query args that should be sent via a POST command. If null, GET may be used */
const char* post_data; const char* post_data;
/* The HTTP Content-Type of the POST data. */
const char* content_type;
/* Storage for the url and post_data */ /* Storage for the url and post_data */
rc_api_buffer_t buffer; rc_api_buffer_t buffer;
@ -41,7 +55,7 @@ rc_api_request_t;
* Common attributes for all server responses. * Common attributes for all server responses.
*/ */
typedef struct rc_api_response_t { 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; int succeeded;
/* Server-provided message associated to the failure */ /* Server-provided message associated to the failure */
const char* error_message; 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_host(const char* hostname);
void rc_api_set_image_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 #ifdef __cplusplus
} }
#endif #endif

View File

@ -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_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_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); void rc_api_destroy_resolve_hash_response(rc_api_resolve_hash_response_t* response);
/* --- Fetch Game Data --- */ /* --- 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_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_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); void rc_api_destroy_fetch_game_data_response(rc_api_fetch_game_data_response_t* response);
/* --- Ping --- */ /* --- 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_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_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); void rc_api_destroy_ping_response(rc_api_ping_response_t* response);
/* --- Award Achievement --- */ /* --- Award Achievement --- */
@ -214,6 +217,8 @@ typedef struct rc_api_award_achievement_response_t {
unsigned awarded_achievement_id; unsigned awarded_achievement_id;
/* The updated player score */ /* The updated player score */
unsigned new_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 /* The number of achievements the user has not yet unlocked for this game
* (in hardcore/non-hardcore per hardcore flag in request) */ * (in hardcore/non-hardcore per hardcore flag in request) */
unsigned achievements_remaining; 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_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_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); void rc_api_destroy_award_achievement_response(rc_api_award_achievement_response_t* response);
/* --- Submit Leaderboard Entry --- */ /* --- 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_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_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); void rc_api_destroy_submit_lboard_entry_response(rc_api_submit_lboard_entry_response_t* response);
#ifdef __cplusplus #ifdef __cplusplus

View File

@ -33,6 +33,8 @@ typedef struct rc_api_login_response_t {
const char* api_token; const char* api_token;
/* The current score of the player */ /* The current score of the player */
unsigned score; 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 */ /* The number of unread messages waiting for the player on the web site */
unsigned num_unread_messages; unsigned num_unread_messages;
/* The preferred name to display for the player */ /* 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_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_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); void rc_api_destroy_login_response(rc_api_login_response_t* response);
/* --- Start Session --- */ /* --- 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_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_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); void rc_api_destroy_start_session_response(rc_api_start_session_response_t* response);
/* --- Fetch User Unlocks --- */ /* --- 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_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_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); void rc_api_destroy_fetch_user_unlocks_response(rc_api_fetch_user_unlocks_response_t* response);
#ifdef __cplusplus #ifdef __cplusplus

View File

@ -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 */

View File

@ -10,6 +10,7 @@ extern "C" {
\*****************************************************************************/ \*****************************************************************************/
enum { enum {
RC_CONSOLE_UNKNOWN = 0,
RC_CONSOLE_MEGA_DRIVE = 1, RC_CONSOLE_MEGA_DRIVE = 1,
RC_CONSOLE_NINTENDO_64 = 2, RC_CONSOLE_NINTENDO_64 = 2,
RC_CONSOLE_SUPER_NINTENDO = 3, RC_CONSOLE_SUPER_NINTENDO = 3,
@ -85,6 +86,11 @@ enum {
RC_CONSOLE_ARCADIA_2001 = 73, RC_CONSOLE_ARCADIA_2001 = 73,
RC_CONSOLE_INTERTON_VC_4000 = 74, RC_CONSOLE_INTERTON_VC_4000 = 74,
RC_CONSOLE_ELEKTOR_TV_GAMES_COMPUTER = 75, 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_HUBS = 100,
RC_CONSOLE_EVENTS = 101 RC_CONSOLE_EVENTS = 101

View File

@ -36,7 +36,13 @@ enum {
RC_INVALID_MEASURED_TARGET = -23, RC_INVALID_MEASURED_TARGET = -23,
RC_INVALID_COMPARISON = -24, RC_INVALID_COMPARISON = -24,
RC_INVALID_STATE = -25, 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); const char* rc_error_str(int ret);

View File

@ -27,20 +27,20 @@ extern "C" {
/* data for rc_hash_iterate /* 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; size_t buffer_size;
uint8_t consoles[12]; uint8_t consoles[12];
int index; int index;
const char* path; const char* path;
}; } rc_hash_iterator_t;
/* initializes a rc_hash_iterator /* initializes a rc_hash_iterator
* - path must be provided * - 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) * - 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 /* 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_FIRST_DATA ((uint32_t)-1) /* the first data track (skip audio tracks) */
#define RC_HASH_CDTRACK_LAST ((uint32_t)-2) #define RC_HASH_CDTRACK_LAST ((uint32_t)-2) /* the last data/audio track */
#define RC_HASH_CDTRACK_LARGEST ((uint32_t)-3) #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. * 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); typedef void* (*rc_hash_cdreader_open_track_handler)(const char* path, uint32_t track);

View File

@ -88,9 +88,12 @@ typedef struct rc_runtime_t {
rc_value_t* variables; rc_value_t* variables;
rc_value_t** next_variable; rc_value_t** next_variable;
char owns_self;
} }
rc_runtime_t; rc_runtime_t;
rc_runtime_t* rc_runtime_alloc(void);
void rc_runtime_init(rc_runtime_t* runtime); void rc_runtime_init(rc_runtime_t* runtime);
void rc_runtime_destroy(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_LBOARD_TRIGGERED,
RC_RUNTIME_EVENT_ACHIEVEMENT_DISABLED, RC_RUNTIME_EVENT_ACHIEVEMENT_DISABLED,
RC_RUNTIME_EVENT_LBOARD_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 { typedef struct rc_runtime_event_t {

View File

@ -56,6 +56,7 @@ enum {
RC_MEMSIZE_32_BITS_BE, RC_MEMSIZE_32_BITS_BE,
RC_MEMSIZE_FLOAT, RC_MEMSIZE_FLOAT,
RC_MEMSIZE_MBF32, RC_MEMSIZE_MBF32,
RC_MEMSIZE_MBF32_LE,
RC_MEMSIZE_VARIABLE RC_MEMSIZE_VARIABLE
}; };
@ -169,7 +170,8 @@ enum {
RC_OPERATOR_NONE, RC_OPERATOR_NONE,
RC_OPERATOR_MULT, RC_OPERATOR_MULT,
RC_OPERATOR_DIV, RC_OPERATOR_DIV,
RC_OPERATOR_AND RC_OPERATOR_AND,
RC_OPERATOR_XOR
}; };
typedef struct rc_condition_t rc_condition_t; 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 */ /* Whether or not the condition evaluated true on the last check */
char is_true; char is_true;
/* Unique identifier of optimized comparator to use */
char optimized_comparator;
}; };
/*****************************************************************************\ /*****************************************************************************\

View File

@ -10,7 +10,9 @@
#include <string.h> #include <string.h>
#define RETROACHIEVEMENTS_HOST "https://retroachievements.org" #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_host = NULL;
static char* g_imagehost = NULL; static char* g_imagehost = NULL;
@ -18,157 +20,174 @@ static char* g_imagehost = NULL;
/* --- rc_json --- */ /* --- 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_object(rc_json_iterator_t* iterator, 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_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; 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 */ case '"': /* quoted string */
++(*json_ptr); ++iterator->json;
while (**json_ptr != '"') { if (!rc_json_find_closing_quote(iterator))
if (**json_ptr == '\\') return RC_INVALID_JSON;
++(*json_ptr); ++iterator->json;
if (**json_ptr == '\0')
return RC_INVALID_JSON;
++(*json_ptr);
}
++(*json_ptr);
break; break;
case '-': case '-':
case '+': /* signed number */ case '+': /* signed number */
++(*json_ptr); ++iterator->json;
/* fallthrough to number */ /* fallthrough to number */
case '0': case '1': case '2': case '3': case '4': case '0': case '1': case '2': case '3': case '4':
case '5': case '6': case '7': case '8': case '9': /* number */ case '5': case '6': case '7': case '8': case '9': /* number */
do { while (iterator->json < iterator->end && *iterator->json >= '0' && *iterator->json <= '9')
++(*json_ptr); ++iterator->json;
} while (**json_ptr >= '0' && **json_ptr <= '9');
if (**json_ptr == '.') { if (rc_json_match_char(iterator, '.')) {
do { while (iterator->json < iterator->end && *iterator->json >= '0' && *iterator->json <= '9')
++(*json_ptr); ++iterator->json;
} while (**json_ptr >= '0' && **json_ptr <= '9');
} }
break; break;
case '[': /* array */ case '[': /* array */
result = rc_json_parse_array(json_ptr, field); result = rc_json_parse_array(iterator, field);
if (result != RC_OK) if (result != RC_OK)
return result; return result;
break; break;
case '{': /* object */ 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) if (result != RC_OK)
return result; return result;
break; break;
default: /* non-quoted text [true,false,null] */ default: /* non-quoted text [true,false,null] */
if (!isalpha((unsigned char)**json_ptr)) if (!isalpha((unsigned char)*iterator->json))
return RC_INVALID_JSON; return RC_INVALID_JSON;
do { while (iterator->json < iterator->end && isalnum((unsigned char)*iterator->json))
++(*json_ptr); ++iterator->json;
} while (isalnum((unsigned char)**json_ptr));
break; break;
} }
field->value_end = *json_ptr; field->value_end = iterator->json;
return RC_OK; 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; rc_json_field_t unused_field;
const char* json = *json_ptr;
int result; int result;
if (*json != '[') if (!rc_json_match_char(iterator, '['))
return RC_INVALID_JSON; return RC_INVALID_JSON;
++json;
field->array_size = 0; field->array_size = 0;
if (*json != ']') {
do
{
while (isspace((unsigned char)*json))
++json;
result = rc_json_parse_field(&json, &unused_field); if (rc_json_match_char(iterator, ']')) /* empty array */
if (result != RC_OK) return RC_OK;
return result;
++field->array_size; do
{
rc_json_skip_whitespace(iterator);
while (isspace((unsigned char)*json)) result = rc_json_parse_field(iterator, &unused_field);
++json; if (result != RC_OK)
return result;
if (*json != ',') ++field->array_size;
break;
++json; rc_json_skip_whitespace(iterator);
} while (1); } while (rc_json_match_char(iterator, ','));
if (*json != ']') if (!rc_json_match_char(iterator, ']'))
return RC_INVALID_JSON; return RC_INVALID_JSON;
}
*json_ptr = ++json;
return RC_OK; return RC_OK;
} }
static int rc_json_get_next_field(rc_json_object_field_iterator_t* iterator) { static int rc_json_get_next_field(rc_json_iterator_t* iterator, rc_json_field_t* field) {
const char* json = iterator->json; rc_json_skip_whitespace(iterator);
while (isspace((unsigned char)*json)) if (!rc_json_match_char(iterator, '"'))
++json;
if (*json != '"')
return RC_INVALID_JSON; return RC_INVALID_JSON;
iterator->field.name = ++json; field->name = iterator->json;
while (*json != '"') { while (iterator->json < iterator->end && *iterator->json != '"') {
if (!*json) if (!*iterator->json)
return RC_INVALID_JSON; return RC_INVALID_JSON;
++json; ++iterator->json;
} }
iterator->name_len = json - iterator->field.name;
++json;
while (isspace((unsigned char)*json)) if (iterator->json == iterator->end)
++json;
if (*json != ':')
return RC_INVALID_JSON; return RC_INVALID_JSON;
++json; field->name_len = iterator->json - field->name;
++iterator->json;
while (isspace((unsigned char)*json)) rc_json_skip_whitespace(iterator);
++json;
if (rc_json_parse_field(&json, &iterator->field) < 0) if (!rc_json_match_char(iterator, ':'))
return RC_INVALID_JSON; return RC_INVALID_JSON;
while (isspace((unsigned char)*json)) rc_json_skip_whitespace(iterator);
++json;
if (rc_json_parse_field(iterator, field) < 0)
return RC_INVALID_JSON;
rc_json_skip_whitespace(iterator);
iterator->json = json;
return RC_OK; 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) { static int rc_json_parse_object(rc_json_iterator_t* iterator, rc_json_field_t* fields, size_t field_count, unsigned* fields_seen) {
rc_json_object_field_iterator_t iterator;
const char* json = *json_ptr;
size_t i; size_t i;
unsigned num_fields = 0; unsigned num_fields = 0;
rc_json_field_t field;
int result; int result;
if (fields_seen) 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) for (i = 0; i < field_count; ++i)
fields[i].value_start = fields[i].value_end = NULL; fields[i].value_start = fields[i].value_end = NULL;
if (*json != '{') if (!rc_json_match_char(iterator, '{'))
return RC_INVALID_JSON; return RC_INVALID_JSON;
++json;
if (*json == '}') { if (rc_json_match_char(iterator, '}')) /* empty object */
*json_ptr = ++json;
return RC_OK; return RC_OK;
}
memset(&iterator, 0, sizeof(iterator));
iterator.json = json;
do do
{ {
result = rc_json_get_next_field(&iterator); result = rc_json_get_next_field(iterator, &field);
if (result != RC_OK) if (result != RC_OK)
return result; return result;
for (i = 0; i < field_count; ++i) { for (i = 0; i < field_count; ++i) {
if (!fields[i].value_start && strncmp(fields[i].name, iterator.field.name, iterator.name_len) == 0 && if (!fields[i].value_start && fields[i].name_len == field.name_len &&
fields[i].name[iterator.name_len] == '\0') { memcmp(fields[i].name, field.name, field.name_len) == 0) {
fields[i].value_start = iterator.field.value_start; fields[i].value_start = field.value_start;
fields[i].value_end = iterator.field.value_end; fields[i].value_end = field.value_end;
fields[i].array_size = iterator.field.array_size; fields[i].array_size = field.array_size;
break; break;
} }
} }
++num_fields; ++num_fields;
if (*iterator.json != ',')
break;
++iterator.json; } while (rc_json_match_char(iterator, ','));
} while (1);
if (*iterator.json != '}') if (!rc_json_match_char(iterator, '}'))
return RC_INVALID_JSON; return RC_INVALID_JSON;
if (fields_seen) if (fields_seen)
*fields_seen = num_fields; *fields_seen = num_fields;
*json_ptr = ++iterator.json;
return RC_OK; return RC_OK;
} }
int rc_json_get_next_object_field(rc_json_object_field_iterator_t* iterator) { int rc_json_get_next_object_field(rc_json_iterator_t* iterator, rc_json_field_t* field) {
if (*iterator->json != ',' && *iterator->json != '{') if (!rc_json_match_char(iterator, ',') && !rc_json_match_char(iterator, '{'))
return 0; return 0;
++iterator->json; return (rc_json_get_next_field(iterator, field) == RC_OK);
return (rc_json_get_next_field(iterator) == 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 #ifndef NDEBUG
if (field_count < 2) if (field_count < 2)
return RC_INVALID_STATE; 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; return RC_INVALID_STATE;
#endif #endif
if (*json == '{') { response->error_message = NULL;
int result = rc_json_parse_object(&json, fields, field_count, 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_string(&response->error_message, response, &fields[1], "Error", NULL);
rc_json_get_optional_bool(&response->succeeded, &fields[0], "Success", 1); rc_json_get_optional_bool(&response->succeeded, &fields[0], "Success", 1);
return result;
} }
response->error_message = NULL; return result;
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;
} }
static int rc_json_missing_field(rc_api_response_t* response, const rc_json_field_t* field) { 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) { 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 #ifndef NDEBUG
if (strcmp(field->name, field_name) != 0) if (strcmp(field->name, field_name) != 0)
return 0; return 0;
#else
(void)field_name;
#endif #endif
if (!json) if (!field->value_start)
return rc_json_missing_field(response, field); 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) { static int rc_json_get_array_entry_value(rc_json_field_t* field, rc_json_iterator_t* iterator) {
if (!iterator->array_size) rc_json_skip_whitespace(iterator);
if (iterator->json >= iterator->end)
return 0; return 0;
while (isspace((unsigned char)*iterator->value_start)) if (rc_json_parse_field(iterator, field) != RC_OK)
++iterator->value_start; return 0;
rc_json_parse_field(&iterator->value_start, field); rc_json_skip_whitespace(iterator);
while (isspace((unsigned char)*iterator->value_start)) if (!rc_json_match_char(iterator, ','))
++iterator->value_start; rc_json_match_char(iterator, ']');
++iterator->value_start; /* skip , or ] */
--iterator->array_size;
return 1; 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) { 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; rc_json_field_t value;
unsigned* entry; 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; return RC_MISSING_VALUE;
if (*num_entries) { if (*num_entries) {
@ -338,6 +399,10 @@ int rc_json_get_required_unum_array(unsigned** entries, unsigned* num_entries, r
value.name = field_name; value.name = field_name;
memset(&iterator, 0, sizeof(iterator));
iterator.json = array.value_start;
iterator.end = array.value_end;
entry = *entries; entry = *entries;
while (rc_json_get_array_entry_value(&value, &iterator)) { while (rc_json_get_array_entry_value(&value, &iterator)) {
if (!rc_json_get_unum(entry, &value, field_name)) 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; 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 #ifndef NDEBUG
if (strcmp(field->name, field_name) != 0) if (strcmp(field->name, field_name) != 0)
return 0; return 0;
#else
(void)field_name;
#endif #endif
if (!field->value_start || *field->value_start != '[') { 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); return rc_json_missing_field(response, field);
} }
memcpy(iterator, field, sizeof(*iterator)); memcpy(array_field, field, sizeof(*array_field));
++iterator->value_start; /* skip [ */ ++array_field->value_start; /* skip [ */
*num_entries = field->array_size; *num_entries = field->array_size;
return 1; return 1;
} }
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_array_entry_object(rc_json_field_t* fields, size_t field_count, rc_json_iterator_t* iterator) {
if (!iterator->array_size) rc_json_skip_whitespace(iterator);
if (iterator->json >= iterator->end)
return 0; return 0;
while (isspace((unsigned char)*iterator->value_start)) if (rc_json_parse_object(iterator, fields, field_count, NULL) != RC_OK)
++iterator->value_start; 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)) if (!rc_json_match_char(iterator, ','))
++iterator->value_start; rc_json_match_char(iterator, ']');
++iterator->value_start; /* skip , or ] */
--iterator->array_size;
return 1; return 1;
} }
@ -451,6 +517,8 @@ int rc_json_get_string(const char** out, rc_api_buffer_t* buffer, const rc_json_
#ifndef NDEBUG #ifndef NDEBUG
if (strcmp(field->name, field_name) != 0) if (strcmp(field->name, field_name) != 0)
return 0; return 0;
#else
(void)field_name;
#endif #endif
if (!src) { 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 #ifndef NDEBUG
if (strcmp(field->name, field_name) != 0) if (strcmp(field->name, field_name) != 0)
return 0; return 0;
#else
(void)field_name;
#endif #endif
if (!src) { if (!src) {
@ -613,6 +683,8 @@ int rc_json_get_unum(unsigned* out, const rc_json_field_t* field, const char* fi
#ifndef NDEBUG #ifndef NDEBUG
if (strcmp(field->name, field_name) != 0) if (strcmp(field->name, field_name) != 0)
return 0; return 0;
#else
(void)field_name;
#endif #endif
if (!src) { if (!src) {
@ -654,11 +726,13 @@ int rc_json_get_datetime(time_t* out, const rc_json_field_t* field, const char*
#ifndef NDEBUG #ifndef NDEBUG
if (strcmp(field->name, field_name) != 0) if (strcmp(field->name, field_name) != 0)
return 0; return 0;
#else
(void)field_name;
#endif #endif
if (*field->value_start == '\"') { if (*field->value_start == '\"') {
memset(&tm, 0, sizeof(tm)); 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_year, &tm.tm_mon, &tm.tm_mday, &tm.tm_hour, &tm.tm_min, &tm.tm_sec) == 6) {
tm.tm_mon--; /* 0-based */ tm.tm_mon--; /* 0-based */
tm.tm_year -= 1900; /* 1900 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 */ * timezone conversion twice and manually removing the difference */
{ {
time_t local_timet = mktime(&tm); time_t local_timet = mktime(&tm);
struct tm* gmt_tm = gmtime(&local_timet); time_t skewed_timet, tz_offset;
time_t skewed_timet = mktime(gmt_tm); /* applies local time adjustment second time */ struct tm gmt_tm;
time_t tz_offset = skewed_timet - local_timet; 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; *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 #ifndef NDEBUG
if (strcmp(field->name, field_name) != 0) if (strcmp(field->name, field_name) != 0)
return 0; return 0;
#else
(void)field_name;
#endif #endif
if (src) { if (src) {
@ -731,31 +809,32 @@ int rc_json_get_required_bool(int* out, rc_api_response_t* response, const rc_js
/* --- rc_buf --- */ /* --- rc_buf --- */
void rc_buf_init(rc_api_buffer_t* buffer) { void rc_buf_init(rc_api_buffer_t* buffer) {
buffer->write = &buffer->data[0]; buffer->chunk.write = buffer->chunk.start = &buffer->data[0];
buffer->end = &buffer->data[sizeof(buffer->data)]; buffer->chunk.end = &buffer->data[sizeof(buffer->data)];
buffer->next = NULL; buffer->chunk.next = NULL;
} }
void rc_buf_destroy(rc_api_buffer_t* buffer) { void rc_buf_destroy(rc_api_buffer_t* buffer) {
rc_api_buffer_chunk_t *chunk;
#ifdef DEBUG_BUFFERS #ifdef DEBUG_BUFFERS
int count = 0; int count = 0;
int wasted = 0; int wasted = 0;
int total = 0; int total = 0;
#endif #endif
/* first buffer is not allocated */ /* first chunk is not allocated. skip it. */
buffer = buffer->next; chunk = buffer->chunk.next;
/* deallocate any additional buffers */ /* deallocate any additional buffers */
while (buffer) { while (chunk) {
rc_api_buffer_t* next = buffer->next; rc_api_buffer_chunk_t* next = chunk->next;
#ifdef DEBUG_BUFFERS #ifdef DEBUG_BUFFERS
total += (int)(buffer->end - buffer->data); total += (int)(chunk->end - chunk->data);
wasted += (int)(buffer->end - buffer->write); wasted += (int)(chunk->end - chunk->write);
++count; ++count;
#endif #endif
free(buffer); free(chunk);
buffer = next; chunk = next;
} }
#ifdef DEBUG_BUFFERS #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) { char* rc_buf_reserve(rc_api_buffer_t* buffer, size_t amount) {
rc_api_buffer_chunk_t* chunk = &buffer->chunk;
size_t remaining; size_t remaining;
while (buffer) { while (chunk) {
remaining = buffer->end - buffer->write; remaining = chunk->end - chunk->write;
if (remaining >= amount) if (remaining >= amount)
return buffer->write; return chunk->write;
if (!buffer->next) { if (!chunk->next) {
/* allocate a chunk of memory that is a multiple of 256-bytes. casting it to an rc_api_buffer_t will /* allocate a chunk of memory that is a multiple of 256-bytes. the first 32 bytes will be associated
* effectively unbound the data field, so use write and end pointers to track how data is being used. * 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 chunk_header_size = sizeof(rc_api_buffer_chunk_t);
const size_t alloc_size = (amount + buffer_prefix_size + 0xFF) & ~0xFF; const size_t alloc_size = (chunk_header_size + amount + 0xFF) & ~0xFF;
buffer->next = (rc_api_buffer_t*)malloc(alloc_size); chunk->next = (rc_api_buffer_chunk_t*)malloc(alloc_size);
if (!buffer->next) if (!chunk->next)
break; break;
buffer->next->write = buffer->next->data; chunk->next->start = (char*)chunk->next + chunk_header_size;
buffer->next->end = buffer->next->write + (alloc_size - buffer_prefix_size); chunk->next->write = chunk->next->start;
buffer->next->next = NULL; chunk->next->end = (char*)chunk->next + alloc_size;
chunk->next->next = NULL;
} }
buffer = buffer->next; chunk = chunk->next;
} }
return NULL; return NULL;
} }
void rc_buf_consume(rc_api_buffer_t* buffer, const char* start, char* end) { void rc_buf_consume(rc_api_buffer_t* buffer, const char* start, char* end) {
rc_api_buffer_chunk_t* chunk = &buffer->chunk;
do { do {
if (buffer->write == start) { if (chunk->write == start) {
size_t offset = (end - buffer->data); size_t offset = (end - chunk->start);
offset = (offset + 7) & ~7; offset = (offset + 7) & ~7;
buffer->write = &buffer->data[offset]; chunk->write = &chunk->start[offset];
if (buffer->write > buffer->end) if (chunk->write > chunk->end)
buffer->write = buffer->end; chunk->write = chunk->end;
break; break;
} }
buffer = buffer->next; chunk = chunk->next;
} while (buffer); } while (chunk);
} }
void* rc_buf_alloc(rc_api_buffer_t* buffer, size_t amount) { 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; 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) { void rc_api_destroy_request(rc_api_request_t* request) {
rc_buf_destroy(&request->buffer); 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 --- */ /* --- rc_url_builder --- */
void rc_url_builder_init(rc_api_url_builder_t* builder, rc_api_buffer_t* buffer, size_t estimated_size) { 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)); memset(builder, 0, sizeof(*builder));
builder->buffer = buffer; builder->buffer = buffer;
builder->write = builder->start = rc_buf_reserve(buffer, estimated_size); 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) while (used_buffer && used_buffer->write != builder->write)
used_buffer = used_buffer->next; 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) { if (remaining < amount) {
const size_t used = builder->write - builder->start; const size_t used = builder->write - builder->start;
const size_t current_size = builder->end - 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; char* new_start;
size_t new_size = (current_size < 256) ? 256 : current_size * 2; size_t new_size = (current_size < 256) ? 256 : current_size * 2;
do { do {
@ -1054,6 +1148,16 @@ static void rc_api_update_host(char** host, const char* hostname) {
void rc_api_set_host(const char* hostname) { void rc_api_set_host(const char* hostname) {
rc_api_update_host(&g_host, 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) { void rc_api_set_image_host(const char* hostname) {

View File

@ -10,10 +10,13 @@
extern "C" { extern "C" {
#endif #endif
#define RC_CONTENT_TYPE_URLENCODED "application/x-www-form-urlencoded"
typedef struct rc_api_url_builder_t { typedef struct rc_api_url_builder_t {
char* write; char* write;
char* start; char* start;
char* end; char* end;
/* pointer to a preallocated rc_api_buffer_t */
rc_api_buffer_t* buffer; rc_api_buffer_t* buffer;
int result; 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); 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); 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 { typedef struct rc_json_field_t {
const char* name;
const char* value_start; const char* value_start;
const char* value_end; const char* value_end;
const char* name;
size_t name_len;
unsigned array_size; unsigned array_size;
} }
rc_json_field_t; rc_json_field_t;
typedef struct rc_json_object_field_iterator_t { typedef struct rc_json_iterator_t {
rc_json_field_t field;
const char* json; 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_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_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); 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_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_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_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_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_field_t* iterator); 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_object_field_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_init(rc_api_buffer_t* buffer);
void rc_buf_destroy(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); 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_consume(rc_api_buffer_t* buffer, const char* start, char* end);
void* rc_buf_alloc(rc_api_buffer_t* buffer, size_t amount); 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_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); void rc_url_builder_append_num_param(rc_api_url_builder_t* builder, const char* param, int value);

View File

@ -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); rc_url_builder_append_unum_param(&builder, "g", api_params->game_id);
request->post_data = rc_url_builder_finalize(&builder); request->post_data = rc_url_builder_finalize(&builder);
request->content_type = RC_CONTENT_TYPE_URLENCODED;
return builder.result; return builder.result;
} }
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_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; rc_api_code_note_t* note;
const char* address_str; const char* address_str;
const char* last_author = ""; 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; int result;
rc_json_field_t fields[] = { rc_json_field_t fields[] = {
{"Success"}, RC_JSON_NEW_FIELD("Success"),
{"Error"}, RC_JSON_NEW_FIELD("Error"),
{"CodeNotes"} RC_JSON_NEW_FIELD("CodeNotes")
}; };
rc_json_field_t note_fields[] = { rc_json_field_t note_fields[] = {
{"Address"}, RC_JSON_NEW_FIELD("Address"),
{"User"}, RC_JSON_NEW_FIELD("User"),
{"Note"} RC_JSON_NEW_FIELD("Note")
}; };
memset(response, 0, sizeof(*response)); memset(response, 0, sizeof(*response));
rc_buf_init(&response->response.buffer); 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) if (result != RC_OK || !response->response.succeeded)
return result; 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; return RC_MISSING_VALUE;
if (response->num_notes) { 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) if (!response->notes)
return RC_OUT_OF_MEMORY; 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; note = response->notes;
while (rc_json_get_array_entry_object(note_fields, sizeof(note_fields) / sizeof(note_fields[0]), &iterator)) { 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 */ /* 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) */ /* a note set to '' also represents a deleted note (remnant of a bug) */
/* NOTE: len will include the quotes */ /* NOTE: len will include the quotes */
len = note_fields[2].value_end - note_fields[2].value_start; if (note_fields[2].value_start) {
if (len == 2 || (len == 4 && note_fields[2].value_start[1] == '\'' && note_fields[2].value_start[2] == '\'')) { len = note_fields[2].value_end - note_fields[2].value_start;
--response->num_notes; if (len == 2 || (len == 4 && note_fields[2].value_start[1] == '\'' && note_fields[2].value_start[2] == '\'')) {
continue; --response->num_notes;
continue;
}
} }
if (!rc_json_get_required_string(&address_str, &response->response, &note_fields[0], "Address")) if (!rc_json_get_required_string(&address_str, &response->response, &note_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); rc_url_builder_append_str_param(&builder, "n", api_params->note);
request->post_data = rc_url_builder_finalize(&builder); request->post_data = rc_url_builder_finalize(&builder);
request->content_type = RC_CONTENT_TYPE_URLENCODED;
return builder.result; return builder.result;
} }
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_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; int result;
rc_json_field_t fields[] = { rc_json_field_t fields[] = {
{"Success"}, RC_JSON_NEW_FIELD("Success"),
{"Error"} RC_JSON_NEW_FIELD("Error")
/* unused fields /* unused fields
{"GameID"}, RC_JSON_NEW_FIELD("GameID"),
{"Address"}, RC_JSON_NEW_FIELD("Address"),
{"Note"} RC_JSON_NEW_FIELD("Note")
*/ */
}; };
memset(response, 0, sizeof(*response)); memset(response, 0, sizeof(*response));
rc_buf_init(&response->response.buffer); 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) if (result != RC_OK || !response->response.succeeded)
return result; 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); rc_url_builder_append_str_param(&builder, "h", buffer);
request->post_data = rc_url_builder_finalize(&builder); request->post_data = rc_url_builder_finalize(&builder);
request->content_type = RC_CONTENT_TYPE_URLENCODED;
return builder.result; return builder.result;
} }
int rc_api_process_update_achievement_response(rc_api_update_achievement_response_t* response, const char* server_response) { 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; int result;
rc_json_field_t fields[] = { rc_json_field_t fields[] = {
{"Success"}, RC_JSON_NEW_FIELD("Success"),
{"Error"}, RC_JSON_NEW_FIELD("Error"),
{"AchievementID"} RC_JSON_NEW_FIELD("AchievementID")
}; };
memset(response, 0, sizeof(*response)); memset(response, 0, sizeof(*response));
rc_buf_init(&response->response.buffer); 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) if (result != RC_OK || !response->response.succeeded)
return result; 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); rc_url_builder_append_str_param(&builder, "h", buffer);
request->post_data = rc_url_builder_finalize(&builder); request->post_data = rc_url_builder_finalize(&builder);
request->content_type = RC_CONTENT_TYPE_URLENCODED;
return builder.result; return builder.result;
} }
int rc_api_process_update_leaderboard_response(rc_api_update_leaderboard_response_t* response, const char* server_response) { 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; int result;
rc_json_field_t fields[] = { rc_json_field_t fields[] = {
{"Success"}, RC_JSON_NEW_FIELD("Success"),
{"Error"}, RC_JSON_NEW_FIELD("Error"),
{"LeaderboardID"} RC_JSON_NEW_FIELD("LeaderboardID")
}; };
memset(response, 0, sizeof(*response)); memset(response, 0, sizeof(*response));
rc_buf_init(&response->response.buffer); 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) if (result != RC_OK || !response->response.succeeded)
return result; 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"); rc_url_builder_append_str_param(&builder, "r", "badgeiter");
request->post_data = rc_url_builder_finalize(&builder); request->post_data = rc_url_builder_finalize(&builder);
request->content_type = RC_CONTENT_TYPE_URLENCODED;
(void)api_params;
return builder.result; return builder.result;
} }
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_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; int result;
rc_json_field_t fields[] = { rc_json_field_t fields[] = {
{"Success"}, RC_JSON_NEW_FIELD("Success"),
{"Error"}, RC_JSON_NEW_FIELD("Error"),
{"FirstBadge"}, RC_JSON_NEW_FIELD("FirstBadge"),
{"NextBadge"} RC_JSON_NEW_FIELD("NextBadge")
}; };
memset(response, 0, sizeof(*response)); memset(response, 0, sizeof(*response));
rc_buf_init(&response->response.buffer); 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) if (result != RC_OK || !response->response.succeeded)
return result; 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); rc_url_builder_append_str_param(&builder, "d", api_params->hash_description);
request->post_data = rc_url_builder_finalize(&builder); request->post_data = rc_url_builder_finalize(&builder);
request->content_type = RC_CONTENT_TYPE_URLENCODED;
return builder.result; return builder.result;
} }
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_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; int result;
rc_json_field_t fields[] = { rc_json_field_t fields[] = {
{"Success"}, RC_JSON_NEW_FIELD("Success"),
{"Error"}, RC_JSON_NEW_FIELD("Error"),
{"Response"} RC_JSON_NEW_FIELD("Response")
}; };
rc_json_field_t response_fields[] = { rc_json_field_t response_fields[] = {
{"GameID"} RC_JSON_NEW_FIELD("GameID")
/* unused fields /* unused fields
{"MD5"}, RC_JSON_NEW_FIELD("MD5"),
{"ConsoleID"}, RC_JSON_NEW_FIELD("ConsoleID"),
{"GameTitle"}, RC_JSON_NEW_FIELD("GameTitle"),
{"Success"} RC_JSON_NEW_FIELD("Success")
*/ */
}; };
memset(response, 0, sizeof(*response)); memset(response, 0, sizeof(*response));
rc_buf_init(&response->response.buffer); 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) if (result != RC_OK || !response->response.succeeded)
return result; return result;

View File

@ -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); rc_url_builder_append_unum_param(&builder, "c", api_params->count);
request->post_data = rc_url_builder_finalize(&builder); request->post_data = rc_url_builder_finalize(&builder);
request->content_type = RC_CONTENT_TYPE_URLENCODED;
} }
return builder.result; return builder.result;
} }
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_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_api_achievement_awarded_entry_t* entry;
rc_json_field_t iterator; rc_json_field_t array_field;
rc_json_iterator_t iterator;
unsigned timet; unsigned timet;
int result; int result;
rc_json_field_t fields[] = { rc_json_field_t fields[] = {
{"Success"}, RC_JSON_NEW_FIELD("Success"),
{"Error"}, RC_JSON_NEW_FIELD("Error"),
{"AchievementID"}, RC_JSON_NEW_FIELD("AchievementID"),
{"Response"} RC_JSON_NEW_FIELD("Response")
/* unused fields /* unused fields
{"Offset"}, RC_JSON_NEW_FIELD("Offset"),
{"Count"}, RC_JSON_NEW_FIELD("Count"),
{"FriendsOnly"}, RC_JSON_NEW_FIELD("FriendsOnly")
* unused fields */ * unused fields */
}; };
rc_json_field_t response_fields[] = { rc_json_field_t response_fields[] = {
{"NumEarned"}, RC_JSON_NEW_FIELD("NumEarned"),
{"TotalPlayers"}, RC_JSON_NEW_FIELD("TotalPlayers"),
{"GameID"}, RC_JSON_NEW_FIELD("GameID"),
{"RecentWinner"} /* array */ RC_JSON_NEW_FIELD("RecentWinner") /* array */
}; };
rc_json_field_t entry_fields[] = { rc_json_field_t entry_fields[] = {
{"User"}, RC_JSON_NEW_FIELD("User"),
{"DateAwarded"} RC_JSON_NEW_FIELD("DateAwarded")
}; };
memset(response, 0, sizeof(*response)); memset(response, 0, sizeof(*response));
rc_buf_init(&response->response.buffer); 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) if (result != RC_OK)
return result; 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")) if (!rc_json_get_required_unum(&response->game_id, &response->response, &response_fields[2], "GameID"))
return RC_MISSING_VALUE; 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; return RC_MISSING_VALUE;
if (response->num_recently_awarded) { 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) if (!response->recently_awarded)
return RC_OUT_OF_MEMORY; 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; entry = response->recently_awarded;
while (rc_json_get_array_entry_object(entry_fields, sizeof(entry_fields) / sizeof(entry_fields[0]), &iterator)) { 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")) 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); rc_url_builder_append_unum_param(&builder, "c", api_params->count);
request->post_data = rc_url_builder_finalize(&builder); request->post_data = rc_url_builder_finalize(&builder);
request->content_type = RC_CONTENT_TYPE_URLENCODED;
return builder.result; return builder.result;
} }
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_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_api_lboard_info_entry_t* entry;
rc_json_field_t iterator; rc_json_field_t array_field;
rc_json_iterator_t iterator;
unsigned timet; unsigned timet;
int result; int result;
size_t len; size_t len;
char format[16]; char format[16];
rc_json_field_t fields[] = { rc_json_field_t fields[] = {
{"Success"}, RC_JSON_NEW_FIELD("Success"),
{"Error"}, RC_JSON_NEW_FIELD("Error"),
{"LeaderboardData"} RC_JSON_NEW_FIELD("LeaderboardData")
}; };
rc_json_field_t leaderboarddata_fields[] = { rc_json_field_t leaderboarddata_fields[] = {
{"LBID"}, RC_JSON_NEW_FIELD("LBID"),
{"LBFormat"}, RC_JSON_NEW_FIELD("LBFormat"),
{"LowerIsBetter"}, RC_JSON_NEW_FIELD("LowerIsBetter"),
{"LBTitle"}, RC_JSON_NEW_FIELD("LBTitle"),
{"LBDesc"}, RC_JSON_NEW_FIELD("LBDesc"),
{"LBMem"}, RC_JSON_NEW_FIELD("LBMem"),
{"GameID"}, RC_JSON_NEW_FIELD("GameID"),
{"LBAuthor"}, RC_JSON_NEW_FIELD("LBAuthor"),
{"LBCreated"}, RC_JSON_NEW_FIELD("LBCreated"),
{"LBUpdated"}, RC_JSON_NEW_FIELD("LBUpdated"),
{"Entries"} /* array */ RC_JSON_NEW_FIELD("Entries") /* array */
/* unused fields /* unused fields
{"GameTitle"}, RC_JSON_NEW_FIELD("GameTitle"),
{"ConsoleID"}, RC_JSON_NEW_FIELD("ConsoleID"),
{"ConsoleName"}, RC_JSON_NEW_FIELD("ConsoleName"),
{"ForumTopicID"}, RC_JSON_NEW_FIELD("ForumTopicID"),
{"GameIcon"}, RC_JSON_NEW_FIELD("GameIcon")
* unused fields */ * unused fields */
}; };
rc_json_field_t entry_fields[] = { rc_json_field_t entry_fields[] = {
{"User"}, RC_JSON_NEW_FIELD("User"),
{"Rank"}, RC_JSON_NEW_FIELD("Rank"),
{"Index"}, RC_JSON_NEW_FIELD("Index"),
{"Score"}, RC_JSON_NEW_FIELD("Score"),
{"DateSubmitted"} RC_JSON_NEW_FIELD("DateSubmitted")
}; };
memset(response, 0, sizeof(*response)); memset(response, 0, sizeof(*response));
rc_buf_init(&response->response.buffer); 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) if (result != RC_OK)
return result; return result;
@ -218,7 +246,7 @@ int rc_api_process_fetch_leaderboard_info_response(rc_api_fetch_leaderboard_info
response->format = RC_FORMAT_VALUE; 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; return RC_MISSING_VALUE;
if (response->num_entries) { if (response->num_entries) {
@ -226,6 +254,10 @@ int rc_api_process_fetch_leaderboard_info_response(rc_api_fetch_leaderboard_info
if (!response->entries) if (!response->entries)
return RC_OUT_OF_MEMORY; 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; entry = response->entries;
while (rc_json_get_array_entry_object(entry_fields, sizeof(entry_fields) / sizeof(entry_fields[0]), &iterator)) { 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")) 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); rc_url_builder_append_unum_param(&builder, "c", api_params->console_id);
request->post_data = rc_url_builder_finalize(&builder); request->post_data = rc_url_builder_finalize(&builder);
request->content_type = RC_CONTENT_TYPE_URLENCODED;
return builder.result; return builder.result;
} }
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_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_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; int result;
char* end; char* end;
rc_json_field_t fields[] = { rc_json_field_t fields[] = {
{"Success"}, RC_JSON_NEW_FIELD("Success"),
{"Error"}, RC_JSON_NEW_FIELD("Error"),
{"Response"} RC_JSON_NEW_FIELD("Response")
}; };
memset(response, 0, sizeof(*response)); memset(response, 0, sizeof(*response));
rc_buf_init(&response->response.buffer); 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) if (result != RC_OK)
return result; 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)); memset(&iterator, 0, sizeof(iterator));
iterator.json = fields[2].value_start; iterator.json = fields[2].value_start;
iterator.end = fields[2].value_end;
entry = response->entries; entry = response->entries;
while (rc_json_get_next_object_field(&iterator)) { while (rc_json_get_next_object_field(&iterator, &field)) {
entry->id = strtol(iterator.field.name, &end, 10); entry->id = strtol(field.name, &end, 10);
iterator.field.name = ""; field.name = "";
if (!rc_json_get_string(&entry->name, &response->response.buffer, &iterator.field, "")) if (!rc_json_get_string(&entry->name, &response->response.buffer, &field, ""))
return RC_MISSING_VALUE; return RC_MISSING_VALUE;
++entry; ++entry;

View File

@ -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, "r", "gameid");
rc_url_builder_append_str_param(&builder, "m", api_params->game_hash); rc_url_builder_append_str_param(&builder, "m", api_params->game_hash);
request->post_data = rc_url_builder_finalize(&builder); request->post_data = rc_url_builder_finalize(&builder);
request->content_type = RC_CONTENT_TYPE_URLENCODED;
return builder.result; return builder.result;
} }
int rc_api_process_resolve_hash_response(rc_api_resolve_hash_response_t* response, const char* server_response) { 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; int result;
rc_json_field_t fields[] = { rc_json_field_t fields[] = {
{"Success"}, RC_JSON_NEW_FIELD("Success"),
{"Error"}, RC_JSON_NEW_FIELD("Error"),
{"GameID"}, RC_JSON_NEW_FIELD("GameID")
}; };
memset(response, 0, sizeof(*response)); memset(response, 0, sizeof(*response));
rc_buf_init(&response->response.buffer); 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) if (result != RC_OK)
return result; 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)) { 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); rc_url_builder_append_unum_param(&builder, "g", api_params->game_id);
request->post_data = rc_url_builder_finalize(&builder); request->post_data = rc_url_builder_finalize(&builder);
request->content_type = RC_CONTENT_TYPE_URLENCODED;
} }
return builder.result; return builder.result;
} }
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_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_achievement_definition_t* achievement;
rc_api_leaderboard_definition_t* leaderboard; 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* str;
const char* last_author = ""; const char* last_author = "";
const char* last_author_field = "";
size_t last_author_len = 0; size_t last_author_len = 0;
size_t len; size_t len;
unsigned timet; 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]; char format[16];
rc_json_field_t fields[] = { rc_json_field_t fields[] = {
{"Success"}, RC_JSON_NEW_FIELD("Success"),
{"Error"}, RC_JSON_NEW_FIELD("Error"),
{"PatchData"} /* nested object */ RC_JSON_NEW_FIELD("PatchData") /* nested object */
}; };
rc_json_field_t patchdata_fields[] = { rc_json_field_t patchdata_fields[] = {
{"ID"}, RC_JSON_NEW_FIELD("ID"),
{"Title"}, RC_JSON_NEW_FIELD("Title"),
{"ConsoleID"}, RC_JSON_NEW_FIELD("ConsoleID"),
{"ImageIcon"}, RC_JSON_NEW_FIELD("ImageIcon"),
{"RichPresencePatch"}, RC_JSON_NEW_FIELD("RichPresencePatch"),
{"Achievements"}, /* array */ RC_JSON_NEW_FIELD("Achievements"), /* array */
{"Leaderboards"} /* array */ RC_JSON_NEW_FIELD("Leaderboards") /* array */
/* unused fields /* unused fields
{"ForumTopicID"}, RC_JSON_NEW_FIELD("ForumTopicID"),
{"Flags"}, RC_JSON_NEW_FIELD("Flags")
* unused fields */ * unused fields */
}; };
rc_json_field_t achievement_fields[] = { rc_json_field_t achievement_fields[] = {
{"ID"}, RC_JSON_NEW_FIELD("ID"),
{"Title"}, RC_JSON_NEW_FIELD("Title"),
{"Description"}, RC_JSON_NEW_FIELD("Description"),
{"Flags"}, RC_JSON_NEW_FIELD("Flags"),
{"Points"}, RC_JSON_NEW_FIELD("Points"),
{"MemAddr"}, RC_JSON_NEW_FIELD("MemAddr"),
{"Author"}, RC_JSON_NEW_FIELD("Author"),
{"BadgeName"}, RC_JSON_NEW_FIELD("BadgeName"),
{"Created"}, RC_JSON_NEW_FIELD("Created"),
{"Modified"} RC_JSON_NEW_FIELD("Modified")
}; };
rc_json_field_t leaderboard_fields[] = { rc_json_field_t leaderboard_fields[] = {
{"ID"}, RC_JSON_NEW_FIELD("ID"),
{"Title"}, RC_JSON_NEW_FIELD("Title"),
{"Description"}, RC_JSON_NEW_FIELD("Description"),
{"Mem"}, RC_JSON_NEW_FIELD("Mem"),
{"Format"}, RC_JSON_NEW_FIELD("Format"),
{"LowerIsBetter"}, RC_JSON_NEW_FIELD("LowerIsBetter"),
{"Hidden"} RC_JSON_NEW_FIELD("Hidden")
}; };
memset(response, 0, sizeof(*response)); memset(response, 0, sizeof(*response));
rc_buf_init(&response->response.buffer); 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) if (result != RC_OK || !response->response.succeeded)
return result; 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) if (!response->rich_presence_script)
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; return RC_MISSING_VALUE;
if (response->num_achievements) { 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) if (!response->achievements)
return RC_OUT_OF_MEMORY; 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; achievement = response->achievements;
while (rc_json_get_array_entry_object(achievement_fields, sizeof(achievement_fields) / sizeof(achievement_fields[0]), &iterator)) { 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")) 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")) if (!rc_json_get_required_string(&achievement->badge_name, &response->response, &achievement_fields[7], "BadgeName"))
return RC_MISSING_VALUE; return RC_MISSING_VALUE;
len = achievement_fields[7].value_end - achievement_fields[7].value_start; len = achievement_fields[6].value_end - achievement_fields[6].value_start;
if (len == last_author_len && memcmp(achievement_fields[7].value_start, last_author, len) == 0) { if (len == last_author_len && memcmp(achievement_fields[6].value_start, last_author_field, len) == 0) {
achievement->author = last_author; achievement->author = last_author;
} }
else { 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; return RC_MISSING_VALUE;
last_author = achievement->author; last_author = achievement->author;
last_author_field = achievement_fields[6].value_start;
last_author_len = len; 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; return RC_MISSING_VALUE;
if (response->num_leaderboards) { 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) if (!response->leaderboards)
return RC_OUT_OF_MEMORY; 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; leaderboard = response->leaderboards;
while (rc_json_get_array_entry_object(leaderboard_fields, sizeof(leaderboard_fields) / sizeof(leaderboard_fields[0]), &iterator)) { 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")) 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); rc_url_builder_append_str_param(&builder, "m", api_params->rich_presence);
request->post_data = rc_url_builder_finalize(&builder); request->post_data = rc_url_builder_finalize(&builder);
request->content_type = RC_CONTENT_TYPE_URLENCODED;
} }
return builder.result; return builder.result;
} }
int rc_api_process_ping_response(rc_api_ping_response_t* response, const char* server_response) { 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[] = { rc_json_field_t fields[] = {
{"Success"}, RC_JSON_NEW_FIELD("Success"),
{"Error"} RC_JSON_NEW_FIELD("Error")
}; };
memset(response, 0, sizeof(*response)); memset(response, 0, sizeof(*response));
rc_buf_init(&response->response.buffer); 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) { 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); rc_url_builder_append_str_param(&builder, "v", buffer);
request->post_data = rc_url_builder_finalize(&builder); request->post_data = rc_url_builder_finalize(&builder);
request->content_type = RC_CONTENT_TYPE_URLENCODED;
} }
return builder.result; return builder.result;
} }
int rc_api_process_award_achievement_response(rc_api_award_achievement_response_t* response, const char* server_response) { 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; int result;
rc_json_field_t fields[] = { rc_json_field_t fields[] = {
{"Success"}, RC_JSON_NEW_FIELD("Success"),
{"Error"}, RC_JSON_NEW_FIELD("Error"),
{"Score"}, RC_JSON_NEW_FIELD("Score"),
{"AchievementID"}, RC_JSON_NEW_FIELD("SoftcoreScore"),
{"AchievementsRemaining"} RC_JSON_NEW_FIELD("AchievementID"),
RC_JSON_NEW_FIELD("AchievementsRemaining")
}; };
memset(response, 0, sizeof(*response)); memset(response, 0, sizeof(*response));
rc_buf_init(&response->response.buffer); 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) if (result != RC_OK)
return result; 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->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->new_player_score_softcore, &fields[3], "SoftcoreScore", 0);
rc_json_get_optional_unum(&response->achievements_remaining, &fields[4], "AchievementsRemaining", (unsigned)-1); 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; 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); rc_url_builder_append_str_param(&builder, "v", buffer);
request->post_data = rc_url_builder_finalize(&builder); request->post_data = rc_url_builder_finalize(&builder);
request->content_type = RC_CONTENT_TYPE_URLENCODED;
} }
return builder.result; return builder.result;
} }
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_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_api_lboard_entry_t* entry;
rc_json_field_t iterator; rc_json_field_t array_field;
rc_json_iterator_t iterator;
const char* str; const char* str;
int result; int result;
rc_json_field_t fields[] = { rc_json_field_t fields[] = {
{"Success"}, RC_JSON_NEW_FIELD("Success"),
{"Error"}, RC_JSON_NEW_FIELD("Error"),
{"Response"} /* nested object */ RC_JSON_NEW_FIELD("Response") /* nested object */
}; };
rc_json_field_t response_fields[] = { rc_json_field_t response_fields[] = {
{"Score"}, RC_JSON_NEW_FIELD("Score"),
{"BestScore"}, RC_JSON_NEW_FIELD("BestScore"),
{"RankInfo"}, /* nested object */ RC_JSON_NEW_FIELD("RankInfo"), /* nested object */
{"TopEntries"} /* array */ RC_JSON_NEW_FIELD("TopEntries") /* array */
/* unused fields /* unused fields
{"LBData"}, / * array * / RC_JSON_NEW_FIELD("LBData"), / * array * /
{"ScoreFormatted"}, RC_JSON_NEW_FIELD("ScoreFormatted"),
{"TopEntriesFriends"}, / * array * / RC_JSON_NEW_FIELD("TopEntriesFriends") / * array * /
* unused fields */ * unused fields */
}; };
/* unused fields /* unused fields
rc_json_field_t lbdata_fields[] = { rc_json_field_t lbdata_fields[] = {
{"Format"}, RC_JSON_NEW_FIELD("Format"),
{"LeaderboardID"}, RC_JSON_NEW_FIELD("LeaderboardID"),
{"GameID"}, RC_JSON_NEW_FIELD("GameID"),
{"Title"}, RC_JSON_NEW_FIELD("Title"),
{"LowerIsBetter"} RC_JSON_NEW_FIELD("LowerIsBetter")
}; };
* unused fields */ * unused fields */
rc_json_field_t entry_fields[] = { rc_json_field_t entry_fields[] = {
{"User"}, RC_JSON_NEW_FIELD("User"),
{"Rank"}, RC_JSON_NEW_FIELD("Rank"),
{"Score"} RC_JSON_NEW_FIELD("Score")
/* unused fields /* unused fields
{"DateSubmitted"}, RC_JSON_NEW_FIELD("DateSubmitted")
* unused fields */ * unused fields */
}; };
rc_json_field_t rank_info_fields[] = { rc_json_field_t rank_info_fields[] = {
{"Rank"}, RC_JSON_NEW_FIELD("Rank"),
{"NumEntries"} RC_JSON_NEW_FIELD("NumEntries")
/* unused fields /* unused fields
{"LowerIsBetter"}, RC_JSON_NEW_FIELD("LowerIsBetter"),
{"UserRank"}, RC_JSON_NEW_FIELD("UserRank")
* unused fields */ * unused fields */
}; };
memset(response, 0, sizeof(*response)); memset(response, 0, sizeof(*response));
rc_buf_init(&response->response.buffer); 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) if (result != RC_OK || !response->response.succeeded)
return result; return result;
@ -495,7 +564,7 @@ int rc_api_process_submit_lboard_entry_response(rc_api_submit_lboard_entry_respo
return RC_MISSING_VALUE; return RC_MISSING_VALUE;
response->num_entries = (unsigned)atoi(str); 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; return RC_MISSING_VALUE;
if (response->num_top_entries) { 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) if (!response->top_entries)
return RC_OUT_OF_MEMORY; 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; entry = response->top_entries;
while (rc_json_get_array_entry_object(entry_fields, sizeof(entry_fields) / sizeof(entry_fields[0]), &iterator)) { 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")) if (!rc_json_get_required_string(&entry->username, &response->response, &entry_fields[0], "User"))

View File

@ -1,6 +1,8 @@
#include "rc_api_user.h" #include "rc_api_user.h"
#include "rc_api_common.h" #include "rc_api_common.h"
#include "../rcheevos/rc_version.h"
#include <string.h> #include <string.h>
/* --- Login --- */ /* --- 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; return RC_INVALID_STATE;
request->post_data = rc_url_builder_finalize(&builder); request->post_data = rc_url_builder_finalize(&builder);
request->content_type = RC_CONTENT_TYPE_URLENCODED;
return builder.result; return builder.result;
} }
int rc_api_process_login_response(rc_api_login_response_t* response, const char* server_response) { 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; int result;
rc_json_field_t fields[] = { rc_json_field_t fields[] = {
{"Success"}, RC_JSON_NEW_FIELD("Success"),
{"Error"}, RC_JSON_NEW_FIELD("Error"),
{"User"}, RC_JSON_NEW_FIELD("User"),
{"Token"}, RC_JSON_NEW_FIELD("Token"),
{"Score"}, RC_JSON_NEW_FIELD("Score"),
{"Messages"}, RC_JSON_NEW_FIELD("SoftcoreScore"),
{"DisplayName"} RC_JSON_NEW_FIELD("Messages"),
RC_JSON_NEW_FIELD("DisplayName")
}; };
memset(response, 0, sizeof(*response)); memset(response, 0, sizeof(*response));
rc_buf_init(&response->response.buffer); 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) if (result != RC_OK || !response->response.succeeded)
return result; return result;
@ -54,9 +68,10 @@ int rc_api_process_login_response(rc_api_login_response_t* response, const char*
return RC_MISSING_VALUE; return RC_MISSING_VALUE;
rc_json_get_optional_unum(&response->score, &fields[4], "Score", 0); 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; 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, "a", 3);
rc_url_builder_append_unum_param(&builder, "m", api_params->game_id); 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->post_data = rc_url_builder_finalize(&builder);
request->content_type = RC_CONTENT_TYPE_URLENCODED;
} }
return builder.result; return builder.result;
} }
int rc_api_process_start_session_response(rc_api_start_session_response_t* response, const char* server_response) { 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[] = { rc_json_field_t fields[] = {
{"Success"}, RC_JSON_NEW_FIELD("Success"),
{"Error"} RC_JSON_NEW_FIELD("Error")
}; };
memset(response, 0, sizeof(*response)); memset(response, 0, sizeof(*response));
rc_buf_init(&response->response.buffer); 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) { 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, "g", api_params->game_id);
rc_url_builder_append_unum_param(&builder, "h", api_params->hardcore ? 1 : 0); rc_url_builder_append_unum_param(&builder, "h", api_params->hardcore ? 1 : 0);
request->post_data = rc_url_builder_finalize(&builder); request->post_data = rc_url_builder_finalize(&builder);
request->content_type = RC_CONTENT_TYPE_URLENCODED;
} }
return builder.result; return builder.result;
} }
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_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; int result;
rc_json_field_t fields[] = { rc_json_field_t fields[] = {
{"Success"}, RC_JSON_NEW_FIELD("Success"),
{"Error"}, RC_JSON_NEW_FIELD("Error"),
{"UserUnlocks"} RC_JSON_NEW_FIELD("UserUnlocks")
/* unused fields /* unused fields
{ "GameID" }, RC_JSON_NEW_FIELD("GameID"),
{ "HardcoreMode" } RC_JSON_NEW_FIELD("HardcoreMode")
* unused fields */ * unused fields */
}; };
memset(response, 0, sizeof(*response)); memset(response, 0, sizeof(*response));
rc_buf_init(&response->response.buffer); 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) if (result != RC_OK || !response->response.succeeded)
return result; return result;

View File

@ -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) const char* rc_error_str(int ret)
{ {
switch (ret) { switch (ret) {
@ -189,7 +200,12 @@ const char* rc_error_str(int ret)
case RC_INVALID_COMPARISON: return "Invalid comparison"; case RC_INVALID_COMPARISON: return "Invalid comparison";
case RC_INVALID_STATE: return "Invalid state"; case RC_INVALID_STATE: return "Invalid state";
case RC_INVALID_JSON: return "Invalid JSON"; 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"; default: return "Unknown error";
} }
} }

View File

@ -3,6 +3,8 @@
#include <ctype.h> #include <ctype.h>
#include <stdarg.h> #include <stdarg.h>
#ifdef RC_C89_HELPERS
int rc_strncasecmp(const char* left, const char* right, size_t length) int rc_strncasecmp(const char* left, const char* right, size_t length)
{ {
while (length) while (length)
@ -44,20 +46,94 @@ char* rc_strdup(const char* str)
{ {
const size_t length = strlen(str); const size_t length = strlen(str);
char* buffer = (char*)malloc(length + 1); char* buffer = (char*)malloc(length + 1);
memcpy(buffer, str, length + 1); if (buffer)
memcpy(buffer, str, length + 1);
return buffer; return buffer;
} }
int rc_snprintf(char* buffer, size_t size, const char* format, ...) int rc_snprintf(char* buffer, size_t size, const char* format, ...)
{ {
int result; int result;
va_list args; va_list args;
va_start(args, format); va_start(args, format);
/* assume buffer is large enough and ignore size */
(void)size;
result = vsprintf(buffer, format, args);
va_end(args);
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 */

View File

@ -1,6 +1,91 @@
#include "rc_internal.h" #include "rc_internal.h"
#include <stdlib.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) { static int rc_parse_operator(const char** memaddr) {
const char* oper = *memaddr; const char* oper = *memaddr;
@ -50,6 +135,10 @@ static int rc_parse_operator(const char** memaddr) {
++(*memaddr); ++(*memaddr);
return RC_OPERATOR_AND; return RC_OPERATOR_AND;
case '^':
++(*memaddr);
return RC_OPERATOR_XOR;
case '\0':/* end of string */ case '\0':/* end of string */
case '_': /* next condition */ case '_': /* next condition */
case 'S': /* next condset */ 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->current_hits = 0;
self->is_true = 0; self->is_true = 0;
self->pause = 0; self->pause = 0;
self->optimized_comparator = RC_PROCESSING_COMPARE_DEFAULT;
if (*aux != 0 && aux[1] == ':') { if (*aux != 0 && aux[1] == ':') {
switch (*aux) { 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_MULT:
case RC_OPERATOR_DIV: case RC_OPERATOR_DIV:
case RC_OPERATOR_AND: case RC_OPERATOR_AND:
case RC_OPERATOR_XOR:
/* modifying operators are only valid on modifying statements */ /* modifying operators are only valid on modifying statements */
if (can_modify) if (can_modify)
break; break;
@ -209,6 +300,9 @@ rc_condition_t* rc_parse_condition(const char** memaddr, rc_parse_state_t* parse
self->required_hits = 0; self->required_hits = 0;
} }
if (parse->buffer != 0)
self->optimized_comparator = rc_condition_determine_comparator(self);
*memaddr = aux; *memaddr = aux;
return self; 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) { int rc_test_condition(rc_condition_t* self, rc_eval_state_t* eval_state) {
rc_typed_value_t value1, value2; 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); 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); 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); rc_typed_value_convert(&amount, RC_VALUE_TYPE_UNSIGNED);
value->value.u32 &= amount.value.u32; value->value.u32 &= amount.value.u32;
break; 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;
} }
} }

View File

@ -84,6 +84,7 @@ rc_condset_t* rc_parse_condset(const char** memaddr, rc_parse_state_t* parse, in
switch ((*next)->oper) switch ((*next)->oper)
{ {
case RC_OPERATOR_AND: case RC_OPERATOR_AND:
case RC_OPERATOR_XOR:
case RC_OPERATOR_DIV: case RC_OPERATOR_DIV:
case RC_OPERATOR_MULT: case RC_OPERATOR_MULT:
case RC_OPERATOR_NONE: 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: case RC_CONDITION_SUB_SOURCE:
rc_evaluate_condition_value(&value, condition, eval_state); rc_evaluate_condition_value(&value, condition, eval_state);
rc_typed_value_convert(&value, RC_VALUE_TYPE_SIGNED); rc_typed_value_negate(&value);
value.value.i32 = -value.value.i32;
rc_typed_value_add(&eval_state->add_value, &value); rc_typed_value_add(&eval_state->add_value, &value);
eval_state->add_address = 0; eval_state->add_address = 0;
continue; continue;

View File

@ -39,6 +39,9 @@ const char* rc_console_name(int console_id)
case RC_CONSOLE_ATARI_JAGUAR: case RC_CONSOLE_ATARI_JAGUAR:
return "Atari Jaguar"; return "Atari Jaguar";
case RC_CONSOLE_ATARI_JAGUAR_CD:
return "Atari Jaguar CD";
case RC_CONSOLE_ATARI_LYNX: case RC_CONSOLE_ATARI_LYNX:
return "Atari Lynx"; return "Atari Lynx";
@ -132,6 +135,9 @@ const char* rc_console_name(int console_id)
case RC_CONSOLE_NINTENDO_DS: case RC_CONSOLE_NINTENDO_DS:
return "Nintendo DS"; return "Nintendo DS";
case RC_CONSOLE_NINTENDO_DSI:
return "Nintendo DSi";
case RC_CONSOLE_NINTENDO_3DS: case RC_CONSOLE_NINTENDO_3DS:
return "Nintendo 3DS"; return "Nintendo 3DS";
@ -156,6 +162,9 @@ const char* rc_console_name(int console_id)
case RC_CONSOLE_PC_ENGINE: case RC_CONSOLE_PC_ENGINE:
return "PC Engine"; return "PC Engine";
case RC_CONSOLE_PC_ENGINE_CD:
return "PC Engine CD";
case RC_CONSOLE_PLAYSTATION: case RC_CONSOLE_PLAYSTATION:
return "PlayStation"; return "PlayStation";
@ -198,9 +207,15 @@ const char* rc_console_name(int console_id)
case RC_CONSOLE_THOMSONTO8: case RC_CONSOLE_THOMSONTO8:
return "Thomson TO8"; return "Thomson TO8";
case RC_CONSOLE_TI83:
return "TI-83";
case RC_CONSOLE_TIC80: case RC_CONSOLE_TIC80:
return "TIC-80"; return "TIC-80";
case RC_CONSOLE_UZEBOX:
return "Uzebox";
case RC_CONSOLE_VECTREX: case RC_CONSOLE_VECTREX:
return "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 }; 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 ===== */ /* ===== Game Gear ===== */
/* http://www.smspower.org/Development/MemoryMap */ /* http://www.smspower.org/Development/MemoryMap */
static const rc_memory_region_t _rc_memory_regions_game_gear[] = { 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 }; 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 ===== */ /* ===== MSX ===== */
/* https://www.msx.org/wiki/The_Memory */ /* https://www.msx.org/wiki/The_Memory */
/* MSX only has 64KB of addressable RAM, of which 32KB is reserved for the system/BIOS. /* 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 }; 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 ===== */ /* ===== Nintendo Entertainment System ===== */
/* https://wiki.nesdev.com/w/index.php/CPU_memory_map */ /* https://wiki.nesdev.com/w/index.php/CPU_memory_map */
static const rc_memory_region_t _rc_memory_regions_nes[] = { 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 }; 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 ===== */ /* ===== Oric ===== */
static const rc_memory_region_t _rc_memory_regions_oric[] = { static const rc_memory_region_t _rc_memory_regions_oric[] = {
/* actual size depends on machine type - up to 64KB */ /* 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 */ /* http://www.archaicpixels.com/Memory_Map */
static const rc_memory_region_t _rc_memory_regions_pc_engine[] = { static const rc_memory_region_t _rc_memory_regions_pc_engine[] = {
{ 0x000000U, 0x001FFFU, 0x1F0000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" }, { 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" }, { 0x002000U, 0x011FFFU, 0x100000U, RC_MEMORY_TYPE_SYSTEM_RAM, "CD RAM" },
{ 0x012000U, 0x041FFFU, 0x0D0000U, RC_MEMORY_TYPE_SYSTEM_RAM, "Super System Card 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" } { 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 ===== */ /* ===== PC-FX ===== */
/* http://daifukkat.su/pcfx/data/memmap.html */ /* 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 }; 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 ===== */ /* ===== TIC-80 ===== */
/* https://github.com/nesbox/TIC-80/wiki/RAM */ /* https://github.com/nesbox/TIC-80/wiki/RAM */
static const rc_memory_region_t _rc_memory_regions_tic80[] = { 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 }; 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 ===== */ /* ===== Vectrex ===== */
/* https://roadsidethoughts.com/vectrex/vectrex-memory-map.htm */ /* https://roadsidethoughts.com/vectrex/vectrex-memory-map.htm */
static const rc_memory_region_t _rc_memory_regions_vectrex[] = { 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 }; 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 ===== */ /* ===== WonderSwan ===== */
/* http://daifukkat.su/docs/wsman/#ovr_memmap */ /* http://daifukkat.su/docs/wsman/#ovr_memmap */
static const rc_memory_region_t _rc_memory_regions_wonderswan[] = { 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; return &rc_memory_regions_atari7800;
case RC_CONSOLE_ATARI_JAGUAR: case RC_CONSOLE_ATARI_JAGUAR:
case RC_CONSOLE_ATARI_JAGUAR_CD:
return &rc_memory_regions_atari_jaguar; return &rc_memory_regions_atari_jaguar;
case RC_CONSOLE_ATARI_LYNX: 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: case RC_CONSOLE_GAMEBOY_ADVANCE:
return &rc_memory_regions_gameboy_advance; return &rc_memory_regions_gameboy_advance;
case RC_CONSOLE_GAMECUBE:
return &rc_memory_regions_gamecube;
case RC_CONSOLE_GAME_GEAR: case RC_CONSOLE_GAME_GEAR:
return &rc_memory_regions_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; return &rc_memory_regions_master_system;
case RC_CONSOLE_MEGA_DRIVE: 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; return &rc_memory_regions_megadrive;
case RC_CONSOLE_SEGA_32X:
return &rc_memory_regions_megadrive_32x;
case RC_CONSOLE_MSX: case RC_CONSOLE_MSX:
return &rc_memory_regions_msx; return &rc_memory_regions_msx;
case RC_CONSOLE_NEOGEO_POCKET: case RC_CONSOLE_NEOGEO_POCKET:
return &rc_memory_regions_neo_geo_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: case RC_CONSOLE_NINTENDO:
return &rc_memory_regions_nes; 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: case RC_CONSOLE_NINTENDO_DS:
return &rc_memory_regions_nintendo_ds; return &rc_memory_regions_nintendo_ds;
case RC_CONSOLE_NINTENDO_DSI:
return &rc_memory_regions_nintendo_dsi;
case RC_CONSOLE_ORIC: case RC_CONSOLE_ORIC:
return &rc_memory_regions_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: case RC_CONSOLE_PC_ENGINE:
return &rc_memory_regions_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: case RC_CONSOLE_PCFX:
return &rc_memory_regions_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: case RC_CONSOLE_THOMSONTO8:
return &rc_memory_regions_thomson_to8; return &rc_memory_regions_thomson_to8;
case RC_CONSOLE_TI83:
return &rc_memory_regions_ti83;
case RC_CONSOLE_TIC80: case RC_CONSOLE_TIC80:
return &rc_memory_regions_tic80; return &rc_memory_regions_tic80;
case RC_CONSOLE_UZEBOX:
return &rc_memory_regions_uzebox;
case RC_CONSOLE_VECTREX: case RC_CONSOLE_VECTREX:
return &rc_memory_regions_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: case RC_CONSOLE_WASM4:
return &rc_memory_regions_wasm4; return &rc_memory_regions_wasm4;
case RC_CONSOLE_WII:
return &rc_memory_regions_wii;
case RC_CONSOLE_WONDERSWAN: case RC_CONSOLE_WONDERSWAN:
return &rc_memory_regions_wonderswan; return &rc_memory_regions_wonderswan;

View File

@ -95,6 +95,7 @@ int rc_parse_memref(const char** memaddr, char* size, unsigned* address) {
switch (*aux++) { switch (*aux++) {
case 'f': case 'F': *size = RC_MEMSIZE_FLOAT; break; case 'f': case 'F': *size = RC_MEMSIZE_FLOAT; break;
case 'm': case 'M': *size = RC_MEMSIZE_MBF32; break; case 'm': case 'M': *size = RC_MEMSIZE_MBF32; break;
case 'l': case 'L': *size = RC_MEMSIZE_MBF32_LE; break;
default: default:
return RC_INVALID_FP_OPERAND; 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; 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 }; 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) { 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); rc_transform_memref_mbf32(value);
break; break;
case RC_MEMSIZE_MBF32_LE:
rc_transform_memref_mbf32_le(value);
break;
default: default:
break; break;
} }
@ -319,6 +339,7 @@ static const unsigned rc_memref_masks[] = {
0xffffffff, /* RC_MEMSIZE_32_BITS_BE */ 0xffffffff, /* RC_MEMSIZE_32_BITS_BE */
0xffffffff, /* RC_MEMSIZE_FLOAT */ 0xffffffff, /* RC_MEMSIZE_FLOAT */
0xffffffff, /* RC_MEMSIZE_MBF32 */ 0xffffffff, /* RC_MEMSIZE_MBF32 */
0xffffffff, /* RC_MEMSIZE_MBF32_LE */
0xffffffff /* RC_MEMSIZE_VARIABLE */ 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_32_BITS_BE */
RC_MEMSIZE_32_BITS, /* RC_MEMSIZE_FLOAT */ RC_MEMSIZE_32_BITS, /* RC_MEMSIZE_FLOAT */
RC_MEMSIZE_32_BITS, /* RC_MEMSIZE_MBF32 */ RC_MEMSIZE_32_BITS, /* RC_MEMSIZE_MBF32 */
RC_MEMSIZE_32_BITS, /* RC_MEMSIZE_MBF32_LE */
RC_MEMSIZE_32_BITS /* RC_MEMSIZE_VARIABLE */ RC_MEMSIZE_32_BITS /* RC_MEMSIZE_VARIABLE */
}; };
@ -365,7 +387,7 @@ char rc_memref_shared_size(char size) {
return rc_memref_shared_sizes[index]; 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) if (!peek)
return 0; return 0;
@ -422,7 +444,7 @@ void rc_init_parse_state_memrefs(rc_parse_state_t* parse, rc_memref_t** memrefs)
*memrefs = 0; *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) switch (operand_type)
{ {
/* most common case explicitly first, even though it could be handled by default case. /* most common case explicitly first, even though it could be handled by default case.

View File

@ -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); self->value.luafunc = luaL_ref(parse->L, LUA_REGISTRYINDEX);
} }
#else
(void)parse;
#endif /* RC_DISABLE_LUA */ #endif /* RC_DISABLE_LUA */
self->type = RC_OPERAND_LUA; self->type = RC_OPERAND_LUA;
@ -300,6 +302,7 @@ int rc_operand_is_float_memref(const rc_operand_t* self) {
switch (self->size) { switch (self->size) {
case RC_MEMSIZE_FLOAT: case RC_MEMSIZE_FLOAT:
case RC_MEMSIZE_MBF32: case RC_MEMSIZE_MBF32:
case RC_MEMSIZE_MBF32_LE:
return 1; return 1;
default: 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) { unsigned rc_transform_operand_value(unsigned value, const rc_operand_t* self) {
switch (self->type) switch (self->type)
{ {

File diff suppressed because it is too large Load Diff

View File

@ -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 */

View File

@ -13,6 +13,8 @@ extern "C" {
/* MinGW redefinitions */ /* MinGW redefinitions */
#define RC_NO_VARIADIC_MACROS 1
#elif defined(_MSC_VER) #elif defined(_MSC_VER)
/* Visual Studio redefinitions */ /* Visual Studio redefinitions */
@ -30,6 +32,9 @@ extern "C" {
#elif __STDC_VERSION__ < 199901L #elif __STDC_VERSION__ < 199901L
/* C89 redefinitions */ /* C89 redefinitions */
#define RC_C89_HELPERS 1
#define RC_NO_VARIADIC_MACROS 1
#ifndef snprintf #ifndef snprintf
extern int rc_snprintf(char* buffer, size_t size, const char* format, ...); extern int rc_snprintf(char* buffer, size_t size, const char* format, ...);
@ -53,6 +58,40 @@ extern "C" {
#endif /* __STDC_VERSION__ < 199901L */ #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 #ifdef __cplusplus
} }
#endif #endif

View File

@ -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(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))) #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 { typedef struct rc_scratch_buffer {
struct rc_scratch_buffer* next; struct rc_scratch_buffer* next;
int offset; int offset;
@ -83,6 +86,8 @@ typedef struct {
} }
rc_typed_value_t; rc_typed_value_t;
#define RC_MEASURED_UNKNOWN 0xFFFFFFFF
typedef struct { typedef struct {
rc_typed_value_t add_value;/* AddSource/SubSource */ rc_typed_value_t add_value;/* AddSource/SubSource */
int add_hits; /* AddHits */ 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); 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); 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); 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); 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); 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); char rc_memref_shared_size(char size);
unsigned rc_memref_mask(char size); unsigned rc_memref_mask(char size);
void rc_transform_memref_value(rc_typed_value_t* value, 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); void rc_parse_trigger_internal(rc_trigger_t* self, const char** memaddr, rc_parse_state_t* parse);
int rc_trigger_state_active(int state); 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); int rc_test_condset(rc_condset_t* self, rc_eval_state_t* eval_state);
void rc_reset_condset(rc_condset_t* self); 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); 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); 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); 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); 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); 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_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); 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); 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); 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); 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); 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_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_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_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); 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); void rc_typed_value_from_memref_value(rc_typed_value_t* value, const rc_memref_value_t* memref);

View File

@ -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 &regions->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, &regions->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;
}

View File

@ -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 */

View File

@ -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);
}

View File

@ -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 */

View File

@ -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 */

View File

@ -280,7 +280,6 @@ static void rc_rebalance_richpresence_lookup(rc_richpresence_lookup_item_t** roo
{ {
rc_richpresence_lookup_item_t** items; rc_richpresence_lookup_item_t** items;
rc_scratch_buffer_t* buffer; rc_scratch_buffer_t* buffer;
const int alignment = sizeof(rc_richpresence_lookup_item_t*);
int index; int index;
int size; 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*); size = count * sizeof(rc_richpresence_lookup_item_t*);
buffer = &parse->scratch.buffer; buffer = &parse->scratch.buffer;
do { 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; const int remaining = sizeof(buffer->buffer) - aligned_offset;
if (remaining >= size) { if (remaining >= size) {
@ -534,10 +533,11 @@ void rc_parse_richpresence_internal(rc_richpresence_t* self, const char* script,
display = nextline; display = nextline;
display_line = parse->lines_read; display_line = parse->lines_read;
/* scan as long as we find conditional lines or full line comments */
do { do {
line = nextline; line = nextline;
nextline = rc_parse_line(line, &endline, parse); nextline = rc_parse_line(line, &endline, parse);
} while (*line == '?'); } while (*line == '?' || (line[0] == '/' && line[1] == '/'));
} }
line = nextline; 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); nextline = rc_parse_line(line, &endline, parse);
while (*line == '?') { do {
/* conditional display: ?trigger?string */ if (line[0] == '?') {
ptr = ++line; /* conditional display: ?trigger?string */
while (ptr < endline && *ptr != '?') ptr = ++line;
++ptr; while (ptr < endline && *ptr != '?')
++ptr;
if (ptr < endline) { if (ptr < endline) {
*nextdisplay = rc_parse_richpresence_display_internal(ptr + 1, endline, parse, firstlookup); *nextdisplay = rc_parse_richpresence_display_internal(ptr + 1, endline, parse, firstlookup);
if (parse->offset < 0) if (parse->offset < 0)
return; return;
trigger = &((*nextdisplay)->trigger); trigger = &((*nextdisplay)->trigger);
rc_parse_trigger_internal(trigger, &line, parse); rc_parse_trigger_internal(trigger, &line, parse);
trigger->memrefs = 0; trigger->memrefs = 0;
if (parse->offset < 0) if (parse->offset < 0)
return; return;
if (parse->buffer) if (parse->buffer)
nextdisplay = &((*nextdisplay)->next); nextdisplay = &((*nextdisplay)->next);
}
}
else if (line[0] != '/' || line[1] != '/') {
break;
} }
line = nextline; line = nextline;
nextline = rc_parse_line(line, &endline, parse); nextline = rc_parse_line(line, &endline, parse);
} } while (1);
/* non-conditional display: string */ /* non-conditional display: string */
*nextdisplay = rc_parse_richpresence_display_internal(line, endline, parse, firstlookup); *nextdisplay = rc_parse_richpresence_display_internal(line, endline, parse, firstlookup);
if (*nextdisplay) { if (*nextdisplay) {
hasdisplay = 1; hasdisplay = 1;
nextdisplay = &((*nextdisplay)->next); nextdisplay = &((*nextdisplay)->next);
}
/* restore the parser state */ /* restore the parser state */
parse->lines_read = lines_read; 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 */ /* finalize */

View File

@ -9,6 +9,17 @@
#define RC_RICHPRESENCE_DISPLAY_BUFFER_SIZE 256 #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) { void rc_runtime_init(rc_runtime_t* self) {
memset(self, 0, sizeof(rc_runtime_t)); memset(self, 0, sizeof(rc_runtime_t));
self->next_memref = &self->memrefs; self->next_memref = &self->memrefs;
@ -48,9 +59,13 @@ void rc_runtime_destroy(rc_runtime_t* self) {
self->next_memref = 0; self->next_memref = 0;
self->memrefs = 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_state_t state;
md5_init(&state); md5_init(&state);
md5_append(&state, (unsigned char*)memaddr, (int)strlen(memaddr)); 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)) { 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; *measured_target = trigger->measured_target;
} }
else { 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" */ /* 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) if (value > trigger->measured_target)
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) { for (i = self->trigger_count - 1; i >= 0; --i) {
rc_trigger_t* trigger = self->triggers[i].trigger; rc_trigger_t* trigger = self->triggers[i].trigger;
int old_state, new_state; int old_state, new_state;
unsigned old_measured_value;
if (!trigger) if (!trigger)
continue; continue;
@ -552,13 +568,13 @@ void rc_runtime_do_frame(rc_runtime_t* self, rc_runtime_event_handler_t event_ha
continue; continue;
} }
old_measured_value = trigger->measured_value;
old_state = trigger->state; old_state = trigger->state;
new_state = rc_evaluate_trigger(trigger, peek, ud, L); 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 */ * 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.type = RC_RUNTIME_EVENT_ACHIEVEMENT_RESET;
runtime_event.id = self->triggers[i].id; runtime_event.id = self->triggers[i].id;
event_handler(&runtime_event); 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; 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 the state hasn't changed, there won't be any events raised */
if (new_state == old_state) if (new_state == old_state)
continue; continue;
@ -685,13 +727,8 @@ void rc_runtime_reset(rc_runtime_t* self) {
rc_reset_lboard(self->lboards[i].lboard); rc_reset_lboard(self->lboards[i].lboard);
} }
if (self->richpresence && self->richpresence->richpresence) { if (self->richpresence && self->richpresence->richpresence)
rc_richpresence_display_t* display = self->richpresence->richpresence->first_display; rc_reset_richpresence(self->richpresence->richpresence);
while (display != 0) {
rc_reset_trigger(&display->trigger);
display = display->next;
}
}
for (variable = self->variables; variable; variable = variable->next) for (variable = self->variables; variable; variable = variable->next)
rc_reset_value(variable); rc_reset_value(variable);
@ -712,7 +749,7 @@ static int rc_condset_contains_memref(const rc_condset_t* condset, const rc_memr
return 0; 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; rc_condset_t* condset;
if (!value) if (!value)
return 0; return 0;
@ -725,7 +762,7 @@ static int rc_value_contains_memref(const rc_value_t* value, const rc_memref_t*
return 0; 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; rc_condset_t* condset;
if (!trigger) if (!trigger)
return 0; return 0;

View File

@ -17,7 +17,7 @@
#define RC_RUNTIME_CHUNK_DONE 0x454E4F44 /* DONE */ #define RC_RUNTIME_CHUNK_DONE 0x454E4F44 /* DONE */
typedef struct rc_runtime_progress_t { typedef struct rc_runtime_progress_t {
rc_runtime_t* runtime; const rc_runtime_t* runtime;
int offset; int offset;
unsigned char* buffer; unsigned char* buffer;
@ -81,17 +81,6 @@ static int rc_runtime_progress_match_md5(rc_runtime_progress_t* progress, unsign
return result; 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) static void rc_runtime_progress_start_chunk(rc_runtime_progress_t* progress, unsigned chunk_id)
{ {
rc_runtime_progress_write_uint(progress, 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)); memset(progress, 0, sizeof(rc_runtime_progress_t));
progress->runtime = runtime; 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) 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_uint(progress, djb2);
rc_runtime_progress_write_variable(progress, variable); 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; count = 0;
for (variable = progress->runtime->variables; variable; variable = variable->next) { for (variable = progress->runtime->variables; variable; variable = variable->next) {
pending_variables[count].variable = variable; pending_variables[count].variable = variable;
pending_variables[count].djb2 = rc_runtime_progress_djb2(variable->name); pending_variables[count].djb2 = rc_djb2(variable->name);
++count; ++count;
} }
@ -751,7 +740,7 @@ int rc_runtime_progress_size(const rc_runtime_t* runtime, lua_State* L)
rc_runtime_progress_t progress; rc_runtime_progress_t progress;
int result; 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); result = rc_runtime_progress_serialize_internal(&progress);
if (result != RC_OK) 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_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; progress.buffer = (unsigned char*)buffer;
return rc_runtime_progress_serialize_internal(&progress); 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 seen_rich_presence = 0;
int result = RC_OK; int result = RC_OK;
if (!serialized) {
rc_runtime_reset(runtime);
return RC_INVALID_STATE;
}
rc_runtime_progress_init(&progress, runtime, L); rc_runtime_progress_init(&progress, runtime, L);
progress.buffer = (unsigned char*)serialized; progress.buffer = (unsigned char*)serialized;

View File

@ -42,8 +42,8 @@ void rc_parse_trigger_internal(rc_trigger_t* self, const char** memaddr, rc_pars
*next = 0; *next = 0;
*memaddr = aux; *memaddr = aux;
self->measured_value = 0;
self->measured_target = parse->measured_target; 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->measured_as_percent = parse->measured_as_percent;
self->state = RC_TRIGGER_STATE_WAITING; self->state = RC_TRIGGER_STATE_WAITING;
self->has_hits = 0; 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; 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 any ResetIf condition was true, reset the hit counts */
if (eval_state.was_reset) { if (eval_state.was_reset) {
/* if the measured value came from a hit count, reset it. do this before calling /* 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; is_primed = 0;
} }
else if (ret) { 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 */ /* trigger was triggered */
self->state = RC_TRIGGER_STATE_TRIGGERED; self->state = RC_TRIGGER_STATE_TRIGGERED;
return 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); rc_reset_trigger_hitcounts(self);
self->state = RC_TRIGGER_STATE_WAITING; self->state = RC_TRIGGER_STATE_WAITING;
self->measured_value = 0;
if (self->measured_target)
self->measured_value = RC_MEASURED_UNKNOWN;
self->has_hits = 0; self->has_hits = 0;
} }

View File

@ -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_MULT:
case RC_OPERATOR_DIV: case RC_OPERATOR_DIV:
case RC_OPERATOR_AND: case RC_OPERATOR_AND:
case RC_OPERATOR_XOR:
case RC_OPERATOR_NONE: case RC_OPERATOR_NONE:
break; break;
@ -284,6 +285,20 @@ void rc_reset_value(rc_value_t* self) {
self->value.changed = 0; 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) { void rc_init_parse_state_variables(rc_parse_state_t* parse, rc_value_t** variables) {
parse->variables = variables; parse->variables = variables;
*variables = 0; *variables = 0;
@ -427,6 +442,26 @@ static rc_typed_value_t* rc_typed_value_convert_into(rc_typed_value_t* dest, con
return dest; 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) { void rc_typed_value_add(rc_typed_value_t* value, const rc_typed_value_t* amount) {
rc_typed_value_t converted; rc_typed_value_t converted;

View File

@ -22,6 +22,7 @@ struct cdrom_t
void* file_handle; /* the file handle for reading the track data */ 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_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 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 */ 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_first_sector; /* the first absolute sector associated to the track (includes pregap) */
int track_pregap_sectors; /* the number of pregap sectors */ 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_size = 0;
cdrom->sector_header_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); 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)) 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; return NULL;
cdrom = (struct cdrom_t*)calloc(1, sizeof(*cdrom)); cdrom = (struct cdrom_t*)calloc(1, sizeof(*cdrom));
if (!cdrom)
return NULL;
cdrom->file_handle = file_handle; cdrom->file_handle = file_handle;
#ifndef NDEBUG #ifndef NDEBUG
cdrom->track_id = track; 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_size = 2352;
cdrom->sector_header_size = 0; 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* bin_filename = NULL;
char *ptr, *ptr2, *end; char *ptr, *ptr2, *end;
int done = 0; int done = 0;
int session = 1;
size_t num_read = 0; size_t num_read = 0;
struct cdrom_t* cdrom = NULL; struct cdrom_t* cdrom = NULL;
@ -339,7 +345,7 @@ static void* cdreader_open_cue_track(const char* path, uint32_t track)
++ptr; ++ptr;
/* convert mm:ss:ff to sector count */ /* 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; sector_offset = ((m * 60) + s) * 75 + f;
if (current_track.first_sector == -1) if (current_track.first_sector == -1)
@ -389,6 +395,13 @@ static void* cdreader_open_cue_track(const char* path, uint32_t track)
done = 1; done = 1;
break; 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) 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)) if (ptr2 - ptr < (int)sizeof(current_track.filename))
memcpy(current_track.filename, ptr, ptr2 - ptr); 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') while (*ptr && *ptr != '\n')
++ptr; ++ptr;
@ -694,8 +721,8 @@ static void* cdreader_open_gdi_track(const char* path, uint32_t track)
largest_track_size = track_size; largest_track_size = track_size;
largest_track = current_track; largest_track = current_track;
largest_track_lba = lba; largest_track_lba = lba;
strcpy(largest_track_file, file); strcpy_s(largest_track_file, sizeof(largest_track_file), file);
strcpy(largest_track_sector_size, sector_size); 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) if (largest_track != 0 && largest_track != current_track)
{ {
current_track = largest_track; current_track = largest_track;
strcpy(file, largest_track_file); strcpy_s(file, sizeof(file), largest_track_file);
strcpy(sector_size, largest_track_sector_size); strcpy_s(sector_size, sizeof(sector_size), largest_track_sector_size);
lba = largest_track_lba; 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 + sector_start = (int64_t)(sector - cdrom->track_first_sector) * cdrom->sector_size +
cdrom->sector_header_size + cdrom->file_track_offset; 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); 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; total_read += num_read;
if (num_read < 2048) if (num_read < (size_t)cdrom->raw_data_size)
return total_read; return total_read;
buffer_ptr += 2048; buffer_ptr += cdrom->raw_data_size;
sector_start += cdrom->sector_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); 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; 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; struct rc_hash_cdreader cdreader;
rc_hash_get_default_cdreader(&cdreader); rc_hash_get_default_cdreader(&cdreader);

View File

@ -48,7 +48,13 @@ static struct rc_hash_filereader* filereader = NULL;
static void* filereader_open(const char* path) 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"); return fopen(path, "rb");
#endif
} }
static void filereader_seek(void* file_handle, int64_t offset, int origin) 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; uint8_t buffer[2048], *tmp;
int sector; int sector;
unsigned num_sectors = 0;
size_t filename_length; size_t filename_length;
const char* slash; const char* slash;
if (!track_handle) if (!track_handle)
return 0; return 0;
/* we start at the root. don't need to explicitly find it */
if (*path == '\\')
++path;
filename_length = strlen(path); filename_length = strlen(path);
slash = strrchr(path, '\\'); slash = strrchr(path, '\\');
if (slash) if (slash)
@ -247,6 +258,8 @@ static uint32_t rc_cd_find_file_sector(void* track_handle, const char* path, uns
} }
else else
{ {
unsigned logical_block_size;
/* find the cd information */ /* find the cd information */
if (!rc_cd_read_sector(track_handle, rc_cd_first_track_sector(track_handle) + 16, buffer, 256)) if (!rc_cd_read_sector(track_handle, rc_cd_first_track_sector(track_handle) + 16, buffer, 256))
return 0; 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 * https://www.cdroller.com/htm/readdata.html
*/ */
sector = buffer[156 + 2] | (buffer[156 + 3] << 8) | (buffer[156 + 4] << 16); 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 */ /* 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; return 0;
tmp = buffer; tmp = buffer;
while (tmp < buffer + sizeof(buffer)) do
{ {
if (!*tmp) if (tmp >= buffer + sizeof(buffer) || !*tmp)
return 0; {
/* 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" */ /* 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) strncasecmp((const char*)(tmp + 33), path, filename_length) == 0)
{ {
sector = tmp[2] | (tmp[3] << 8) | (tmp[4] << 16); sector = tmp[2] | (tmp[3] << 8) | (tmp[4] << 16);
if (verbose_message_callback) if (verbose_message_callback)
{ {
snprintf((char*)buffer, sizeof(buffer), "Found %s at sector %d", path, sector); char message[128];
verbose_message_callback((const char*)buffer); snprintf(message, sizeof(message), "Found %s at sector %d", path, sector);
verbose_message_callback(message);
} }
if (size) 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 */ /* the first byte of the record is the length of the record */
tmp += *tmp; tmp += *tmp;
} } while (1);
return 0; 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]) static int rc_hash_finalize(md5_state_t* md5, char hash[33])
{ {
md5_byte_t digest[16]; 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); verbose_message_callback(message);
} }
if (size < (unsigned)num_read)
size = (unsigned)num_read;
do do
{ {
md5_append(md5, buffer, (int)num_read); 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); 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) static int rc_hash_lynx(char hash[33], const uint8_t* buffer, size_t buffer_size)
{ {
/* if the file contains a header, ignore it */ /* 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); 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) static int rc_hash_nes(char hash[33], const uint8_t* buffer, size_t buffer_size)
{ {
/* if the file contains a header, ignore it */ /* 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); 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) static int rc_hash_n64(char hash[33], const char* path)
{ {
uint8_t* buffer; 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"); rc_hash_verbose("converting n64 to z64");
is_n64 = 1; is_n64 = 1;
} }
else if (buffer[0] == 0xE8 || buffer[0] == 0x22) /* ndd format (don't byteswap) */
{
}
else else
{ {
free(buffer); 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); rc_file_read(file_handle, buffer, (int)buffer_size);
if (is_v64) if (is_v64)
rc_hash_v64_to_z64(buffer, stop); rc_hash_byteswap16(buffer, stop);
else if (is_n64) else if (is_n64)
rc_hash_n64_to_z64(buffer, stop); rc_hash_byteswap32(buffer, stop);
md5_append(&md5, buffer, (int)buffer_size); md5_append(&md5, buffer, (int)buffer_size);
remaining -= buffer_size; remaining -= buffer_size;
@ -832,9 +1073,9 @@ static int rc_hash_n64(char hash[33], const char* path)
stop = buffer + remaining; stop = buffer + remaining;
if (is_v64) if (is_v64)
rc_hash_v64_to_z64(buffer, stop); rc_hash_byteswap16(buffer, stop);
else if (is_n64) else if (is_n64)
rc_hash_n64_to_z64(buffer, stop); rc_hash_byteswap32(buffer, stop);
md5_append(&md5, buffer, (int)remaining); 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); 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) 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) */ /* 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) if (strncmp(ptr, cdrom_prefix, cdrom_prefix_len) == 0)
ptr += cdrom_prefix_len; ptr += cdrom_prefix_len;
if (*ptr == '\\') while (*ptr == '\\')
++ptr; ++ptr;
start = 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); sector = rc_cd_find_file_sector(track_handle, "PSP_GAME\\PARAM.SFO", &size);
if (!sector) if (!sector)
{
rc_cd_close_track(track_handle);
return rc_hash_error("Not a PSP game disc"); return rc_hash_error("Not a PSP game disc");
}
md5_init(&md5); md5_init(&md5);
if (!rc_hash_cd_file(&md5, track_handle, sector, NULL, size, "PSP_GAME\\PARAM.SFO")) if (!rc_hash_cd_file(&md5, track_handle, sector, NULL, size, "PSP_GAME\\PARAM.SFO"))
{
rc_cd_close_track(track_handle);
return 0; return 0;
}
sector = rc_cd_find_file_sector(track_handle, "PSP_GAME\\SYSDIR\\EBOOT.BIN", &size); sector = rc_cd_find_file_sector(track_handle, "PSP_GAME\\SYSDIR\\EBOOT.BIN", &size);
if (!sector) if (!sector)
{
rc_cd_close_track(track_handle);
return rc_hash_error("Could not find primary executable"); 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")) if (!rc_hash_cd_file(&md5, track_handle, sector, NULL, size, "PSP_GAME\\SYSDIR\\EBOOT.BIN"))
{
rc_cd_close_track(track_handle);
return 0; return 0;
}
rc_cd_close_track(track_handle); rc_cd_close_track(track_handle);
return rc_hash_finalize(&md5, hash); 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) 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)); 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; 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_SEGA_32X:
case RC_CONSOLE_SG1000: case RC_CONSOLE_SG1000:
case RC_CONSOLE_SUPERVISION: case RC_CONSOLE_SUPERVISION:
case RC_CONSOLE_TI83:
case RC_CONSOLE_TIC80: case RC_CONSOLE_TIC80:
case RC_CONSOLE_UZEBOX:
case RC_CONSOLE_VECTREX: case RC_CONSOLE_VECTREX:
case RC_CONSOLE_VIRTUAL_BOY: case RC_CONSOLE_VIRTUAL_BOY:
case RC_CONSOLE_WASM4: 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_64:
case RC_CONSOLE_NINTENDO_DS: case RC_CONSOLE_NINTENDO_DS:
case RC_CONSOLE_NINTENDO_DSI:
return rc_hash_file_from_buffer(hash, console_id, buffer, buffer_size); 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_SEGA_32X:
case RC_CONSOLE_SG1000: case RC_CONSOLE_SG1000:
case RC_CONSOLE_SUPERVISION: case RC_CONSOLE_SUPERVISION:
case RC_CONSOLE_TI83:
case RC_CONSOLE_TIC80: case RC_CONSOLE_TIC80:
case RC_CONSOLE_UZEBOX:
case RC_CONSOLE_VECTREX: case RC_CONSOLE_VECTREX:
case RC_CONSOLE_VIRTUAL_BOY: case RC_CONSOLE_VIRTUAL_BOY:
case RC_CONSOLE_WASM4: 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_7800:
case RC_CONSOLE_ATARI_LYNX: case RC_CONSOLE_ATARI_LYNX:
case RC_CONSOLE_NINTENDO: case RC_CONSOLE_NINTENDO:
case RC_CONSOLE_PC_ENGINE:
case RC_CONSOLE_SUPER_NINTENDO: case RC_CONSOLE_SUPER_NINTENDO:
/* additional logic whole-file hash - buffer then call rc_hash_generate_from_buffer */ /* additional logic whole-file hash - buffer then call rc_hash_generate_from_buffer */
return rc_hash_buffered_file(hash, console_id, path); 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: case RC_CONSOLE_ARCADE:
return rc_hash_arcade(hash, path); 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: case RC_CONSOLE_NINTENDO_64:
return rc_hash_n64(hash, path); return rc_hash_n64(hash, path);
case RC_CONSOLE_NINTENDO_DS: case RC_CONSOLE_NINTENDO_DS:
case RC_CONSOLE_NINTENDO_DSI:
return rc_hash_nintendo_ds(hash, path); 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")) if (rc_path_compare_extension(path, "cue") || rc_path_compare_extension(path, "chd"))
return rc_hash_pce_cd(hash, path); 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: case RC_CONSOLE_PSP:
return rc_hash_psp(hash, path); 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_SEGA_CD:
case RC_CONSOLE_SATURN: case RC_CONSOLE_SATURN:
if (rc_path_compare_extension(path, "m3u")) 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); 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; int need_path = !buffer;
@ -2108,6 +2496,15 @@ void rc_hash_initialize_iterator(struct rc_hash_iterator* iterator, const char*
} }
break; 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': case 'a':
if (rc_path_compare_extension(ext, "a78")) 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[1] = RC_CONSOLE_PLAYSTATION_2;
iterator->consoles[2] = RC_CONSOLE_DREAMCAST; iterator->consoles[2] = RC_CONSOLE_DREAMCAST;
iterator->consoles[3] = RC_CONSOLE_SEGA_CD; /* ASSERT: handles both Sega CD and Saturn */ 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[5] = RC_CONSOLE_3DO;
iterator->consoles[6] = RC_CONSOLE_PCFX; 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; need_path = 1;
} }
else if (rc_path_compare_extension(ext, "chd")) 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[1] = RC_CONSOLE_PLAYSTATION_2;
iterator->consoles[2] = RC_CONSOLE_DREAMCAST; iterator->consoles[2] = RC_CONSOLE_DREAMCAST;
iterator->consoles[3] = RC_CONSOLE_SEGA_CD; /* ASSERT: handles both Sega CD and Saturn */ 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[5] = RC_CONSOLE_3DO;
iterator->consoles[6] = RC_CONSOLE_PCFX; iterator->consoles[6] = RC_CONSOLE_PCFX;
need_path = 1; 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")) 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")) 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")) else if (rc_path_compare_extension(ext, "fd"))
{ {
iterator->consoles[0] = RC_CONSOLE_THOMSONTO8; /* disk */ iterator->consoles[0] = RC_CONSOLE_THOMSONTO8; /* disk */
} }
break; 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")) 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") || else if (rc_path_compare_extension(ext, "n64") ||
rc_path_compare_extension(ext, "ndd")) rc_path_compare_extension(ext, "ndd"))
@ -2412,6 +2811,13 @@ void rc_hash_initialize_iterator(struct rc_hash_iterator* iterator, const char*
} }
break; break;
case 'u':
if (rc_path_compare_extension(ext, "uze"))
{
iterator->consoles[0] = RC_CONSOLE_UZEBOX;
}
break;
case 'v': case 'v':
if (rc_path_compare_extension(ext, "vb")) if (rc_path_compare_extension(ext, "vb"))
{ {

View File

@ -52,6 +52,7 @@
*/ */
#include "md5.h" #include "md5.h"
#include <stddef.h>
#include <string.h> #include <string.h>
#undef BYTE_ORDER /* 1 = big-endian, -1 = little-endian, 0 = unknown */ #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 * On little-endian machines, we can process properly aligned
* data without copying it. * data without copying it.
*/ */
if (!((data - (const md5_byte_t *)0) & 3)) { if (!((ptrdiff_t)data & 3)) {
/* data are properly aligned */ /* data are properly aligned */
X = (const md5_word_t *)data; X = (const md5_word_t *)data;
} else { } else {

View File

@ -675,7 +675,7 @@ bool HostDisplay::WriteScreenshotToFile(std::string filename, bool internal_reso
u32 pixels_stride; u32 pixels_stride;
GPUTexture::Format pixels_format; GPUTexture::Format pixels_format;
if (!RenderScreenshot(width, height, 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)) &pixels_stride, &pixels_format))
{ {
Log_ErrorPrintf("Failed to render %ux%u screenshot", width, height); 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

View File

@ -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, static void UnlockAchievementCallback(s32 status_code, std::string content_type,
Common::HTTPDownloader::Request::Data data); Common::HTTPDownloader::Request::Data data);
static void SubmitLeaderboardCallback(s32 status_code, std::string content_type, 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_active = false;
static bool s_logged_in = 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 Common::Timer s_last_ping_time;
static u32 s_last_queried_lboard = 0; static u32 s_last_queried_lboard = 0;
static u32 s_submitting_lboard_id = 0;
static std::optional<std::vector<Achievements::LeaderboardEntry>> s_lboard_entries; static std::optional<std::vector<Achievements::LeaderboardEntry>> s_lboard_entries;
template<typename T> template<typename T>
@ -356,7 +355,6 @@ void Achievements::ClearGameInfo(bool clear_achievements, bool clear_leaderboard
} }
s_last_queried_lboard = 0; s_last_queried_lboard = 0;
s_submitting_lboard_id = 0;
s_lboard_entries.reset(); s_lboard_entries.reset();
} }
@ -682,7 +680,8 @@ void Achievements::FrameUpdate()
#ifdef WITH_RAINTEGRATION #ifdef WITH_RAINTEGRATION
if (IsUsingRAIntegration()) if (IsUsingRAIntegration())
{ {
RA_DoAchievementsFrame(); if (!System::IsPaused())
RA_DoAchievementsFrame();
return; return;
} }
#endif #endif
@ -692,7 +691,8 @@ void Achievements::FrameUpdate()
if (HasActiveGame()) if (HasActiveGame())
{ {
std::unique_lock lock(s_achievements_mutex); 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(); UpdateRichPresence();
if (!IsTestModeActive()) 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, 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()) if (!System::IsValid())
return; 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 // Force the next leaderboard query to repopulate everything, just in case the user wants to see their new score
s_last_queried_lboard = 0; s_last_queried_lboard = 0;
// RA API doesn't send us the leaderboard ID back.. hopefully we don't submit two at once :/ const Leaderboard* lb = GetLeaderboardByID(lboard_id);
if (s_submitting_lboard_id == 0)
return;
const Leaderboard* lb = GetLeaderboardByID(std::exchange(s_submitting_lboard_id, 0u));
if (!lb || !FullscreenUI::IsInitialized() || !g_settings.achievements_notifications) if (!lb || !FullscreenUI::IsInitialized() || !g_settings.achievements_notifications)
return; return;
@ -1870,17 +1866,16 @@ void Achievements::SubmitLeaderboard(u32 leaderboard_id, int value)
return; 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; RAPIRequest<rc_api_submit_lboard_entry_request_t, rc_api_init_submit_lboard_entry_request> request;
request.username = s_username.c_str(); request.username = s_username.c_str();
request.api_token = s_api_token.c_str(); request.api_token = s_api_token.c_str();
request.game_hash = s_game_hash.c_str(); request.game_hash = s_game_hash.c_str();
request.leaderboard_id = leaderboard_id; request.leaderboard_id = leaderboard_id;
request.score = value; 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) void Achievements::AchievementPrimed(u32 achievement_id)

View File

@ -678,11 +678,16 @@ DEFINE_HOTKEY("OpenPauseMenu", TRANSLATABLE("Hotkeys", "General"), TRANSLATABLE(
if (!pressed) if (!pressed)
FullscreenUI::OpenPauseMenu(); 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) { [](s32 pressed) {
if (!pressed) if (!pressed)
ImGuiManager::OpenNetplayChat(); ImGuiManager::OpenNetplayChat();
}) })
DEFINE_HOTKEY("ToggleDesyncNotifications", TRANSLATABLE("Hotkeys", "Netplay"), TRANSLATABLE("Hotkeys", "Toggle Desync Notifications"),
[](s32 pressed) {
if (!pressed)
Netplay::ToggleDesyncNotifications();
})
#endif #endif
DEFINE_HOTKEY("FastForward", TRANSLATABLE("Hotkeys", "General"), TRANSLATABLE("Hotkeys", "Fast Forward"), DEFINE_HOTKEY("FastForward", TRANSLATABLE("Hotkeys", "General"), TRANSLATABLE("Hotkeys", "Fast Forward"),

View File

@ -2,6 +2,7 @@
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0) // SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
#include "common/log.h" #include "common/log.h"
#include "common/scoped_guard.h"
#include "common/string.h" #include "common/string.h"
#include "input_manager.h" #include "input_manager.h"
#include "platform_misc.h" #include "platform_misc.h"
@ -41,68 +42,77 @@ static bool SetScreensaverInhibitX11(bool inhibit, const WindowInfo& wi)
#elif defined(USE_DBUS) #elif defined(USE_DBUS)
#include <dbus/dbus.h> #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; 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 const char* bus_method = (inhibit_requested) ? "Inhibit" : "UnInhibit";
// no error set, since calling "dbus_error_free" reinitializes it like "dbus_error_init" after freeing. DBusError error;
DBusError error_dbus;
dbus_error_init(&error_dbus);
DBusConnection* connection = nullptr; DBusConnection* connection = nullptr;
static DBusConnection* s_comparison_connection;
DBusMessage* message = nullptr; DBusMessage* message = nullptr;
DBusMessage* response = 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; 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); dbus_message_iter_init_append(message, &message_itr);
if (inhibit_requested) 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. // Append process/window name.
if (!dbus_message_iter_append_basic(&message_itr, DBUS_TYPE_STRING, &program_name)) if (!dbus_message_iter_append_basic(&message_itr, DBUS_TYPE_STRING, &program_name))
goto cleanup; return false;
// Append reason for inhibiting the screensaver. // Append reason for inhibiting the screensaver.
if (!dbus_message_iter_append_basic(&message_itr, DBUS_TYPE_STRING, &reason)) if (!dbus_message_iter_append_basic(&message_itr, DBUS_TYPE_STRING, &reason))
goto cleanup; return false;
} }
else else
{ {
// Only Append the cookie. // Only Append the cookie.
if (!dbus_message_iter_append_basic(&message_itr, DBUS_TYPE_UINT32, &s_cookie)) if (!dbus_message_iter_append_basic(&message_itr, DBUS_TYPE_UINT32, &s_cookie))
goto cleanup; return false;
} }
// Send message and get response. // Send message and get response.
if (!(response = response = dbus_connection_send_with_reply_and_block(connection, message, DBUS_TIMEOUT_USE_DEFAULT, &error);
dbus_connection_send_with_reply_and_block(connection, message, DBUS_TIMEOUT_USE_DEFAULT, &error_dbus)) || if (!response || dbus_error_is_set(&error))
dbus_error_is_set(&error_dbus)) return false;
goto cleanup; s_cookie = 0;
if (inhibit_requested) if (inhibit_requested)
{ {
// Get the cookie from the response message. // Get the cookie from the response message.
if (!dbus_message_get_args(response, &error_dbus, DBUS_TYPE_UINT32, &s_cookie, DBUS_TYPE_INVALID)) if (!dbus_message_get_args(response, &error, DBUS_TYPE_UINT32, &s_cookie, DBUS_TYPE_INVALID) ||
goto cleanup; dbus_error_is_set(&error))
return false;
} }
dbus_message_unref(message);
dbus_message_unref(response);
return true; 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 #endif
@ -110,7 +120,7 @@ cleanup:
static bool SetScreensaverInhibit(bool inhibit) static bool SetScreensaverInhibit(bool inhibit)
{ {
#ifdef USE_DBUS #ifdef USE_DBUS
return ChangeScreenSaverStateDBus(inhibit, "DuckStation", "DuckStation VM is running."); return SetScreensaverInhibitDBus(inhibit, "DuckStation", "DuckStation VM is running.");
#else #else
std::optional<WindowInfo> wi(Host::GetTopLevelWindowInfo()); std::optional<WindowInfo> wi(Host::GetTopLevelWindowInfo());

View File

@ -7,14 +7,19 @@
#include "cd_image.h" #include "cd_image.h"
#include "cd_subchannel_replacement.h" #include "cd_subchannel_replacement.h"
#include "common/align.h" #include "common/align.h"
#include "common/assert.h" #include "common/assert.h"
#include "common/error.h" #include "common/error.h"
#include "common/file_system.h" #include "common/file_system.h"
#include "common/log.h" #include "common/log.h"
#include "common/path.h"
#include "common/platform.h" #include "common/platform.h"
#include "common/string_util.h"
#include "fmt/format.h" #include "fmt/format.h"
#include "libchdr/chd.h" #include "libchdr/chd.h"
#include <algorithm> #include <algorithm>
#include <cerrno> #include <cerrno>
#include <cstdio> #include <cstdio>
@ -22,6 +27,7 @@
#include <limits> #include <limits>
#include <map> #include <map>
#include <optional> #include <optional>
Log_SetChannel(CDImageCHD); Log_SetChannel(CDImageCHD);
static std::optional<CDImage::TrackMode> ParseTrackModeString(const char* str) 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; return std::nullopt;
} }
namespace {
class CDImageCHD : public CDImage class CDImageCHD : public CDImage
{ {
public: public:
@ -66,12 +73,13 @@ private:
enum : u32 enum : u32
{ {
CHD_CD_SECTOR_DATA_SIZE = 2352 + 96, 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); bool ReadHunk(u32 hunk_index);
std::FILE* m_fp = nullptr;
chd_file* m_chd = nullptr; chd_file* m_chd = nullptr;
u32 m_hunk_size = 0; u32 m_hunk_size = 0;
u32 m_sectors_per_hunk = 0; u32 m_sectors_per_hunk = 0;
@ -82,6 +90,7 @@ private:
CDSubChannelReplacement m_sbi; CDSubChannelReplacement m_sbi;
}; };
} // namespace
CDImageCHD::CDImageCHD() = default; CDImageCHD::CDImageCHD() = default;
@ -89,15 +98,102 @@ CDImageCHD::~CDImageCHD()
{ {
if (m_chd) if (m_chd)
chd_close(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) bool CDImageCHD::Open(const char* filename, Common::Error* error)
{ {
Assert(!m_fp); auto fp = FileSystem::OpenManagedSharedCFile(filename, "rb", FileSystem::FileShareMode::DenyWrite);
m_fp = FileSystem::OpenCFile(filename, "rb"); if (!fp)
if (!m_fp)
{ {
Log_ErrorPrintf("Failed to open CHD '%s': errno %d", filename, errno); Log_ErrorPrintf("Failed to open CHD '%s': errno %d", filename, errno);
if (error) if (error)
@ -106,15 +202,9 @@ bool CDImageCHD::Open(const char* filename, Common::Error* error)
return false; return false;
} }
chd_error err = chd_open_file(m_fp, CHD_OPEN_READ, nullptr, &m_chd); m_chd = OpenCHD(filename, std::move(fp), error, 0);
if (err != CHDERR_NONE) if (!m_chd)
{
Log_ErrorPrintf("Failed to open CHD '%s': %s", filename, chd_error_string(err));
if (error)
error->SetMessage(chd_error_string(err));
return false; return false;
}
const chd_header* header = chd_get_header(m_chd); const chd_header* header = chd_get_header(m_chd);
m_hunk_size = header->hunkbytes; m_hunk_size = header->hunkbytes;
@ -146,8 +236,8 @@ bool CDImageCHD::Open(const char* filename, Common::Error* error)
u32 metadata_length; u32 metadata_length;
int track_num = 0, frames = 0, pregap_frames = 0, postgap_frames = 0; 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), chd_error err = chd_get_metadata(m_chd, CDROM_TRACK_METADATA2_TAG, num_tracks, metadata_str, sizeof(metadata_str),
&metadata_length, nullptr, nullptr); &metadata_length, nullptr, nullptr);
if (err == CHDERR_NONE) if (err == CHDERR_NONE)
{ {
if (std::sscanf(metadata_str, CDROM_TRACK_METADATA2_FORMAT, &track_num, type_str, subtype_str, &frames, if (std::sscanf(metadata_str, CDROM_TRACK_METADATA2_FORMAT, &track_num, type_str, subtype_str, &frames,