mirror of https://github.com/PCSX2/pcsx2.git
3rdparty/rcheevos: Bump to 3d01191 and move in-tree
- aes and 3DS functions in hash.c removed, due to potential legal issues. - .github/test/validator directories removed, as they are unnecessary.
This commit is contained in:
parent
a903387182
commit
ef9cbf6be8
|
@ -21,9 +21,6 @@
|
|||
[submodule "3rdparty/zstd/zstd"]
|
||||
path = 3rdparty/zstd/zstd
|
||||
url = https://github.com/facebook/zstd.git
|
||||
[submodule "3rdparty/rcheevos/rcheevos"]
|
||||
path = 3rdparty/rcheevos/rcheevos
|
||||
url = https://github.com/RetroAchievements/rcheevos.git
|
||||
[submodule "3rdparty/libwebp/libwebp"]
|
||||
path = 3rdparty/libwebp/libwebp
|
||||
url = https://github.com/webmproject/libwebp
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
# More info: http://EditorConfig.org
|
||||
root = true
|
||||
|
||||
# * here means any file type
|
||||
[*]
|
||||
end_of_line = crlf
|
||||
insert_final_newline = true
|
||||
|
||||
# latin1 is a type of ASCII, should work with mbcs
|
||||
[*.{h,c,cpp}]
|
||||
charset = latin1
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
trim_trailing_whitespace = true
|
||||
curly_bracket_next_line = false
|
|
@ -0,0 +1,65 @@
|
|||
# Prerequisites
|
||||
*.d
|
||||
|
||||
# Object files
|
||||
*.o
|
||||
*.ko
|
||||
*.obj
|
||||
*.elf
|
||||
|
||||
# Linker output
|
||||
*.ilk
|
||||
*.map
|
||||
*.exp
|
||||
|
||||
# Precompiled Headers
|
||||
*.gch
|
||||
*.pch
|
||||
|
||||
# Libraries
|
||||
*.lib
|
||||
*.a
|
||||
*.la
|
||||
*.lo
|
||||
|
||||
# Shared objects (inc. Windows DLLs)
|
||||
*.dll
|
||||
*.so
|
||||
*.so.*
|
||||
*.dylib
|
||||
|
||||
# Executables
|
||||
*.exe
|
||||
*.out
|
||||
*.app
|
||||
*.i*86
|
||||
*.x86_64
|
||||
*.hex
|
||||
|
||||
# Debug files
|
||||
*.dSYM/
|
||||
*.su
|
||||
*.idb
|
||||
*.pdb
|
||||
|
||||
# Kernel Module Compile Results
|
||||
*.mod*
|
||||
*.cmd
|
||||
.tmp_versions/
|
||||
modules.order
|
||||
Module.symvers
|
||||
Mkfile.old
|
||||
dkms.conf
|
||||
|
||||
# Visual Studio files
|
||||
Debug/
|
||||
Release/
|
||||
*.user
|
||||
.vs/
|
||||
|
||||
# Repository specific
|
||||
test/test
|
||||
test/galaga_nes.h
|
||||
test/smw_snes.h
|
||||
validator/validator
|
||||
.vscode/*
|
|
@ -0,0 +1,339 @@
|
|||
# v11.1.0
|
||||
* add rc_client_get_user_agent_clause to generate substring to include in client User-Agents
|
||||
* add rc_client_can_pause function to control pause spam
|
||||
* add achievement type and rarity to rc_api_fetch_game_data_response_t and rc_client_achievement_t
|
||||
* add RC_CLIENT_ACHIEVEMENT_BUCKET_UNSYNCED for achievements that have been unlocked locally but not synced to the server
|
||||
* add RC_CONSOLE_NEO_GEO_CD to supported consoles for chd file extension
|
||||
* add hash logic for RC_CONSOLE_NINTENDO_3DS (note: added new file rhash/aes.c to support this)
|
||||
* add hash logic for RC_CONSOLE_MS_DOS
|
||||
* add game_hash and hardcore fields to rc_api_start_session_request_t and rc_api_ping_request_t
|
||||
* add RC_FORMAT_FIXED1/2/3, RC_FORMAT_TENS, RC_FORMAT_HUNDREDS, RC_FORMAT_THOUSANDS, and RC_FORMAT_UNSIGNED_VALUE
|
||||
* add RC_CONSOLE_STANDALONE
|
||||
* add extern "C" and __cdecl attributes to public functions
|
||||
* add __declspec(dllexport/dllimport) attributes to public functions via #define enablement
|
||||
* add rc_version and rc_version_string functions for accessing version from external linkage
|
||||
* add unicode path support to default filereader (Windows builds)
|
||||
* add rc_mutex support for GEKKO (libogc)
|
||||
* fix async_handle being returned when rc_client_begin_login is aborted synchronously
|
||||
* fix logic error hashing CD files smaller than one sector
|
||||
* fix read across region boundary in rc_libretro_memory_read
|
||||
* fix RC_CLIENT_EVENT_ACHIEVEMENT_CHALLENGE_INDICATOR_SHOW event not being raised if achievement is reset in the same frame that it's primed
|
||||
* moved rc_util.h from src/ to include/
|
||||
* initial (incomplete) support for rc_client_external_t and rc_client_raintegration_t
|
||||
|
||||
# v11.0.0
|
||||
* add rc_client_t and related functions
|
||||
* add RC_MEMSIZE_FLOAT_BE
|
||||
* add Game Pak SRAM to GBA memory map
|
||||
* add hash method for Super Cassettevision
|
||||
* add PSP to potential consoles for chd iterator
|
||||
* add content_type to rc_api_request_t for client to pass to server
|
||||
* add rc_api_process_X_server_response methods to pass status_code and body_length to response processing functions
|
||||
* add additional error codes to rc_api_process_login_response: RC_INVALID_CREDENTIALS, RC_EXPIRED_TOKEN, RC_ACCESS_DENIED
|
||||
* rc_api_start_session now also returns unlocks without having to explicitly call rc_api_fetch_user_unlocks separately
|
||||
* add validation warning for using hit target of 1 on ResetIf condition
|
||||
* move compat.c up a directory and rename to rc_compat.c as it's shared by all subfolders
|
||||
* move rc_libretro.c up a directory as it uses files from all subfolders
|
||||
* convert loosely sized types to strongly sized types (unsigned -> uint32t, unsigned char -> uint8_t, etc)
|
||||
|
||||
# v10.7.1
|
||||
* add rc_runtime_alloc
|
||||
* add rc_libretro_memory_find_avail
|
||||
* extract nginx errors from HTML returned for JSON endpoints
|
||||
* fix real address for 32X extension RAM
|
||||
* fix crash attempting to calculate gamecube hash for non-existent file
|
||||
|
||||
# v10.7.0
|
||||
* add hash method and memory map for Gamecube
|
||||
* add console enum, hash method, and memory map for DSi
|
||||
* add console enum, hash method, and memory map for TI-83
|
||||
* add console enum, hash method, and memory map for Uzebox
|
||||
* add constant for rcheevos version; include in start session server API call
|
||||
* fix SubSource calculations using float values
|
||||
* fix game identification for homebrew Jaguar CD games
|
||||
* fix game identification for CD with many files at root directory
|
||||
* address _CRT_SECURE_NO_WARNINGS warnings
|
||||
|
||||
# v10.6.0
|
||||
* add RC_RUNTIME_EVENT_ACHIEVEMENT_PROGRESS_UPDATED
|
||||
* use optimized comparators for most common condition logic
|
||||
* fix game identification of psx ISOs that have extra slashes in their boot path
|
||||
* fix game identification of ndd files
|
||||
|
||||
# v10.5.0
|
||||
* add RC_MEMSIZE_MBF32_LE
|
||||
* add RC_OPERATOR_XOR
|
||||
* add RC_CONSOLE_ATARI_JAGUAR_CD and hash/memory map for Atari Jaguar CD
|
||||
* add RC_CONSOLE_ARCADIA_2001 and hash/memory map for Arcadia 2001
|
||||
* add RC_CONSOLE_INTERTON_VC_4000 and hash/memory map for Interton VC 4000
|
||||
* add RC_CONSOLE_ELEKTOR_TV_GAMES_COMPUTER and hash/memory map for Elektor TV Games Computer
|
||||
* split RC_CONSOLE_PC_ENGINE_CD off of RC_CONSOLE_PC_ENGINE
|
||||
* add hash/memory map for RC_CONSOLE_NEO_GEO_CD
|
||||
* add additional 256KB of RAM to memory map for RC_CONSOLE_SEGA_32X
|
||||
* validation: don't report redundancy between trigger and non-trigger conditions
|
||||
* validation: don't report range validation errors for float comparisons
|
||||
* change default image host to media.retroachievements.org
|
||||
* fix decoding of denormalized floats
|
||||
* fix full line comments in the middle of Display: section causing RC_MISSING_DISPLAY_STRING
|
||||
|
||||
# v10.4.0
|
||||
* add rc_libretro_hash_set_t with support for #SAVEDISK: m3u extension
|
||||
* add rc_libretro_is_system_allowed for finer-grain control over core support
|
||||
* fix measured value from hitcount not resetting while paused
|
||||
* add RC_CONSOLE_WASM and hash/memory map for WASM-4
|
||||
* add scratchpad memory to RC_CONSOLE_PLAYSTATION_2 memory map
|
||||
* add hash/memory map for RC_CONSOLE_FAIRCHILD_CHANNEL_F
|
||||
* add hash/memory map for RC_CONSOLE_COMMODORE_64
|
||||
* add memory map for RC_CONSOLE_AMIGA
|
||||
|
||||
# v10.3.3
|
||||
* add RC_CONSOLE_ARDUBOY and hash/memory map for Arduboy
|
||||
* add display_name to rc_api_login_response_t
|
||||
* detect logical conflicts and redundancies in validator
|
||||
* fix tab sequences in JSON responses being turned into t
|
||||
* fix overflow when float value has more than 9 digits after the decimal
|
||||
* fix libretro memory mapping when disconnect mask breaks a region into multiple blocks
|
||||
* fix non-virtualized file system call when reading some iso files
|
||||
|
||||
# v10.3.2
|
||||
* fix RC_OPERAND_PRIOR for bit sizes other than RC_MEMSIZE_BIT_0
|
||||
* add memory map and hash for Amstrad CPC
|
||||
* fix an issue where fetch_game_data and fetch_user_unlocks could return RC_MISSING_VALUE instead of acknowledging a server error
|
||||
|
||||
# v10.3.1
|
||||
* allow empty description in rc_api_init_update_leaderboard_request
|
||||
* fix buffered n64 hash when no filereader is registered
|
||||
* add memory map and hash for Mega Duck
|
||||
|
||||
# v10.3.0
|
||||
* support for floating point memory sizes and logic
|
||||
* add built-in macros for rich presence: @Number, @Score, @Centisecs, @Seconds, @Minutes, @ASCIIChar, @UnicodeChar
|
||||
* add rapi functions for fetch_code_notes, update_code_note, upload_achievement, update_leaderboard, fetch_badge_range, and add_game_hash
|
||||
* add lower_is_better and hidden flags to leaderboards in rc_api_fetch_game_data_response_t
|
||||
* add achievements_remaining to rc_api_award_achievement_response_t
|
||||
* add console enums for PC6000, PICO, MEGADUCK and ZEEBO
|
||||
* add memory map for Dreamcast
|
||||
* capture leaderboard/rich presence state in rc_runtime_progress data
|
||||
* support for hashing Dreamcast bin/cues
|
||||
* support for hashing buffered NDS ROMs
|
||||
* fix prior for sizes smaller than a byte sometimes returning current value
|
||||
|
||||
# v10.2.0
|
||||
|
||||
* add RC_MEMSIZE_16_BITS_BE, RC_MEMSIZE_24_BITS_BE, and RC_MEMSIZE_32_BITS_BE
|
||||
* add secondary flag for RC_CONDITION_MEASURED that tells the UI when to show progress as raw vs. as a percentage
|
||||
* add rapi calls for fetch_leaderboard_info, fetch_achievement_info and fetch_game_list
|
||||
* add hash support for RC_CONSOLE_PSP
|
||||
* add RCHEEVOS_URL_SSL compile flag to use https in rurl functions
|
||||
* add space to "PC Engine" label
|
||||
* update RC_CONSOLE_INTELLIVISION memory map to acknowledge non-8-bit addresses
|
||||
* standardize to z64 format when hashing RC_CONSOLE_N64
|
||||
* prevent generating hash for PSX disc when requesting RC_CONSOLE_PLAYSTATION2
|
||||
* fix wrong error message being returned when a leaderboard was only slightly malformed
|
||||
|
||||
# v10.1.0
|
||||
|
||||
* add RC_RUNTIME_EVENT_ACHIEVEMENT_UNPRIMED
|
||||
* add rc_runtime_validate_addresses
|
||||
* add external memory to memory map for Magnavox Odyssey 2
|
||||
* fix memory map base address for NeoGeo Pocket
|
||||
* fix bitcount always returning 0 when used in rich presence
|
||||
|
||||
# v10.0.0
|
||||
|
||||
* add rapi sublibrary for communicating with server (eliminates need for client-side JSON parsing; client must still
|
||||
provide HTTP functionality). rurl is now deprecated
|
||||
* renamed 'rhash.h' to 'rc_hash.h' to eliminate conflict with system headers, renamed 'rconsoles.h' and 'rurl.h' for
|
||||
consistency
|
||||
* split non-runtime functions out of 'rcheevos.h' as they're not needed by most clients
|
||||
* allow ranges in rich presence lookups
|
||||
* add rc_richpresence_size_lines function to fetch line associated to error when processing rich presence script
|
||||
* add rc_runtime_invalidate_address function to disable achievements when an unknown address is queried
|
||||
* add RC_CONDITION_RESET_NEXT_IF
|
||||
* add RC_CONDITION_SUB_HITS
|
||||
* support MAXOF operator ($) for leaderboard values using trigger syntax
|
||||
* allow RC_CONDITION_PAUSE_IF and RC_CONDITION_RESET_IF in leaderboard value expression
|
||||
* changed track parameter of rc_hash_cdreader_open_track_handler to support three virtual tracks:
|
||||
RC_HASH_CDTRACK_FIRST_DATA, RC_HASH_CDTRACK_LAST and RC_HASH_CDTRACK_LARGEST.
|
||||
* changed offset parameter of rc_hash_filereader_seek_handler and return value of rc_hash_filereader_tell_handler
|
||||
from size_t to int64_t to support files larger than 2GB when compiling in 32-bit mode.
|
||||
* reset to default cd reader if NULL is passed to rc_hash_init_custom_cdreader
|
||||
* add hash support for RC_CONSOLE_DREAMCAST, RC_CONSOLE_PLAYSTATION_2, RC_CONSOLE_SUPERVISION, and RC_CONSOLE_TIC80
|
||||
* ignore headers when generating hashs for RC_CONSOLE_PC_ENGINE and RC_CONSOLE_ATARI_7800
|
||||
* require unique identifier when hashing RC_CONSOLE_SEGA_CD and RC_CONSOLE_SATURN discs
|
||||
* add expansion memory to RC_CONSOLE_SG1000 memory map
|
||||
* rename RC_CONSOLE_MAGNAVOX_ODYSSEY -> RC_CONSOLE_MAGNAVOX_ODYSSEY2
|
||||
* rename RC_CONSOLE_AMIGA_ST -> RC_CONSOLE_ATARI_ST
|
||||
* add RC_CONSOLE_SUPERVISION, RC_CONSOLE_SHARPX1, RC_CONSOLE_TIC80, RC_CONSOLE_THOMSONTO8
|
||||
* fix error identifying largest track when track has multiple bins
|
||||
* fix memory corruption error when cue track has more than 6 INDEXs
|
||||
* several improvements to data storage for conditions (rc_memref_t and rc_memref_value_t structures have been modified)
|
||||
|
||||
# v9.2.0
|
||||
|
||||
* fix issue identifying some PC-FX titles where the boot code is not in the first data track
|
||||
* add enums and labels for RC_CONSOLE_MAGNAVOX_ODYSSEY, RC_CONSOLE_SUPER_CASSETTEVISION, RC_CONSOLE_NEO_GEO_CD,
|
||||
RC_CONSOLE_FAIRCHILD_CHANNEL_F, RC_CONSOLE_FM_TOWNS, RC_CONSOLE_ZX_SPECTRUM, RC_CONSOLE_GAME_AND_WATCH,
|
||||
RC_CONSOLE_NOKIA_NGAGE, RC_CONSOLE_NINTENDO_3DS
|
||||
|
||||
# v9.1.0
|
||||
|
||||
* add hash support and memory map for RC_CONSOLE_MSX
|
||||
* add hash support and memory map for RC_CONSOLE_PCFX
|
||||
* include parent directory when hashing non-arcade titles in arcade mode
|
||||
* support absolute paths in m3u
|
||||
* make cue scanning case-insensitive
|
||||
* expand SRAM mapping for RC_CONSOLE_WONDERSWAN
|
||||
* fix display of measured value when another group has an unmeasured hit count
|
||||
* fix memory read error when hashing file with no extension
|
||||
* fix possible divide by zero when using RC_CONDITION_ADD_SOURCE/RC_CONDITION_SUB_SOURCE
|
||||
* fix classification of secondary RC_CONSOLE_SATURN memory region
|
||||
|
||||
# v9.0.0
|
||||
|
||||
* new size: RC_MEMSIZE_BITCOUNT
|
||||
* new flag: RC_CONDITION_OR_NEXT
|
||||
* new flag: RC_CONDITION_TRIGGER
|
||||
* new flag: RC_CONDITION_MEASURED_IF
|
||||
* new operators: RC_OPERATOR_MULT / RC_OPERATOR_DIV
|
||||
* is_bcd removed from memref - now part of RC_MEMSIZE
|
||||
* add rc_runtime_t and associated functions
|
||||
* add rc_hash_ functions
|
||||
* add rc_error_str function
|
||||
* add game_hash parameter to rc_url_award_cheevo
|
||||
* remove hash parameter from rc_url_submit_lboard
|
||||
* add rc_url_ping function
|
||||
* add rc_console_ functions
|
||||
|
||||
# v8.1.0
|
||||
|
||||
* new flag: RC_CONDITION_MEASURED
|
||||
* new flag: RC_CONDITION_ADD_ADDRESS
|
||||
* add rc_evaluate_trigger - extended version of rc_test_trigger with more granular return codes
|
||||
* make rc_evaluate_value return a signed int (was unsigned int)
|
||||
* new formats: RC_FORMAT_MINUTES and RC_FORMAT_SECONDS_AS_MINUTES
|
||||
* removed " Points" text from RC_FORMAT_SCORE format
|
||||
* removed RC_FORMAT_OTHER format. "OTHER" format now parses to RC_FORMAT_SCORE
|
||||
* bugfix: AddHits will now honor AndNext on previous condition
|
||||
|
||||
# v8.0.1
|
||||
|
||||
* bugfix: prevent null reference exception if rich presence contains condition without display string
|
||||
* bugfix: 24-bit read from memory should only read 24-bits
|
||||
|
||||
# v8.0.0
|
||||
|
||||
* support for prior operand type
|
||||
* support for AndNext condition flag
|
||||
* support for rich presence
|
||||
* bugfix: update delta/prior memory values while group is paused
|
||||
* bugfix: allow floating point number without leading 0
|
||||
* bugfix: support empty alt groups
|
||||
|
||||
# v7.1.1
|
||||
|
||||
* Address signed/unsigned mismatch warnings
|
||||
|
||||
# v7.1.0
|
||||
|
||||
* Added the RC_DISABLE_LUA macro to compile rcheevos without Lua support
|
||||
|
||||
# v7.0.2
|
||||
|
||||
* Make sure the code is C89-compliant
|
||||
* Use 32-bit types in Lua
|
||||
* Only evaluate Lua operands when the Lua state is not `NULL`
|
||||
|
||||
# v7.0.1
|
||||
|
||||
* Fix the alignment of memory allocations
|
||||
|
||||
# v7.0.0
|
||||
|
||||
* Removed **rjson**
|
||||
|
||||
# v6.5.0
|
||||
|
||||
* Added a schema for errors returned by the server
|
||||
|
||||
# v6.4.0
|
||||
|
||||
* Added an enumeration with the console identifiers used in RetroAchievements
|
||||
|
||||
# v6.3.1
|
||||
|
||||
* Pass the peek function and the user data to the Lua functions used in operands.
|
||||
|
||||
# v6.3.0
|
||||
|
||||
* Added **rurl**, an API to build URLs to access RetroAchievements web services.
|
||||
|
||||
# v6.2.0
|
||||
|
||||
* Added **rjson**, an API to easily decode RetroAchievements JSON files into C structures.
|
||||
|
||||
# v6.1.0
|
||||
|
||||
* Added support for 24-bit operands with the `'W'` prefix (`RC_OPERAND_24_BITS`)
|
||||
|
||||
# v6.0.2
|
||||
|
||||
* Only define RC_ALIGNMENT if it has not been already defined
|
||||
|
||||
# v6.0.1
|
||||
|
||||
* Use `sizeof(void*)` as a better default for `RC_ALIGNMENT`
|
||||
|
||||
# v6.0.0
|
||||
|
||||
* Simplified API: separate functions to get the buffer size and to parse `memaddr` into the provided buffer
|
||||
* Fixed crash trying to call `rc_update_condition_pause` during a dry-run
|
||||
* The callers are now responsible to pass down a scratch buffer to avoid accesses to out-of-scope memory
|
||||
|
||||
# v5.0.0
|
||||
|
||||
* Pre-compute if a condition has a pause condition in its group
|
||||
* Added a pre-computed flag that tells if the condition set has at least one pause condition
|
||||
* Removed the link to the previous condition in a condition set chain
|
||||
|
||||
# v4.0.0
|
||||
|
||||
* Fixed `ret` not being properly initialized in `rc_parse_trigger`
|
||||
* Build the unit tests with optimizations and `-Wall` to help catch more issues
|
||||
* Added `extern "C"` around the inclusion of the Lua headers so that **rcheevos** can be compiled cleanly as C++
|
||||
* Exposed `rc_parse_value` and `rc_evaluate_value` to be used with rich presence
|
||||
* Removed the `reset` and `dirty` flags from the external API
|
||||
|
||||
# v3.2.0
|
||||
|
||||
* Added the ability to reset triggers and leaderboards
|
||||
* Add a function to parse a format string and return the format enum, and some unit tests for it
|
||||
|
||||
# v3.1.0
|
||||
|
||||
* Added `rc_format_value` to the API
|
||||
|
||||
# v3.0.1
|
||||
|
||||
* Fixed wrong 32-bit value on 64-bit platforms
|
||||
|
||||
# v3.0.0
|
||||
|
||||
* Removed function rc_evaluate_value from the API
|
||||
|
||||
# v2.0.0
|
||||
|
||||
* Removed leaderboard callbacks in favor of a simpler scheme
|
||||
|
||||
# v1.1.2
|
||||
|
||||
* Fixed NULL pointer deference when there's an error during the parse
|
||||
|
||||
# v1.1.1
|
||||
|
||||
* Removed unwanted garbage
|
||||
* Should be v1.0.1 :/
|
||||
|
||||
# v1.0.0
|
||||
|
||||
* First version
|
|
@ -1,54 +1,54 @@
|
|||
add_library(rcheevos
|
||||
rcheevos/include/rcheevos.h
|
||||
rcheevos/include/rc_api_editor.h
|
||||
rcheevos/include/rc_api_info.h
|
||||
rcheevos/include/rc_api_request.h
|
||||
rcheevos/include/rc_api_runtime.h
|
||||
rcheevos/include/rc_api_user.h
|
||||
rcheevos/include/rc_client.h
|
||||
rcheevos/include/rc_consoles.h
|
||||
rcheevos/include/rc_error.h
|
||||
rcheevos/include/rc_hash.h
|
||||
rcheevos/include/rc_runtime.h
|
||||
rcheevos/include/rc_runtime_types.h
|
||||
rcheevos/include/rc_url.h
|
||||
rcheevos/src/rapi/rc_api_common.c
|
||||
rcheevos/src/rapi/rc_api_common.h
|
||||
rcheevos/src/rapi/rc_api_editor.c
|
||||
rcheevos/src/rapi/rc_api_info.c
|
||||
rcheevos/src/rapi/rc_api_runtime.c
|
||||
rcheevos/src/rapi/rc_api_user.c
|
||||
rcheevos/src/rcheevos/alloc.c
|
||||
rcheevos/src/rcheevos/condition.c
|
||||
rcheevos/src/rcheevos/condset.c
|
||||
rcheevos/src/rcheevos/consoleinfo.c
|
||||
rcheevos/src/rcheevos/format.c
|
||||
rcheevos/src/rcheevos/lboard.c
|
||||
rcheevos/src/rcheevos/memref.c
|
||||
rcheevos/src/rcheevos/operand.c
|
||||
rcheevos/src/rcheevos/rc_internal.h
|
||||
rcheevos/src/rcheevos/rc_validate.c
|
||||
rcheevos/src/rcheevos/rc_validate.h
|
||||
rcheevos/src/rcheevos/richpresence.c
|
||||
rcheevos/src/rcheevos/runtime.c
|
||||
rcheevos/src/rcheevos/runtime_progress.c
|
||||
rcheevos/src/rcheevos/trigger.c
|
||||
rcheevos/src/rcheevos/value.c
|
||||
rcheevos/src/rc_client.c
|
||||
rcheevos/src/rc_client_internal.h
|
||||
rcheevos/src/rc_compat.c
|
||||
rcheevos/src/rc_compat.h
|
||||
rcheevos/src/rc_util.c
|
||||
rcheevos/src/rc_util.h
|
||||
rcheevos/src/rc_version.h
|
||||
rcheevos/src/rhash/cdreader.c
|
||||
rcheevos/src/rhash/hash.c
|
||||
rcheevos/src/rhash/md5.c
|
||||
rcheevos/src/rhash/md5.h
|
||||
rcheevos/src/rurl/url.c
|
||||
include/rcheevos.h
|
||||
include/rc_api_editor.h
|
||||
include/rc_api_info.h
|
||||
include/rc_api_request.h
|
||||
include/rc_api_runtime.h
|
||||
include/rc_api_user.h
|
||||
include/rc_client.h
|
||||
include/rc_consoles.h
|
||||
include/rc_error.h
|
||||
include/rc_hash.h
|
||||
include/rc_runtime.h
|
||||
include/rc_runtime_types.h
|
||||
include/rc_url.h
|
||||
include/rc_util.h
|
||||
src/rapi/rc_api_common.c
|
||||
src/rapi/rc_api_common.h
|
||||
src/rapi/rc_api_editor.c
|
||||
src/rapi/rc_api_info.c
|
||||
src/rapi/rc_api_runtime.c
|
||||
src/rapi/rc_api_user.c
|
||||
src/rcheevos/alloc.c
|
||||
src/rcheevos/condition.c
|
||||
src/rcheevos/condset.c
|
||||
src/rcheevos/consoleinfo.c
|
||||
src/rcheevos/format.c
|
||||
src/rcheevos/lboard.c
|
||||
src/rcheevos/memref.c
|
||||
src/rcheevos/operand.c
|
||||
src/rcheevos/rc_internal.h
|
||||
src/rcheevos/rc_validate.c
|
||||
src/rcheevos/rc_validate.h
|
||||
src/rcheevos/richpresence.c
|
||||
src/rcheevos/runtime.c
|
||||
src/rcheevos/runtime_progress.c
|
||||
src/rcheevos/trigger.c
|
||||
src/rcheevos/value.c
|
||||
src/rc_client.c
|
||||
src/rc_client_internal.h
|
||||
src/rc_compat.c
|
||||
src/rc_compat.h
|
||||
src/rc_util.c
|
||||
src/rc_version.h
|
||||
src/rhash/cdreader.c
|
||||
src/rhash/hash.c
|
||||
src/rhash/md5.c
|
||||
src/rhash/md5.h
|
||||
src/rurl/url.c
|
||||
)
|
||||
|
||||
target_include_directories(rcheevos PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/rcheevos/include")
|
||||
target_include_directories(rcheevos INTERFACE "${CMAKE_CURRENT_SOURCE_DIR}/rcheevos/include")
|
||||
target_include_directories(rcheevos PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/include")
|
||||
target_include_directories(rcheevos INTERFACE "${CMAKE_CURRENT_SOURCE_DIR}/include")
|
||||
target_compile_definitions(rcheevos PRIVATE "RC_DISABLE_LUA=1" "RCHEEVOS_URL_SSL")
|
||||
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2018 RetroAchievements.org
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
|
@ -0,0 +1,2 @@
|
|||
- aes and 3DS functions in hash.c removed, due to potential legal issues.
|
||||
- .github/test/validator directories removed, as they are unnecessary.
|
|
@ -0,0 +1,32 @@
|
|||
// swift-tools-version: 5.8
|
||||
// The swift-tools-version declares the minimum version of Swift required to build this package.
|
||||
|
||||
import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
name: "rcheevos",
|
||||
products: [
|
||||
// Products define the executables and libraries a package produces, and make them visible to other packages.
|
||||
.library(
|
||||
name: "rcheevos",
|
||||
targets: ["rcheevos"]),
|
||||
],
|
||||
dependencies: [
|
||||
// Dependencies declare other packages that this package depends on.
|
||||
// .package(url: /* package url */, from: "1.0.0"),
|
||||
],
|
||||
targets: [
|
||||
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
|
||||
// Targets can depend on other targets in this package, and on products in packages this package depends on.
|
||||
.target(
|
||||
name: "rcheevos",
|
||||
dependencies: [],
|
||||
path: ".",
|
||||
exclude: ["src/rcheevos/rc_libretro.c"],
|
||||
sources: ["include", "src/rcheevos", "src/rapi", "src/rhash"],
|
||||
publicHeadersPath: "include",
|
||||
cSettings: [
|
||||
.define("RC_DISABLE_LUA")
|
||||
]),
|
||||
]
|
||||
)
|
|
@ -0,0 +1,81 @@
|
|||
# **rcheevos**
|
||||
|
||||
**rcheevos** is a set of C code, or a library if you will, that tries to make it easier for emulators to process [RetroAchievements](https://retroachievements.org) data, providing support for achievements and leaderboards for their players.
|
||||
|
||||
Keep in mind that **rcheevos** does *not* provide HTTP network connections. Clients must get data from RetroAchievements, and pass the response down to **rcheevos** for processing.
|
||||
|
||||
Not all structures defined by **rcheevos** can be created via the public API, but are exposed to allow interactions beyond just creation, destruction, and testing, such as the ones required by UI code that helps to create them.
|
||||
|
||||
## Lua
|
||||
|
||||
RetroAchievements previously considered the use of the [Lua](https://www.lua.org) language to expand the syntax supported for creating achievements.
|
||||
|
||||
To enable Lua support, you must compile with an additional compilation flag: `HAVE_LUA`, as neither the backend nor the UI for editing achievements are currently Lua-enabled. We do not foresee enabling it any time soon, but the code has not yet been completely eliminated as many of the low-level API fuctions have parameters for LUA data.
|
||||
|
||||
> **rcheevos** does *not* create or maintain a Lua state, you have to create your own state and provide it to **rcheevos** to be used when Lua-coded achievements are found. Calls to **rcheevos** may allocate and/or free additional memory as part of the Lua runtime.
|
||||
|
||||
Lua functions used in trigger operands receive two parameters: `peek`, which is used to read from the emulated system's memory, and `userdata`, which must be passed to `peek`. `peek`'s signature is the same as its C counterpart:
|
||||
|
||||
```lua
|
||||
function peek(address, num_bytes, userdata)
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
An understanding about how achievements are developed may be useful, you can read more about it [here](http://docs.retroachievements.org/Developer-docs/).
|
||||
|
||||
Most of the exposed APIs are documented [here](https://github.com/RetroAchievements/rcheevos/wiki)
|
||||
|
||||
### Return values
|
||||
|
||||
Any function in the rcheevos library that returns a success indicator will return `RC_OK` or one of the constants defined in `rc_error.h`.
|
||||
|
||||
To convert the return code into something human-readable, pass it to:
|
||||
```c
|
||||
const char* rc_error_str(int ret);
|
||||
```
|
||||
|
||||
### Console identifiers
|
||||
|
||||
Platforms supported by RetroAchievements are enumerated in `rc_consoles.h`. Note that some consoles in the enum are not yet fully supported (may require a memory map or some way to uniquely identify games).
|
||||
|
||||
## Runtime support
|
||||
|
||||
Provides a set of functions for managing an active game - initializing and processing achievements, leaderboards, and rich presence. When important things occur, events are raised for the caller via a callback.
|
||||
|
||||
These are in `rc_runtime.h`.
|
||||
|
||||
Note: `rc_runtime_t` still requires the client implement all of the logic that calls the APIs to retrieve the data and perform the unlocks.
|
||||
|
||||
The `rc_client_t` functions wrap a `rc_runtime_t` and manage the API calls and other common functionality (like managing the user information, identifying/loading a game, and building the active/inactive achievements list for the UI). Please see [the wiki](https://github.com/RetroAchievements/rcheevos/wiki/rc_client-integration) for details on using the `rc_client_t` functions.
|
||||
|
||||
## Server Communication
|
||||
|
||||
**rapi** builds URLs to access many RetroAchievements web services. Its purpose it to just to free the developer from having to URL-encode parameters and build correct URLs that are valid for the server.
|
||||
|
||||
**rapi** does *not* make HTTP requests.
|
||||
|
||||
NOTE: **rapi** is a replacement for **rurl**. **rurl** has been deprecated.
|
||||
|
||||
NOTE: `rc_client` is the preferred way to have a client interact with the server.
|
||||
|
||||
These are in `rc_api_user.h`, `rc_api_runtime.h` and `rc_api_common.h`.
|
||||
|
||||
The basic process of making an **rapi** call is to initialize a params object, call a function to convert it to a URL, send that to the server, then pass the response to a function to convert it into a response object, and handle the response values.
|
||||
|
||||
An example can be found on the [rc_api_init_login_request](https://github.com/RetroAchievements/rcheevos/wiki/rc_api_init_login_request#example) page.
|
||||
|
||||
### Functions
|
||||
|
||||
Please see the [wiki](https://github.com/RetroAchievements/rcheevos/wiki) for details on the functions exposed for **rapi**.
|
||||
|
||||
## Game Identification
|
||||
|
||||
**rhash** provides logic for generating a RetroAchievements hash for a given game. There are two ways to use the API - you can pass the filename and let rhash open and process the file, or you can pass the buffered copy of the file to rhash if you've already loaded it into memory.
|
||||
|
||||
These are in `rc_hash.h`.
|
||||
|
||||
```c
|
||||
int rc_hash_generate_from_buffer(char hash[33], int console_id, uint8_t* buffer, size_t buffer_size);
|
||||
int rc_hash_generate_from_file(char hash[33], int console_id, const char* path);
|
||||
```
|
|
@ -0,0 +1,253 @@
|
|||
#ifndef RC_API_EDITOR_H
|
||||
#define RC_API_EDITOR_H
|
||||
|
||||
#include "rc_api_request.h"
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
RC_BEGIN_C_DECLS
|
||||
|
||||
/* --- Fetch Code Notes --- */
|
||||
|
||||
/**
|
||||
* API parameters for a fetch code notes request.
|
||||
*/
|
||||
typedef struct rc_api_fetch_code_notes_request_t {
|
||||
/* The unique identifier of the game */
|
||||
uint32_t game_id;
|
||||
}
|
||||
rc_api_fetch_code_notes_request_t;
|
||||
|
||||
/* A code note definiton */
|
||||
typedef struct rc_api_code_note_t {
|
||||
/* The address the note is associated to */
|
||||
uint32_t address;
|
||||
/* The name of the use who last updated the note */
|
||||
const char* author;
|
||||
/* The contents of the note */
|
||||
const char* note;
|
||||
} rc_api_code_note_t;
|
||||
|
||||
/**
|
||||
* Response data for a fetch code notes request.
|
||||
*/
|
||||
typedef struct rc_api_fetch_code_notes_response_t {
|
||||
/* An array of code notes for the game */
|
||||
rc_api_code_note_t* notes;
|
||||
/* The number of items in the notes array */
|
||||
uint32_t num_notes;
|
||||
|
||||
/* Common server-provided response information */
|
||||
rc_api_response_t response;
|
||||
}
|
||||
rc_api_fetch_code_notes_response_t;
|
||||
|
||||
RC_EXPORT int RC_CCONV rc_api_init_fetch_code_notes_request(rc_api_request_t* request, const rc_api_fetch_code_notes_request_t* api_params);
|
||||
RC_EXPORT int RC_CCONV rc_api_process_fetch_code_notes_response(rc_api_fetch_code_notes_response_t* response, const char* server_response);
|
||||
RC_EXPORT int RC_CCONV 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_EXPORT void RC_CCONV rc_api_destroy_fetch_code_notes_response(rc_api_fetch_code_notes_response_t* response);
|
||||
|
||||
/* --- Update Code Note --- */
|
||||
|
||||
/**
|
||||
* API parameters for an update code note request.
|
||||
*/
|
||||
typedef struct rc_api_update_code_note_request_t {
|
||||
/* The username of the developer */
|
||||
const char* username;
|
||||
/* The API token from the login request */
|
||||
const char* api_token;
|
||||
/* The unique identifier of the game */
|
||||
uint32_t game_id;
|
||||
/* The address the note is associated to */
|
||||
uint32_t address;
|
||||
/* The contents of the note (NULL or empty to delete a note) */
|
||||
const char* note;
|
||||
}
|
||||
rc_api_update_code_note_request_t;
|
||||
|
||||
/**
|
||||
* Response data for an update code note request.
|
||||
*/
|
||||
typedef struct rc_api_update_code_note_response_t {
|
||||
/* Common server-provided response information */
|
||||
rc_api_response_t response;
|
||||
}
|
||||
rc_api_update_code_note_response_t;
|
||||
|
||||
RC_EXPORT int RC_CCONV rc_api_init_update_code_note_request(rc_api_request_t* request, const rc_api_update_code_note_request_t* api_params);
|
||||
RC_EXPORT int RC_CCONV rc_api_process_update_code_note_response(rc_api_update_code_note_response_t* response, const char* server_response);
|
||||
RC_EXPORT int RC_CCONV rc_api_process_update_code_note_server_response(rc_api_update_code_note_response_t* response, const rc_api_server_response_t* server_response);
|
||||
RC_EXPORT void RC_CCONV rc_api_destroy_update_code_note_response(rc_api_update_code_note_response_t* response);
|
||||
|
||||
/* --- Update Achievement --- */
|
||||
|
||||
/**
|
||||
* API parameters for an update achievement request.
|
||||
*/
|
||||
typedef struct rc_api_update_achievement_request_t {
|
||||
/* The username of the developer */
|
||||
const char* username;
|
||||
/* The API token from the login request */
|
||||
const char* api_token;
|
||||
/* The unique identifier of the achievement (0 to create a new achievement) */
|
||||
uint32_t achievement_id;
|
||||
/* The unique identifier of the game */
|
||||
uint32_t game_id;
|
||||
/* The name of the achievement */
|
||||
const char* title;
|
||||
/* The description of the achievement */
|
||||
const char* description;
|
||||
/* The badge name for the achievement */
|
||||
const char* badge;
|
||||
/* The serialized trigger for the achievement */
|
||||
const char* trigger;
|
||||
/* The number of points the achievement is worth */
|
||||
uint32_t points;
|
||||
/* The category of the achievement */
|
||||
uint32_t category;
|
||||
/* The type of the achievement */
|
||||
uint32_t type;
|
||||
}
|
||||
rc_api_update_achievement_request_t;
|
||||
|
||||
/**
|
||||
* Response data for an update achievement request.
|
||||
*/
|
||||
typedef struct rc_api_update_achievement_response_t {
|
||||
/* The unique identifier of the achievement */
|
||||
uint32_t achievement_id;
|
||||
|
||||
/* Common server-provided response information */
|
||||
rc_api_response_t response;
|
||||
}
|
||||
rc_api_update_achievement_response_t;
|
||||
|
||||
RC_EXPORT int RC_CCONV rc_api_init_update_achievement_request(rc_api_request_t* request, const rc_api_update_achievement_request_t* api_params);
|
||||
RC_EXPORT int RC_CCONV rc_api_process_update_achievement_response(rc_api_update_achievement_response_t* response, const char* server_response);
|
||||
RC_EXPORT int RC_CCONV rc_api_process_update_achievement_server_response(rc_api_update_achievement_response_t* response, const rc_api_server_response_t* server_response);
|
||||
RC_EXPORT void RC_CCONV rc_api_destroy_update_achievement_response(rc_api_update_achievement_response_t* response);
|
||||
|
||||
/* --- Update Leaderboard --- */
|
||||
|
||||
/**
|
||||
* API parameters for an update leaderboard request.
|
||||
*/
|
||||
typedef struct rc_api_update_leaderboard_request_t {
|
||||
/* The username of the developer */
|
||||
const char* username;
|
||||
/* The API token from the login request */
|
||||
const char* api_token;
|
||||
/* The unique identifier of the leaderboard (0 to create a new leaderboard) */
|
||||
uint32_t leaderboard_id;
|
||||
/* The unique identifier of the game */
|
||||
uint32_t game_id;
|
||||
/* The name of the leaderboard */
|
||||
const char* title;
|
||||
/* The description of the leaderboard */
|
||||
const char* description;
|
||||
/* The start trigger for the leaderboard */
|
||||
const char* start_trigger;
|
||||
/* The submit trigger for the leaderboard */
|
||||
const char* submit_trigger;
|
||||
/* The cancel trigger for the leaderboard */
|
||||
const char* cancel_trigger;
|
||||
/* The value definition for the leaderboard */
|
||||
const char* value_definition;
|
||||
/* The format of leaderboard values */
|
||||
const char* format;
|
||||
/* Whether or not lower scores are better for the leaderboard */
|
||||
uint32_t lower_is_better;
|
||||
}
|
||||
rc_api_update_leaderboard_request_t;
|
||||
|
||||
/**
|
||||
* Response data for an update leaderboard request.
|
||||
*/
|
||||
typedef struct rc_api_update_leaderboard_response_t {
|
||||
/* The unique identifier of the leaderboard */
|
||||
uint32_t leaderboard_id;
|
||||
|
||||
/* Common server-provided response information */
|
||||
rc_api_response_t response;
|
||||
}
|
||||
rc_api_update_leaderboard_response_t;
|
||||
|
||||
RC_EXPORT int RC_CCONV rc_api_init_update_leaderboard_request(rc_api_request_t* request, const rc_api_update_leaderboard_request_t* api_params);
|
||||
RC_EXPORT int RC_CCONV rc_api_process_update_leaderboard_response(rc_api_update_leaderboard_response_t* response, const char* server_response);
|
||||
RC_EXPORT int RC_CCONV rc_api_process_update_leaderboard_server_response(rc_api_update_leaderboard_response_t* response, const rc_api_server_response_t* server_response);
|
||||
RC_EXPORT void RC_CCONV rc_api_destroy_update_leaderboard_response(rc_api_update_leaderboard_response_t* response);
|
||||
|
||||
/* --- Fetch Badge Range --- */
|
||||
|
||||
/**
|
||||
* API parameters for a fetch badge range request.
|
||||
*/
|
||||
typedef struct rc_api_fetch_badge_range_request_t {
|
||||
/* Unused */
|
||||
uint32_t unused;
|
||||
}
|
||||
rc_api_fetch_badge_range_request_t;
|
||||
|
||||
/**
|
||||
* Response data for a fetch badge range request.
|
||||
*/
|
||||
typedef struct rc_api_fetch_badge_range_response_t {
|
||||
/* The numeric identifier of the first valid badge ID */
|
||||
uint32_t first_badge_id;
|
||||
/* The numeric identifier of the first unassigned badge ID */
|
||||
uint32_t next_badge_id;
|
||||
|
||||
/* Common server-provided response information */
|
||||
rc_api_response_t response;
|
||||
}
|
||||
rc_api_fetch_badge_range_response_t;
|
||||
|
||||
RC_EXPORT int RC_CCONV rc_api_init_fetch_badge_range_request(rc_api_request_t* request, const rc_api_fetch_badge_range_request_t* api_params);
|
||||
RC_EXPORT int RC_CCONV rc_api_process_fetch_badge_range_response(rc_api_fetch_badge_range_response_t* response, const char* server_response);
|
||||
RC_EXPORT int RC_CCONV rc_api_process_fetch_badge_range_server_response(rc_api_fetch_badge_range_response_t* response, const rc_api_server_response_t* server_response);
|
||||
RC_EXPORT void RC_CCONV rc_api_destroy_fetch_badge_range_response(rc_api_fetch_badge_range_response_t* response);
|
||||
|
||||
/* --- Add Game Hash --- */
|
||||
|
||||
/**
|
||||
* API parameters for an add game hash request.
|
||||
*/
|
||||
typedef struct rc_api_add_game_hash_request_t {
|
||||
/* The username of the developer */
|
||||
const char* username;
|
||||
/* The API token from the login request */
|
||||
const char* api_token;
|
||||
/* The unique identifier of the game (0 to create a new game entry) */
|
||||
uint32_t game_id;
|
||||
/* The unique identifier of the console for the game */
|
||||
uint32_t console_id;
|
||||
/* The title of the game */
|
||||
const char* title;
|
||||
/* The hash being added */
|
||||
const char* hash;
|
||||
/* A description of the hash being added (usually the normalized ROM name) */
|
||||
const char* hash_description;
|
||||
}
|
||||
rc_api_add_game_hash_request_t;
|
||||
|
||||
/**
|
||||
* Response data for an update code note request.
|
||||
*/
|
||||
typedef struct rc_api_add_game_hash_response_t {
|
||||
/* The unique identifier of the game */
|
||||
uint32_t game_id;
|
||||
|
||||
/* Common server-provided response information */
|
||||
rc_api_response_t response;
|
||||
}
|
||||
rc_api_add_game_hash_response_t;
|
||||
|
||||
RC_EXPORT int RC_CCONV rc_api_init_add_game_hash_request(rc_api_request_t* request, const rc_api_add_game_hash_request_t* api_params);
|
||||
RC_EXPORT int RC_CCONV rc_api_process_add_game_hash_response(rc_api_add_game_hash_response_t* response, const char* server_response);
|
||||
RC_EXPORT int RC_CCONV rc_api_process_add_game_hash_server_response(rc_api_add_game_hash_response_t* response, const rc_api_server_response_t* server_response);
|
||||
RC_EXPORT void RC_CCONV rc_api_destroy_add_game_hash_response(rc_api_add_game_hash_response_t* response);
|
||||
|
||||
RC_END_C_DECLS
|
||||
|
||||
#endif /* RC_EDITOR_H */
|
|
@ -0,0 +1,182 @@
|
|||
#ifndef RC_API_INFO_H
|
||||
#define RC_API_INFO_H
|
||||
|
||||
#include "rc_api_request.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <time.h>
|
||||
|
||||
RC_BEGIN_C_DECLS
|
||||
|
||||
/* --- Fetch Achievement Info --- */
|
||||
|
||||
/**
|
||||
* API parameters for a fetch achievement info request.
|
||||
*/
|
||||
typedef struct rc_api_fetch_achievement_info_request_t {
|
||||
/* The username of the player */
|
||||
const char* username;
|
||||
/* The API token from the login request */
|
||||
const char* api_token;
|
||||
/* The unique identifier of the achievement */
|
||||
uint32_t achievement_id;
|
||||
/* The 1-based index of the first entry to retrieve */
|
||||
uint32_t first_entry;
|
||||
/* The number of entries to retrieve */
|
||||
uint32_t count;
|
||||
/* Non-zero to only return unlocks earned by the user's friends */
|
||||
uint32_t friends_only;
|
||||
}
|
||||
rc_api_fetch_achievement_info_request_t;
|
||||
|
||||
/* An achievement awarded entry */
|
||||
typedef struct rc_api_achievement_awarded_entry_t {
|
||||
/* The user associated to the entry */
|
||||
const char* username;
|
||||
/* When the achievement was awarded */
|
||||
time_t awarded;
|
||||
}
|
||||
rc_api_achievement_awarded_entry_t;
|
||||
|
||||
/**
|
||||
* Response data for a fetch achievement info request.
|
||||
*/
|
||||
typedef struct rc_api_fetch_achievement_info_response_t {
|
||||
/* The unique identifier of the achievement */
|
||||
uint32_t id;
|
||||
/* The unique identifier of the game to which the leaderboard is associated */
|
||||
uint32_t game_id;
|
||||
/* The number of times the achievement has been awarded */
|
||||
uint32_t num_awarded;
|
||||
/* The number of players that have earned at least one achievement for the game */
|
||||
uint32_t num_players;
|
||||
|
||||
/* An array of recently rewarded entries */
|
||||
rc_api_achievement_awarded_entry_t* recently_awarded;
|
||||
/* The number of items in the recently_awarded array */
|
||||
uint32_t num_recently_awarded;
|
||||
|
||||
/* Common server-provided response information */
|
||||
rc_api_response_t response;
|
||||
}
|
||||
rc_api_fetch_achievement_info_response_t;
|
||||
|
||||
RC_EXPORT int RC_CCONV rc_api_init_fetch_achievement_info_request(rc_api_request_t* request, const rc_api_fetch_achievement_info_request_t* api_params);
|
||||
RC_EXPORT int RC_CCONV rc_api_process_fetch_achievement_info_response(rc_api_fetch_achievement_info_response_t* response, const char* server_response);
|
||||
RC_EXPORT int RC_CCONV 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_EXPORT void RC_CCONV rc_api_destroy_fetch_achievement_info_response(rc_api_fetch_achievement_info_response_t* response);
|
||||
|
||||
/* --- Fetch Leaderboard Info --- */
|
||||
|
||||
/**
|
||||
* API parameters for a fetch leaderboard info request.
|
||||
*/
|
||||
typedef struct rc_api_fetch_leaderboard_info_request_t {
|
||||
/* The unique identifier of the leaderboard */
|
||||
uint32_t leaderboard_id;
|
||||
/* The number of entries to retrieve */
|
||||
uint32_t count;
|
||||
/* The 1-based index of the first entry to retrieve */
|
||||
uint32_t first_entry;
|
||||
/* The username of the player around whom the entries should be returned */
|
||||
const char* username;
|
||||
}
|
||||
rc_api_fetch_leaderboard_info_request_t;
|
||||
|
||||
/* A leaderboard info entry */
|
||||
typedef struct rc_api_lboard_info_entry_t {
|
||||
/* The user associated to the entry */
|
||||
const char* username;
|
||||
/* The rank of the entry */
|
||||
uint32_t rank;
|
||||
/* The index of the entry */
|
||||
uint32_t index;
|
||||
/* The value of the entry */
|
||||
int32_t score;
|
||||
/* When the entry was submitted */
|
||||
time_t submitted;
|
||||
}
|
||||
rc_api_lboard_info_entry_t;
|
||||
|
||||
/**
|
||||
* Response data for a fetch leaderboard info request.
|
||||
*/
|
||||
typedef struct rc_api_fetch_leaderboard_info_response_t {
|
||||
/* The unique identifier of the leaderboard */
|
||||
uint32_t id;
|
||||
/* The format to pass to rc_format_value to format the leaderboard value */
|
||||
int format;
|
||||
/* If non-zero, indicates that lower scores appear first */
|
||||
uint32_t lower_is_better;
|
||||
/* The title of the leaderboard */
|
||||
const char* title;
|
||||
/* The description of the leaderboard */
|
||||
const char* description;
|
||||
/* The definition of the leaderboard to be passed to rc_runtime_activate_lboard */
|
||||
const char* definition;
|
||||
/* The unique identifier of the game to which the leaderboard is associated */
|
||||
uint32_t game_id;
|
||||
/* The author of the leaderboard */
|
||||
const char* author;
|
||||
/* When the leaderboard was first uploaded to the server */
|
||||
time_t created;
|
||||
/* When the leaderboard was last modified on the server */
|
||||
time_t updated;
|
||||
|
||||
/* An array of requested entries */
|
||||
rc_api_lboard_info_entry_t* entries;
|
||||
/* The number of items in the entries array */
|
||||
uint32_t num_entries;
|
||||
|
||||
/* Common server-provided response information */
|
||||
rc_api_response_t response;
|
||||
}
|
||||
rc_api_fetch_leaderboard_info_response_t;
|
||||
|
||||
RC_EXPORT int RC_CCONV rc_api_init_fetch_leaderboard_info_request(rc_api_request_t* request, const rc_api_fetch_leaderboard_info_request_t* api_params);
|
||||
RC_EXPORT int RC_CCONV rc_api_process_fetch_leaderboard_info_response(rc_api_fetch_leaderboard_info_response_t* response, const char* server_response);
|
||||
RC_EXPORT int RC_CCONV 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_EXPORT void RC_CCONV rc_api_destroy_fetch_leaderboard_info_response(rc_api_fetch_leaderboard_info_response_t* response);
|
||||
|
||||
/* --- Fetch Games List --- */
|
||||
|
||||
/**
|
||||
* API parameters for a fetch games list request.
|
||||
*/
|
||||
typedef struct rc_api_fetch_games_list_request_t {
|
||||
/* The unique identifier of the console to query */
|
||||
uint32_t console_id;
|
||||
}
|
||||
rc_api_fetch_games_list_request_t;
|
||||
|
||||
/* A game list entry */
|
||||
typedef struct rc_api_game_list_entry_t {
|
||||
/* The unique identifier of the game */
|
||||
uint32_t id;
|
||||
/* The name of the game */
|
||||
const char* name;
|
||||
}
|
||||
rc_api_game_list_entry_t;
|
||||
|
||||
/**
|
||||
* Response data for a fetch games list request.
|
||||
*/
|
||||
typedef struct rc_api_fetch_games_list_response_t {
|
||||
/* An array of requested entries */
|
||||
rc_api_game_list_entry_t* entries;
|
||||
/* The number of items in the entries array */
|
||||
uint32_t num_entries;
|
||||
|
||||
/* Common server-provided response information */
|
||||
rc_api_response_t response;
|
||||
}
|
||||
rc_api_fetch_games_list_response_t;
|
||||
|
||||
RC_EXPORT int RC_CCONV rc_api_init_fetch_games_list_request(rc_api_request_t* request, const rc_api_fetch_games_list_request_t* api_params);
|
||||
RC_EXPORT int RC_CCONV rc_api_process_fetch_games_list_response(rc_api_fetch_games_list_response_t* response, const char* server_response);
|
||||
RC_EXPORT int RC_CCONV 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_EXPORT void RC_CCONV rc_api_destroy_fetch_games_list_response(rc_api_fetch_games_list_response_t* response);
|
||||
|
||||
RC_END_C_DECLS
|
||||
|
||||
#endif /* RC_API_INFO_H */
|
|
@ -0,0 +1,64 @@
|
|||
#ifndef RC_API_REQUEST_H
|
||||
#define RC_API_REQUEST_H
|
||||
|
||||
#include "rc_error.h"
|
||||
#include "rc_util.h"
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
RC_BEGIN_C_DECLS
|
||||
|
||||
/**
|
||||
* A constructed request to send to the retroachievements server.
|
||||
*/
|
||||
typedef struct rc_api_request_t {
|
||||
/* The URL to send the request to (contains protocol, host, path, and query args) */
|
||||
const char* url;
|
||||
/* Additional query args that should be sent via a POST command. If null, GET may be used */
|
||||
const char* post_data;
|
||||
/* The HTTP Content-Type of the POST data. */
|
||||
const char* content_type;
|
||||
|
||||
/* Storage for the url and post_data */
|
||||
rc_buffer_t buffer;
|
||||
}
|
||||
rc_api_request_t;
|
||||
|
||||
/**
|
||||
* Common attributes for all server responses.
|
||||
*/
|
||||
typedef struct rc_api_response_t {
|
||||
/* Server-provided success indicator (non-zero on success, zero on failure) */
|
||||
int succeeded;
|
||||
/* Server-provided message associated to the failure */
|
||||
const char* error_message;
|
||||
/* Server-provided error code associated to the failure */
|
||||
const char* error_code;
|
||||
|
||||
/* Storage for the response data */
|
||||
rc_buffer_t buffer;
|
||||
}
|
||||
rc_api_response_t;
|
||||
|
||||
RC_EXPORT void RC_CCONV rc_api_destroy_request(rc_api_request_t* request);
|
||||
|
||||
RC_EXPORT void RC_CCONV rc_api_set_host(const char* hostname);
|
||||
RC_EXPORT void RC_CCONV 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;
|
||||
|
||||
enum {
|
||||
RC_API_SERVER_RESPONSE_CLIENT_ERROR = -1,
|
||||
RC_API_SERVER_RESPONSE_RETRYABLE_CLIENT_ERROR = -2
|
||||
};
|
||||
|
||||
RC_END_C_DECLS
|
||||
|
||||
#endif /* RC_API_REQUEST_H */
|
|
@ -0,0 +1,310 @@
|
|||
#ifndef RC_API_RUNTIME_H
|
||||
#define RC_API_RUNTIME_H
|
||||
|
||||
#include "rc_api_request.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <time.h>
|
||||
|
||||
RC_BEGIN_C_DECLS
|
||||
|
||||
/* --- Fetch Image --- */
|
||||
|
||||
/**
|
||||
* API parameters for a fetch image request.
|
||||
* NOTE: fetch image server response is the raw image data. There is no rc_api_process_fetch_image_response function.
|
||||
*/
|
||||
typedef struct rc_api_fetch_image_request_t {
|
||||
/* The name of the image to fetch */
|
||||
const char* image_name;
|
||||
/* The type of image to fetch */
|
||||
uint32_t image_type;
|
||||
}
|
||||
rc_api_fetch_image_request_t;
|
||||
|
||||
#define RC_IMAGE_TYPE_GAME 1
|
||||
#define RC_IMAGE_TYPE_ACHIEVEMENT 2
|
||||
#define RC_IMAGE_TYPE_ACHIEVEMENT_LOCKED 3
|
||||
#define RC_IMAGE_TYPE_USER 4
|
||||
|
||||
RC_EXPORT int RC_CCONV rc_api_init_fetch_image_request(rc_api_request_t* request, const rc_api_fetch_image_request_t* api_params);
|
||||
|
||||
/* --- Resolve Hash --- */
|
||||
|
||||
/**
|
||||
* API parameters for a resolve hash request.
|
||||
*/
|
||||
typedef struct rc_api_resolve_hash_request_t {
|
||||
/* Unused - hash lookup does not require credentials */
|
||||
const char* username;
|
||||
/* Unused - hash lookup does not require credentials */
|
||||
const char* api_token;
|
||||
/* The generated hash of the game to be identified */
|
||||
const char* game_hash;
|
||||
}
|
||||
rc_api_resolve_hash_request_t;
|
||||
|
||||
/**
|
||||
* Response data for a resolve hash request.
|
||||
*/
|
||||
typedef struct rc_api_resolve_hash_response_t {
|
||||
/* The unique identifier of the game, 0 if no match was found */
|
||||
uint32_t game_id;
|
||||
|
||||
/* Common server-provided response information */
|
||||
rc_api_response_t response;
|
||||
}
|
||||
rc_api_resolve_hash_response_t;
|
||||
|
||||
RC_EXPORT int RC_CCONV rc_api_init_resolve_hash_request(rc_api_request_t* request, const rc_api_resolve_hash_request_t* api_params);
|
||||
RC_EXPORT int RC_CCONV rc_api_process_resolve_hash_response(rc_api_resolve_hash_response_t* response, const char* server_response);
|
||||
RC_EXPORT int RC_CCONV rc_api_process_resolve_hash_server_response(rc_api_resolve_hash_response_t* response, const rc_api_server_response_t* server_response);
|
||||
RC_EXPORT void RC_CCONV rc_api_destroy_resolve_hash_response(rc_api_resolve_hash_response_t* response);
|
||||
|
||||
/* --- Fetch Game Data --- */
|
||||
|
||||
/**
|
||||
* API parameters for a fetch game data request.
|
||||
*/
|
||||
typedef struct rc_api_fetch_game_data_request_t {
|
||||
/* The username of the player */
|
||||
const char* username;
|
||||
/* The API token from the login request */
|
||||
const char* api_token;
|
||||
/* The unique identifier of the game */
|
||||
uint32_t game_id;
|
||||
}
|
||||
rc_api_fetch_game_data_request_t;
|
||||
|
||||
/* A leaderboard definition */
|
||||
typedef struct rc_api_leaderboard_definition_t {
|
||||
/* The unique identifier of the leaderboard */
|
||||
uint32_t id;
|
||||
/* The format to pass to rc_format_value to format the leaderboard value */
|
||||
int format;
|
||||
/* The title of the leaderboard */
|
||||
const char* title;
|
||||
/* The description of the leaderboard */
|
||||
const char* description;
|
||||
/* The definition of the leaderboard to be passed to rc_runtime_activate_lboard */
|
||||
const char* definition;
|
||||
/* Non-zero if lower values are better for this leaderboard */
|
||||
uint8_t lower_is_better;
|
||||
/* Non-zero if the leaderboard should not be displayed in a list of leaderboards */
|
||||
uint8_t hidden;
|
||||
}
|
||||
rc_api_leaderboard_definition_t;
|
||||
|
||||
/* An achievement definition */
|
||||
typedef struct rc_api_achievement_definition_t {
|
||||
/* The unique identifier of the achievement */
|
||||
uint32_t id;
|
||||
/* The number of points the achievement is worth */
|
||||
uint32_t points;
|
||||
/* The achievement category (core, unofficial) */
|
||||
uint32_t category;
|
||||
/* The title of the achievement */
|
||||
const char* title;
|
||||
/* The dscription of the achievement */
|
||||
const char* description;
|
||||
/* The definition of the achievement to be passed to rc_runtime_activate_achievement */
|
||||
const char* definition;
|
||||
/* The author of the achievment */
|
||||
const char* author;
|
||||
/* The image name for the achievement badge */
|
||||
const char* badge_name;
|
||||
/* When the achievement was first uploaded to the server */
|
||||
time_t created;
|
||||
/* When the achievement was last modified on the server */
|
||||
time_t updated;
|
||||
/* The achievement type (win/progression/missable) */
|
||||
uint32_t type;
|
||||
/* The approximate rarity of the achievement (X% of users have earned the achievement) */
|
||||
float rarity;
|
||||
/* The approximate rarity of the achievement in hardcore (X% of users have earned the achievement in hardcore) */
|
||||
float rarity_hardcore;
|
||||
}
|
||||
rc_api_achievement_definition_t;
|
||||
|
||||
#define RC_ACHIEVEMENT_CATEGORY_CORE 3
|
||||
#define RC_ACHIEVEMENT_CATEGORY_UNOFFICIAL 5
|
||||
|
||||
#define RC_ACHIEVEMENT_TYPE_STANDARD 0
|
||||
#define RC_ACHIEVEMENT_TYPE_MISSABLE 1
|
||||
#define RC_ACHIEVEMENT_TYPE_PROGRESSION 2
|
||||
#define RC_ACHIEVEMENT_TYPE_WIN 3
|
||||
|
||||
/**
|
||||
* Response data for a fetch game data request.
|
||||
*/
|
||||
typedef struct rc_api_fetch_game_data_response_t {
|
||||
/* The unique identifier of the game */
|
||||
uint32_t id;
|
||||
/* The console associated to the game */
|
||||
uint32_t console_id;
|
||||
/* The title of the game */
|
||||
const char* title;
|
||||
/* The image name for the game badge */
|
||||
const char* image_name;
|
||||
/* The rich presence script for the game to be passed to rc_runtime_activate_richpresence */
|
||||
const char* rich_presence_script;
|
||||
|
||||
/* An array of achievements for the game */
|
||||
rc_api_achievement_definition_t* achievements;
|
||||
/* The number of items in the achievements array */
|
||||
uint32_t num_achievements;
|
||||
|
||||
/* An array of leaderboards for the game */
|
||||
rc_api_leaderboard_definition_t* leaderboards;
|
||||
/* The number of items in the leaderboards array */
|
||||
uint32_t num_leaderboards;
|
||||
|
||||
/* Common server-provided response information */
|
||||
rc_api_response_t response;
|
||||
}
|
||||
rc_api_fetch_game_data_response_t;
|
||||
|
||||
RC_EXPORT int RC_CCONV rc_api_init_fetch_game_data_request(rc_api_request_t* request, const rc_api_fetch_game_data_request_t* api_params);
|
||||
RC_EXPORT int RC_CCONV rc_api_process_fetch_game_data_response(rc_api_fetch_game_data_response_t* response, const char* server_response);
|
||||
RC_EXPORT int RC_CCONV 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_EXPORT void RC_CCONV rc_api_destroy_fetch_game_data_response(rc_api_fetch_game_data_response_t* response);
|
||||
|
||||
/* --- Ping --- */
|
||||
|
||||
/**
|
||||
* API parameters for a ping request.
|
||||
*/
|
||||
typedef struct rc_api_ping_request_t {
|
||||
/* The username of the player */
|
||||
const char* username;
|
||||
/* The API token from the login request */
|
||||
const char* api_token;
|
||||
/* The unique identifier of the game */
|
||||
uint32_t game_id;
|
||||
/* (optional) The current rich presence evaluation for the user */
|
||||
const char* rich_presence;
|
||||
/* (recommended) The hash associated to the game being played */
|
||||
const char* game_hash;
|
||||
/* Non-zero if hardcore is currently enabled (ignored if game_hash is null) */
|
||||
uint32_t hardcore;
|
||||
}
|
||||
rc_api_ping_request_t;
|
||||
|
||||
/**
|
||||
* Response data for a ping request.
|
||||
*/
|
||||
typedef struct rc_api_ping_response_t {
|
||||
/* Common server-provided response information */
|
||||
rc_api_response_t response;
|
||||
}
|
||||
rc_api_ping_response_t;
|
||||
|
||||
RC_EXPORT int RC_CCONV rc_api_init_ping_request(rc_api_request_t* request, const rc_api_ping_request_t* api_params);
|
||||
RC_EXPORT int RC_CCONV rc_api_process_ping_response(rc_api_ping_response_t* response, const char* server_response);
|
||||
RC_EXPORT int RC_CCONV rc_api_process_ping_server_response(rc_api_ping_response_t* response, const rc_api_server_response_t* server_response);
|
||||
RC_EXPORT void RC_CCONV rc_api_destroy_ping_response(rc_api_ping_response_t* response);
|
||||
|
||||
/* --- Award Achievement --- */
|
||||
|
||||
/**
|
||||
* API parameters for an award achievement request.
|
||||
*/
|
||||
typedef struct rc_api_award_achievement_request_t {
|
||||
/* The username of the player */
|
||||
const char* username;
|
||||
/* The API token from the login request */
|
||||
const char* api_token;
|
||||
/* The unique identifier of the achievement */
|
||||
uint32_t achievement_id;
|
||||
/* Non-zero if the achievement was earned in hardcore */
|
||||
uint32_t hardcore;
|
||||
/* The hash associated to the game being played */
|
||||
const char* game_hash;
|
||||
}
|
||||
rc_api_award_achievement_request_t;
|
||||
|
||||
/**
|
||||
* Response data for an award achievement request.
|
||||
*/
|
||||
typedef struct rc_api_award_achievement_response_t {
|
||||
/* The unique identifier of the achievement that was awarded */
|
||||
uint32_t awarded_achievement_id;
|
||||
/* The updated player score */
|
||||
uint32_t new_player_score;
|
||||
/* The updated player softcore score */
|
||||
uint32_t new_player_score_softcore;
|
||||
/* The number of achievements the user has not yet unlocked for this game
|
||||
* (in hardcore/non-hardcore per hardcore flag in request) */
|
||||
uint32_t achievements_remaining;
|
||||
|
||||
/* Common server-provided response information */
|
||||
rc_api_response_t response;
|
||||
}
|
||||
rc_api_award_achievement_response_t;
|
||||
|
||||
RC_EXPORT int RC_CCONV rc_api_init_award_achievement_request(rc_api_request_t* request, const rc_api_award_achievement_request_t* api_params);
|
||||
RC_EXPORT int RC_CCONV rc_api_process_award_achievement_response(rc_api_award_achievement_response_t* response, const char* server_response);
|
||||
RC_EXPORT int RC_CCONV rc_api_process_award_achievement_server_response(rc_api_award_achievement_response_t* response, const rc_api_server_response_t* server_response);
|
||||
RC_EXPORT void RC_CCONV rc_api_destroy_award_achievement_response(rc_api_award_achievement_response_t* response);
|
||||
|
||||
/* --- Submit Leaderboard Entry --- */
|
||||
|
||||
/**
|
||||
* API parameters for a submit lboard entry request.
|
||||
*/
|
||||
typedef struct rc_api_submit_lboard_entry_request_t {
|
||||
/* The username of the player */
|
||||
const char* username;
|
||||
/* The API token from the login request */
|
||||
const char* api_token;
|
||||
/* The unique identifier of the leaderboard */
|
||||
uint32_t leaderboard_id;
|
||||
/* The value being submitted */
|
||||
int32_t score;
|
||||
/* The hash associated to the game being played */
|
||||
const char* game_hash;
|
||||
}
|
||||
rc_api_submit_lboard_entry_request_t;
|
||||
|
||||
/* A leaderboard entry */
|
||||
typedef struct rc_api_lboard_entry_t {
|
||||
/* The user associated to the entry */
|
||||
const char* username;
|
||||
/* The rank of the entry */
|
||||
uint32_t rank;
|
||||
/* The value of the entry */
|
||||
int32_t score;
|
||||
}
|
||||
rc_api_lboard_entry_t;
|
||||
|
||||
/**
|
||||
* Response data for a submit lboard entry request.
|
||||
*/
|
||||
typedef struct rc_api_submit_lboard_entry_response_t {
|
||||
/* The value that was submitted */
|
||||
int32_t submitted_score;
|
||||
/* The player's best submitted value */
|
||||
int32_t best_score;
|
||||
/* The player's new rank within the leaderboard */
|
||||
uint32_t new_rank;
|
||||
/* The total number of entries in the leaderboard */
|
||||
uint32_t num_entries;
|
||||
|
||||
/* An array of the top entries for the leaderboard */
|
||||
rc_api_lboard_entry_t* top_entries;
|
||||
/* The number of items in the top_entries array */
|
||||
uint32_t num_top_entries;
|
||||
|
||||
/* Common server-provided response information */
|
||||
rc_api_response_t response;
|
||||
}
|
||||
rc_api_submit_lboard_entry_response_t;
|
||||
|
||||
RC_EXPORT int RC_CCONV rc_api_init_submit_lboard_entry_request(rc_api_request_t* request, const rc_api_submit_lboard_entry_request_t* api_params);
|
||||
RC_EXPORT int RC_CCONV rc_api_process_submit_lboard_entry_response(rc_api_submit_lboard_entry_response_t* response, const char* server_response);
|
||||
RC_EXPORT int RC_CCONV 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_EXPORT void RC_CCONV rc_api_destroy_submit_lboard_entry_response(rc_api_submit_lboard_entry_response_t* response);
|
||||
|
||||
RC_END_C_DECLS
|
||||
|
||||
#endif /* RC_API_RUNTIME_H */
|
|
@ -0,0 +1,149 @@
|
|||
#ifndef RC_API_USER_H
|
||||
#define RC_API_USER_H
|
||||
|
||||
#include "rc_api_request.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <time.h>
|
||||
|
||||
RC_BEGIN_C_DECLS
|
||||
|
||||
/* --- Login --- */
|
||||
|
||||
/**
|
||||
* API parameters for a login request.
|
||||
* If both password and api_token are provided, api_token will be ignored.
|
||||
*/
|
||||
typedef struct rc_api_login_request_t {
|
||||
/* The username of the player being logged in */
|
||||
const char* username;
|
||||
/* The API token from a previous login */
|
||||
const char* api_token;
|
||||
/* The player's password */
|
||||
const char* password;
|
||||
}
|
||||
rc_api_login_request_t;
|
||||
|
||||
/**
|
||||
* Response data for a login request.
|
||||
*/
|
||||
typedef struct rc_api_login_response_t {
|
||||
/* The case-corrected username of the player */
|
||||
const char* username;
|
||||
/* The API token to use for all future requests */
|
||||
const char* api_token;
|
||||
/* The current score of the player */
|
||||
uint32_t score;
|
||||
/* The current softcore score of the player */
|
||||
uint32_t score_softcore;
|
||||
/* The number of unread messages waiting for the player on the web site */
|
||||
uint32_t num_unread_messages;
|
||||
/* The preferred name to display for the player */
|
||||
const char* display_name;
|
||||
|
||||
/* Common server-provided response information */
|
||||
rc_api_response_t response;
|
||||
}
|
||||
rc_api_login_response_t;
|
||||
|
||||
RC_EXPORT int RC_CCONV rc_api_init_login_request(rc_api_request_t* request, const rc_api_login_request_t* api_params);
|
||||
RC_EXPORT int RC_CCONV rc_api_process_login_response(rc_api_login_response_t* response, const char* server_response);
|
||||
RC_EXPORT int RC_CCONV rc_api_process_login_server_response(rc_api_login_response_t* response, const rc_api_server_response_t* server_response);
|
||||
RC_EXPORT void RC_CCONV rc_api_destroy_login_response(rc_api_login_response_t* response);
|
||||
|
||||
/* --- Start Session --- */
|
||||
|
||||
/**
|
||||
* API parameters for a start session request.
|
||||
*/
|
||||
typedef struct rc_api_start_session_request_t {
|
||||
/* The username of the player */
|
||||
const char* username;
|
||||
/* The API token from the login request */
|
||||
const char* api_token;
|
||||
/* The unique identifier of the game */
|
||||
uint32_t game_id;
|
||||
/* (recommended) The hash associated to the game being played */
|
||||
const char* game_hash;
|
||||
/* Non-zero if hardcore is currently enabled (ignored if game_hash is null) */
|
||||
uint32_t hardcore;
|
||||
}
|
||||
rc_api_start_session_request_t;
|
||||
|
||||
/**
|
||||
* Response data for an achievement unlock.
|
||||
*/
|
||||
typedef struct rc_api_unlock_entry_t {
|
||||
/* The unique identifier of the unlocked achievement */
|
||||
uint32_t achievement_id;
|
||||
/* When the achievement was unlocked */
|
||||
time_t when;
|
||||
}
|
||||
rc_api_unlock_entry_t;
|
||||
|
||||
/**
|
||||
* Response data for a start session request.
|
||||
*/
|
||||
typedef struct rc_api_start_session_response_t {
|
||||
/* An array of hardcore user unlocks */
|
||||
rc_api_unlock_entry_t* hardcore_unlocks;
|
||||
/* An array of user unlocks */
|
||||
rc_api_unlock_entry_t* unlocks;
|
||||
|
||||
/* The number of items in the hardcore_unlocks array */
|
||||
uint32_t num_hardcore_unlocks;
|
||||
/* The number of items in the unlocks array */
|
||||
uint32_t num_unlocks;
|
||||
|
||||
/* The server timestamp when the response was generated */
|
||||
time_t server_now;
|
||||
|
||||
/* Common server-provided response information */
|
||||
rc_api_response_t response;
|
||||
}
|
||||
rc_api_start_session_response_t;
|
||||
|
||||
RC_EXPORT int RC_CCONV rc_api_init_start_session_request(rc_api_request_t* request, const rc_api_start_session_request_t* api_params);
|
||||
RC_EXPORT int RC_CCONV rc_api_process_start_session_response(rc_api_start_session_response_t* response, const char* server_response);
|
||||
RC_EXPORT int RC_CCONV rc_api_process_start_session_server_response(rc_api_start_session_response_t* response, const rc_api_server_response_t* server_response);
|
||||
RC_EXPORT void RC_CCONV rc_api_destroy_start_session_response(rc_api_start_session_response_t* response);
|
||||
|
||||
/* --- Fetch User Unlocks --- */
|
||||
|
||||
/**
|
||||
* API parameters for a fetch user unlocks request.
|
||||
*/
|
||||
typedef struct rc_api_fetch_user_unlocks_request_t {
|
||||
/* The username of the player */
|
||||
const char* username;
|
||||
/* The API token from the login request */
|
||||
const char* api_token;
|
||||
/* The unique identifier of the game */
|
||||
uint32_t game_id;
|
||||
/* Non-zero to fetch hardcore unlocks, 0 to fetch non-hardcore unlocks */
|
||||
uint32_t hardcore;
|
||||
}
|
||||
rc_api_fetch_user_unlocks_request_t;
|
||||
|
||||
/**
|
||||
* Response data for a fetch user unlocks request.
|
||||
*/
|
||||
typedef struct rc_api_fetch_user_unlocks_response_t {
|
||||
/* An array of achievement IDs previously unlocked by the user */
|
||||
uint32_t* achievement_ids;
|
||||
/* The number of items in the achievement_ids array */
|
||||
uint32_t num_achievement_ids;
|
||||
|
||||
/* Common server-provided response information */
|
||||
rc_api_response_t response;
|
||||
}
|
||||
rc_api_fetch_user_unlocks_response_t;
|
||||
|
||||
RC_EXPORT int RC_CCONV rc_api_init_fetch_user_unlocks_request(rc_api_request_t* request, const rc_api_fetch_user_unlocks_request_t* api_params);
|
||||
RC_EXPORT int RC_CCONV rc_api_process_fetch_user_unlocks_response(rc_api_fetch_user_unlocks_response_t* response, const char* server_response);
|
||||
RC_EXPORT int RC_CCONV rc_api_process_fetch_user_unlocks_server_response(rc_api_fetch_user_unlocks_response_t* response, const rc_api_server_response_t* server_response);
|
||||
RC_EXPORT void RC_CCONV rc_api_destroy_fetch_user_unlocks_response(rc_api_fetch_user_unlocks_response_t* response);
|
||||
|
||||
RC_END_C_DECLS
|
||||
|
||||
#endif /* RC_API_H */
|
|
@ -0,0 +1,677 @@
|
|||
#ifndef RC_CLIENT_H
|
||||
#define RC_CLIENT_H
|
||||
|
||||
#include "rc_api_request.h"
|
||||
#include "rc_error.h"
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <time.h>
|
||||
|
||||
RC_BEGIN_C_DECLS
|
||||
|
||||
/* 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_CCONV *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_CCONV *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_CCONV *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_CCONV *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_CCONV *rc_client_message_callback_t)(const char* message, const rc_client_t* client);
|
||||
|
||||
/*****************************************************************************\
|
||||
| Runtime |
|
||||
\*****************************************************************************/
|
||||
|
||||
/**
|
||||
* Creates a new rc_client_t object.
|
||||
*/
|
||||
RC_EXPORT rc_client_t* RC_CCONV 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.
|
||||
*/
|
||||
RC_EXPORT void RC_CCONV 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.
|
||||
*/
|
||||
RC_EXPORT void RC_CCONV rc_client_set_hardcore_enabled(rc_client_t* client, int enabled);
|
||||
|
||||
/**
|
||||
* Gets whether hardcore is enabled (on by default).
|
||||
*/
|
||||
RC_EXPORT int RC_CCONV 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.
|
||||
*/
|
||||
RC_EXPORT void RC_CCONV rc_client_set_encore_mode_enabled(rc_client_t* client, int enabled);
|
||||
|
||||
/**
|
||||
* Gets whether encore mode is enabled (off by default).
|
||||
*/
|
||||
RC_EXPORT int RC_CCONV 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.
|
||||
*/
|
||||
RC_EXPORT void RC_CCONV rc_client_set_unofficial_enabled(rc_client_t* client, int enabled);
|
||||
|
||||
/**
|
||||
* Gets whether unofficial achievements should be loaded.
|
||||
*/
|
||||
RC_EXPORT int RC_CCONV 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.
|
||||
*/
|
||||
RC_EXPORT void RC_CCONV rc_client_set_spectator_mode_enabled(rc_client_t* client, int enabled);
|
||||
|
||||
/**
|
||||
* Gets whether spectator mode is enabled (off by default).
|
||||
*/
|
||||
RC_EXPORT int RC_CCONV rc_client_get_spectator_mode_enabled(const rc_client_t* client);
|
||||
|
||||
/**
|
||||
* Attaches client-specific data to the runtime.
|
||||
*/
|
||||
RC_EXPORT void RC_CCONV rc_client_set_userdata(rc_client_t* client, void* userdata);
|
||||
|
||||
/**
|
||||
* Gets the client-specific data attached to the runtime.
|
||||
*/
|
||||
RC_EXPORT void* RC_CCONV rc_client_get_userdata(const rc_client_t* client);
|
||||
|
||||
/**
|
||||
* Sets the name of the server to use.
|
||||
*/
|
||||
RC_EXPORT void RC_CCONV rc_client_set_host(const rc_client_t* client, const char* hostname);
|
||||
|
||||
typedef uint64_t rc_clock_t;
|
||||
typedef rc_clock_t (RC_CCONV *rc_get_time_millisecs_func_t)(const rc_client_t* client);
|
||||
|
||||
/**
|
||||
* Specifies a function that returns a value that increases once per millisecond.
|
||||
*/
|
||||
RC_EXPORT void RC_CCONV rc_client_set_get_time_millisecs_function(rc_client_t* client, rc_get_time_millisecs_func_t handler);
|
||||
|
||||
/**
|
||||
* Marks an async process as aborted. The associated callback will not be called.
|
||||
*/
|
||||
RC_EXPORT void RC_CCONV rc_client_abort_async(rc_client_t* client, rc_client_async_handle_t* async_handle);
|
||||
|
||||
/**
|
||||
* Gets a clause that can be added to the User-Agent to identify the version of rcheevos being used.
|
||||
*/
|
||||
RC_EXPORT size_t RC_CCONV rc_client_get_user_agent_clause(rc_client_t* client, char buffer[], size_t buffer_size);
|
||||
|
||||
/*****************************************************************************\
|
||||
| Logging |
|
||||
\*****************************************************************************/
|
||||
|
||||
/**
|
||||
* Sets the logging level and provides a callback to be called to do the logging.
|
||||
*/
|
||||
RC_EXPORT void RC_CCONV 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,
|
||||
NUM_RC_CLIENT_LOG_LEVELS = 5
|
||||
};
|
||||
|
||||
/*****************************************************************************\
|
||||
| User |
|
||||
\*****************************************************************************/
|
||||
|
||||
/**
|
||||
* Attempt to login a user.
|
||||
*/
|
||||
RC_EXPORT rc_client_async_handle_t* RC_CCONV 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_EXPORT rc_client_async_handle_t* RC_CCONV 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.
|
||||
*/
|
||||
RC_EXPORT void RC_CCONV 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.
|
||||
*/
|
||||
RC_EXPORT const rc_client_user_t* RC_CCONV rc_client_get_user_info(const rc_client_t* client);
|
||||
|
||||
/**
|
||||
* Gets the URL for the user's profile picture.
|
||||
* Returns RC_OK on success.
|
||||
*/
|
||||
RC_EXPORT int RC_CCONV 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.
|
||||
*/
|
||||
RC_EXPORT void RC_CCONV 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_EXPORT rc_client_async_handle_t* RC_CCONV 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_EXPORT rc_client_async_handle_t* RC_CCONV rc_client_begin_load_game(rc_client_t* client, const char* hash,
|
||||
rc_client_callback_t callback, void* callback_userdata);
|
||||
|
||||
/**
|
||||
* Unloads the current game.
|
||||
*/
|
||||
RC_EXPORT void RC_CCONV 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.
|
||||
*/
|
||||
RC_EXPORT const rc_client_game_t* RC_CCONV rc_client_get_game_info(const rc_client_t* client);
|
||||
|
||||
/**
|
||||
* Gets the URL for the game image.
|
||||
* Returns RC_OK on success.
|
||||
*/
|
||||
RC_EXPORT int RC_CCONV 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_EXPORT rc_client_async_handle_t* RC_CCONV 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;
|
||||
|
||||
RC_EXPORT const rc_client_subset_t* RC_CCONV 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 */
|
||||
NUM_RC_CLIENT_ACHIEVEMENT_STATES = 4
|
||||
};
|
||||
|
||||
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_TYPE_STANDARD = 0,
|
||||
RC_CLIENT_ACHIEVEMENT_TYPE_MISSABLE = 1,
|
||||
RC_CLIENT_ACHIEVEMENT_TYPE_PROGRESSION = 2,
|
||||
RC_CLIENT_ACHIEVEMENT_TYPE_WIN = 3
|
||||
};
|
||||
|
||||
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,
|
||||
RC_CLIENT_ACHIEVEMENT_BUCKET_UNSYNCED = 8,
|
||||
NUM_RC_CLIENT_ACHIEVEMENT_BUCKETS = 9
|
||||
};
|
||||
|
||||
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;
|
||||
/* minimum version: 11.1 */
|
||||
float rarity;
|
||||
float rarity_hardcore;
|
||||
uint8_t type;
|
||||
} rc_client_achievement_t;
|
||||
|
||||
/**
|
||||
* Get information about an achievement. Returns NULL if not found.
|
||||
*/
|
||||
RC_EXPORT const rc_client_achievement_t* RC_CCONV rc_client_get_achievement_info(rc_client_t* client, uint32_t id);
|
||||
|
||||
/**
|
||||
* Gets the URL for the achievement image.
|
||||
* Returns RC_OK on success.
|
||||
*/
|
||||
RC_EXPORT int RC_CCONV 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_EXPORT rc_client_achievement_list_t* RC_CCONV rc_client_create_achievement_list(rc_client_t* client, int category, int grouping);
|
||||
|
||||
/**
|
||||
* Destroys a list allocated by rc_client_get_achievement_list.
|
||||
*/
|
||||
RC_EXPORT void RC_CCONV rc_client_destroy_achievement_list(rc_client_achievement_list_t* list);
|
||||
|
||||
/**
|
||||
* Returns non-zero if there are any achievements that can be queried through rc_client_create_achievement_list().
|
||||
*/
|
||||
RC_EXPORT int RC_CCONV rc_client_has_achievements(rc_client_t* client);
|
||||
|
||||
/*****************************************************************************\
|
||||
| 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,
|
||||
NUM_RC_CLIENT_LEADERBOARD_STATES = 4
|
||||
};
|
||||
|
||||
enum {
|
||||
RC_CLIENT_LEADERBOARD_FORMAT_TIME = 0,
|
||||
RC_CLIENT_LEADERBOARD_FORMAT_SCORE = 1,
|
||||
RC_CLIENT_LEADERBOARD_FORMAT_VALUE = 2,
|
||||
NUM_RC_CLIENT_LEADERBOARD_FORMATS = 3
|
||||
};
|
||||
|
||||
#define RC_CLIENT_LEADERBOARD_DISPLAY_SIZE 24
|
||||
|
||||
typedef struct rc_client_leaderboard_t {
|
||||
const char* title;
|
||||
const char* description;
|
||||
const char* tracker_value;
|
||||
uint32_t id;
|
||||
uint8_t state;
|
||||
uint8_t format;
|
||||
uint8_t lower_is_better;
|
||||
} rc_client_leaderboard_t;
|
||||
|
||||
/**
|
||||
* Get information about a leaderboard. Returns NULL if not found.
|
||||
*/
|
||||
RC_EXPORT const rc_client_leaderboard_t* RC_CCONV rc_client_get_leaderboard_info(const rc_client_t* client, uint32_t id);
|
||||
|
||||
typedef struct rc_client_leaderboard_tracker_t {
|
||||
char display[RC_CLIENT_LEADERBOARD_DISPLAY_SIZE];
|
||||
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,
|
||||
NUM_RC_CLIENT_LEADERBOARD_BUCKETS = 5
|
||||
};
|
||||
|
||||
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_EXPORT rc_client_leaderboard_list_t* RC_CCONV rc_client_create_leaderboard_list(rc_client_t* client, int grouping);
|
||||
|
||||
/**
|
||||
* Destroys a list allocated by rc_client_get_leaderboard_list.
|
||||
*/
|
||||
RC_EXPORT void RC_CCONV rc_client_destroy_leaderboard_list(rc_client_leaderboard_list_t* list);
|
||||
|
||||
/**
|
||||
* Returns non-zero if the current game has any leaderboards.
|
||||
*/
|
||||
RC_EXPORT int RC_CCONV rc_client_has_leaderboards(rc_client_t* client);
|
||||
|
||||
typedef struct rc_client_leaderboard_entry_t {
|
||||
const char* user;
|
||||
char display[RC_CLIENT_LEADERBOARD_DISPLAY_SIZE];
|
||||
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_CCONV *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_EXPORT rc_client_async_handle_t* RC_CCONV 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_EXPORT rc_client_async_handle_t* RC_CCONV 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.
|
||||
*/
|
||||
RC_EXPORT int RC_CCONV 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.
|
||||
*/
|
||||
RC_EXPORT void RC_CCONV rc_client_destroy_leaderboard_entry_list(rc_client_leaderboard_entry_list_t* list);
|
||||
|
||||
/**
|
||||
* Used for scoreboard events. Contains the response from the server when a leaderboard entry is submitted.
|
||||
* NOTE: This structure is only valid within the event callback. If you want to make use of the data outside
|
||||
* of the callback, you should create copies of both the top entries and usernames within.
|
||||
*/
|
||||
typedef struct rc_client_leaderboard_scoreboard_entry_t {
|
||||
/* The user associated to the entry */
|
||||
const char* username;
|
||||
/* The rank of the entry */
|
||||
uint32_t rank;
|
||||
/* The value of the entry */
|
||||
char score[RC_CLIENT_LEADERBOARD_DISPLAY_SIZE];
|
||||
} rc_client_leaderboard_scoreboard_entry_t;
|
||||
|
||||
typedef struct rc_client_leaderboard_scoreboard_t {
|
||||
/* The ID of the leaderboard which was submitted */
|
||||
uint32_t leaderboard_id;
|
||||
/* The value that was submitted */
|
||||
char submitted_score[RC_CLIENT_LEADERBOARD_DISPLAY_SIZE];
|
||||
/* The player's best submitted value */
|
||||
char best_score[RC_CLIENT_LEADERBOARD_DISPLAY_SIZE];
|
||||
/* The player's new rank within the leaderboard */
|
||||
uint32_t new_rank;
|
||||
/* The total number of entries in the leaderboard */
|
||||
uint32_t num_entries;
|
||||
|
||||
/* An array of the top entries for the leaderboard */
|
||||
rc_client_leaderboard_scoreboard_entry_t* top_entries;
|
||||
/* The number of items in the top_entries array */
|
||||
uint32_t num_top_entries;
|
||||
} rc_client_leaderboard_scoreboard_t;
|
||||
|
||||
/*****************************************************************************\
|
||||
| Rich Presence |
|
||||
\*****************************************************************************/
|
||||
|
||||
/**
|
||||
* Returns non-zero if the current game supports rich presence.
|
||||
*/
|
||||
RC_EXPORT int RC_CCONV rc_client_has_rich_presence(rc_client_t* client);
|
||||
|
||||
/**
|
||||
* Gets the current rich presence message.
|
||||
* Returns the number of characters written to buffer.
|
||||
*/
|
||||
RC_EXPORT size_t RC_CCONV 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_LEADERBOARD_SCOREBOARD = 13, /* [leaderboard_scoreboard] possibly-new ranking received for [leaderboard] */
|
||||
RC_CLIENT_EVENT_RESET = 14, /* emulated system should be reset (as the result of enabling hardcore) */
|
||||
RC_CLIENT_EVENT_GAME_COMPLETED = 15, /* all achievements for the game have been earned */
|
||||
RC_CLIENT_EVENT_SERVER_ERROR = 16, /* an API response returned a [server_error] and will not be retried */
|
||||
RC_CLIENT_EVENT_DISCONNECTED = 17, /* an unlock request could not be completed and is pending */
|
||||
RC_CLIENT_EVENT_RECONNECTED = 18 /* all pending unlocks have been completed */
|
||||
};
|
||||
|
||||
typedef struct rc_client_server_error_t {
|
||||
const char* error_message;
|
||||
const char* api;
|
||||
int result;
|
||||
uint32_t related_id;
|
||||
} 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_leaderboard_scoreboard_t* leaderboard_scoreboard;
|
||||
rc_client_server_error_t* server_error;
|
||||
|
||||
} rc_client_event_t;
|
||||
|
||||
/**
|
||||
* Callback used to notify the client when certain events occur.
|
||||
*/
|
||||
typedef void (RC_CCONV *rc_client_event_handler_t)(const rc_client_event_t* event, rc_client_t* client);
|
||||
|
||||
/**
|
||||
* Provides a callback for event handling.
|
||||
*/
|
||||
RC_EXPORT void RC_CCONV rc_client_set_event_handler(rc_client_t* client, rc_client_event_handler_t handler);
|
||||
|
||||
/**
|
||||
* Provides a callback for reading memory.
|
||||
*/
|
||||
RC_EXPORT void RC_CCONV 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.
|
||||
*/
|
||||
RC_EXPORT int RC_CCONV rc_client_is_processing_required(rc_client_t* client);
|
||||
|
||||
/**
|
||||
* Processes achievements for the current frame.
|
||||
*/
|
||||
RC_EXPORT void RC_CCONV 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.
|
||||
*/
|
||||
RC_EXPORT void RC_CCONV rc_client_idle(rc_client_t* client);
|
||||
|
||||
/**
|
||||
* Determines if a sufficient amount of frames have been processed since the last call to rc_client_can_pause.
|
||||
* Should not be called unless the client is trying to pause.
|
||||
* If false is returned, and frames_remaining is not NULL, frames_remaining will be set to the number of frames
|
||||
* still required before pause is allowed, which can be converted to a time in seconds for displaying to the user.
|
||||
*/
|
||||
RC_EXPORT int RC_CCONV rc_client_can_pause(rc_client_t* client, uint32_t* frames_remaining);
|
||||
|
||||
/**
|
||||
* Informs the runtime that the emulator has been reset. Will reset all achievements and leaderboards
|
||||
* to their initial state (includes hiding indicators/trackers).
|
||||
*/
|
||||
RC_EXPORT void RC_CCONV rc_client_reset(rc_client_t* client);
|
||||
|
||||
/**
|
||||
* Gets the number of bytes needed to serialized the runtime state.
|
||||
*/
|
||||
RC_EXPORT size_t RC_CCONV rc_client_progress_size(rc_client_t* client);
|
||||
|
||||
/**
|
||||
* Serializes the runtime state into a buffer.
|
||||
* Returns RC_OK on success, or an error indicator.
|
||||
*/
|
||||
RC_EXPORT int RC_CCONV 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.
|
||||
*/
|
||||
RC_EXPORT int RC_CCONV rc_client_deserialize_progress(rc_client_t* client, const uint8_t* serialized);
|
||||
|
||||
RC_END_C_DECLS
|
||||
|
||||
#endif /* RC_RUNTIME_H */
|
|
@ -0,0 +1,85 @@
|
|||
#ifndef RC_CLIENT_RAINTEGRATION_H
|
||||
#define RC_CLIENT_RAINTEGRATION_H
|
||||
|
||||
#ifndef _WIN32
|
||||
#undef RC_CLIENT_SUPPORTS_RAINTEGRATION /* Windows required for RAIntegration */
|
||||
#endif
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "rc_export.h"
|
||||
|
||||
RC_BEGIN_C_DECLS
|
||||
|
||||
typedef struct rc_client_t rc_client_t; /* forward reference; in rc_client.h */
|
||||
|
||||
/* types needed to implement raintegration */
|
||||
|
||||
typedef struct rc_client_raintegration_menu_item_t {
|
||||
const char* label;
|
||||
uint32_t id;
|
||||
uint8_t checked;
|
||||
uint8_t enabled;
|
||||
} rc_client_raintegration_menu_item_t;
|
||||
|
||||
typedef struct rc_client_raintegration_menu_t {
|
||||
rc_client_raintegration_menu_item_t* items;
|
||||
uint32_t num_items;
|
||||
} rc_client_raintegration_menu_t;
|
||||
|
||||
enum {
|
||||
RC_CLIENT_RAINTEGRATION_EVENT_TYPE_NONE = 0,
|
||||
RC_CLIENT_RAINTEGRATION_EVENT_MENUITEM_CHECKED_CHANGED = 1, /* [menu_item] checked changed */
|
||||
RC_CLIENT_RAINTEGRATION_EVENT_HARDCORE_CHANGED = 2, /* hardcore was enabled or disabled */
|
||||
RC_CLIENT_RAINTEGRATION_EVENT_PAUSE = 3 /* emulated system should be paused */
|
||||
};
|
||||
|
||||
typedef struct rc_client_raintegration_event_t {
|
||||
uint32_t type;
|
||||
|
||||
const rc_client_raintegration_menu_item_t* menu_item;
|
||||
} rc_client_raintegration_event_t;
|
||||
|
||||
typedef void (RC_CCONV *rc_client_raintegration_event_handler_t)(const rc_client_raintegration_event_t* event,
|
||||
rc_client_t* client);
|
||||
|
||||
typedef void (RC_CCONV *rc_client_raintegration_write_memory_func_t)(uint32_t address, uint8_t* buffer,
|
||||
uint32_t num_bytes, rc_client_t* client);
|
||||
|
||||
/* types needed to integrate raintegration */
|
||||
|
||||
#ifdef RC_CLIENT_SUPPORTS_RAINTEGRATION
|
||||
|
||||
#ifndef RC_CLIENT_SUPPORTS_EXTERNAL
|
||||
#define RC_CLIENT_SUPPORTS_EXTERNAL /* external rc_client required for RAIntegration */
|
||||
#endif
|
||||
|
||||
#include <wtypes.h> /* HWND */
|
||||
|
||||
#include "rc_client.h"
|
||||
|
||||
RC_EXPORT rc_client_async_handle_t* RC_CCONV rc_client_begin_load_raintegration(rc_client_t* client,
|
||||
const wchar_t* search_directory, HWND main_window_handle,
|
||||
const char* client_name, const char* client_version,
|
||||
rc_client_callback_t callback, void* callback_userdata);
|
||||
|
||||
RC_EXPORT void RC_CCONV rc_client_unload_raintegration(rc_client_t* client);
|
||||
|
||||
RC_EXPORT void RC_CCONV rc_client_raintegration_update_main_window_handle(rc_client_t* client, HWND main_window_handle);
|
||||
|
||||
RC_EXPORT const rc_client_raintegration_menu_t* RC_CCONV rc_client_raintegration_get_menu(const rc_client_t* client);
|
||||
|
||||
RC_EXPORT void RC_CCONV rc_client_raintegration_rebuild_submenu(rc_client_t* client, HMENU hMenu);
|
||||
RC_EXPORT void RC_CCONV rc_client_raintegration_update_menu_item(const rc_client_t* client, const rc_client_raintegration_menu_item_t* menu_item);
|
||||
RC_EXPORT int RC_CCONV rc_client_raintegration_activate_menu_item(const rc_client_t* client, uint32_t nMenuItemId);
|
||||
|
||||
RC_EXPORT void RC_CCONV rc_client_raintegration_set_write_memory_function(rc_client_t* client, rc_client_raintegration_write_memory_func_t handler);
|
||||
|
||||
RC_EXPORT void RC_CCONV rc_client_raintegration_set_event_handler(rc_client_t* client,
|
||||
rc_client_raintegration_event_handler_t handler);
|
||||
|
||||
#endif /* RC_CLIENT_SUPPORTS_RAINTEGRATION */
|
||||
|
||||
RC_END_C_DECLS
|
||||
|
||||
#endif /* RC_CLIENT_RAINTEGRATION_H */
|
|
@ -0,0 +1,137 @@
|
|||
#ifndef RC_CONSOLES_H
|
||||
#define RC_CONSOLES_H
|
||||
|
||||
#include "rc_export.h"
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
RC_BEGIN_C_DECLS
|
||||
|
||||
/*****************************************************************************\
|
||||
| Console identifiers |
|
||||
\*****************************************************************************/
|
||||
|
||||
enum {
|
||||
RC_CONSOLE_UNKNOWN = 0,
|
||||
RC_CONSOLE_MEGA_DRIVE = 1,
|
||||
RC_CONSOLE_NINTENDO_64 = 2,
|
||||
RC_CONSOLE_SUPER_NINTENDO = 3,
|
||||
RC_CONSOLE_GAMEBOY = 4,
|
||||
RC_CONSOLE_GAMEBOY_ADVANCE = 5,
|
||||
RC_CONSOLE_GAMEBOY_COLOR = 6,
|
||||
RC_CONSOLE_NINTENDO = 7,
|
||||
RC_CONSOLE_PC_ENGINE = 8,
|
||||
RC_CONSOLE_SEGA_CD = 9,
|
||||
RC_CONSOLE_SEGA_32X = 10,
|
||||
RC_CONSOLE_MASTER_SYSTEM = 11,
|
||||
RC_CONSOLE_PLAYSTATION = 12,
|
||||
RC_CONSOLE_ATARI_LYNX = 13,
|
||||
RC_CONSOLE_NEOGEO_POCKET = 14,
|
||||
RC_CONSOLE_GAME_GEAR = 15,
|
||||
RC_CONSOLE_GAMECUBE = 16,
|
||||
RC_CONSOLE_ATARI_JAGUAR = 17,
|
||||
RC_CONSOLE_NINTENDO_DS = 18,
|
||||
RC_CONSOLE_WII = 19,
|
||||
RC_CONSOLE_WII_U = 20,
|
||||
RC_CONSOLE_PLAYSTATION_2 = 21,
|
||||
RC_CONSOLE_XBOX = 22,
|
||||
RC_CONSOLE_MAGNAVOX_ODYSSEY2 = 23,
|
||||
RC_CONSOLE_POKEMON_MINI = 24,
|
||||
RC_CONSOLE_ATARI_2600 = 25,
|
||||
RC_CONSOLE_MS_DOS = 26,
|
||||
RC_CONSOLE_ARCADE = 27,
|
||||
RC_CONSOLE_VIRTUAL_BOY = 28,
|
||||
RC_CONSOLE_MSX = 29,
|
||||
RC_CONSOLE_COMMODORE_64 = 30,
|
||||
RC_CONSOLE_ZX81 = 31,
|
||||
RC_CONSOLE_ORIC = 32,
|
||||
RC_CONSOLE_SG1000 = 33,
|
||||
RC_CONSOLE_VIC20 = 34,
|
||||
RC_CONSOLE_AMIGA = 35,
|
||||
RC_CONSOLE_ATARI_ST = 36,
|
||||
RC_CONSOLE_AMSTRAD_PC = 37,
|
||||
RC_CONSOLE_APPLE_II = 38,
|
||||
RC_CONSOLE_SATURN = 39,
|
||||
RC_CONSOLE_DREAMCAST = 40,
|
||||
RC_CONSOLE_PSP = 41,
|
||||
RC_CONSOLE_CDI = 42,
|
||||
RC_CONSOLE_3DO = 43,
|
||||
RC_CONSOLE_COLECOVISION = 44,
|
||||
RC_CONSOLE_INTELLIVISION = 45,
|
||||
RC_CONSOLE_VECTREX = 46,
|
||||
RC_CONSOLE_PC8800 = 47,
|
||||
RC_CONSOLE_PC9800 = 48,
|
||||
RC_CONSOLE_PCFX = 49,
|
||||
RC_CONSOLE_ATARI_5200 = 50,
|
||||
RC_CONSOLE_ATARI_7800 = 51,
|
||||
RC_CONSOLE_X68K = 52,
|
||||
RC_CONSOLE_WONDERSWAN = 53,
|
||||
RC_CONSOLE_CASSETTEVISION = 54,
|
||||
RC_CONSOLE_SUPER_CASSETTEVISION = 55,
|
||||
RC_CONSOLE_NEO_GEO_CD = 56,
|
||||
RC_CONSOLE_FAIRCHILD_CHANNEL_F = 57,
|
||||
RC_CONSOLE_FM_TOWNS = 58,
|
||||
RC_CONSOLE_ZX_SPECTRUM = 59,
|
||||
RC_CONSOLE_GAME_AND_WATCH = 60,
|
||||
RC_CONSOLE_NOKIA_NGAGE = 61,
|
||||
RC_CONSOLE_NINTENDO_3DS = 62,
|
||||
RC_CONSOLE_SUPERVISION = 63,
|
||||
RC_CONSOLE_SHARPX1 = 64,
|
||||
RC_CONSOLE_TIC80 = 65,
|
||||
RC_CONSOLE_THOMSONTO8 = 66,
|
||||
RC_CONSOLE_PC6000 = 67,
|
||||
RC_CONSOLE_PICO = 68,
|
||||
RC_CONSOLE_MEGADUCK = 69,
|
||||
RC_CONSOLE_ZEEBO = 70,
|
||||
RC_CONSOLE_ARDUBOY = 71,
|
||||
RC_CONSOLE_WASM4 = 72,
|
||||
RC_CONSOLE_ARCADIA_2001 = 73,
|
||||
RC_CONSOLE_INTERTON_VC_4000 = 74,
|
||||
RC_CONSOLE_ELEKTOR_TV_GAMES_COMPUTER = 75,
|
||||
RC_CONSOLE_PC_ENGINE_CD = 76,
|
||||
RC_CONSOLE_ATARI_JAGUAR_CD = 77,
|
||||
RC_CONSOLE_NINTENDO_DSI = 78,
|
||||
RC_CONSOLE_TI83 = 79,
|
||||
RC_CONSOLE_UZEBOX = 80,
|
||||
|
||||
RC_CONSOLE_HUBS = 100,
|
||||
RC_CONSOLE_EVENTS = 101,
|
||||
RC_CONSOLE_STANDALONE = 102
|
||||
};
|
||||
|
||||
RC_EXPORT const char* RC_CCONV rc_console_name(uint32_t console_id);
|
||||
|
||||
/*****************************************************************************\
|
||||
| Memory mapping |
|
||||
\*****************************************************************************/
|
||||
|
||||
enum {
|
||||
RC_MEMORY_TYPE_SYSTEM_RAM, /* normal system memory */
|
||||
RC_MEMORY_TYPE_SAVE_RAM, /* memory that persists between sessions */
|
||||
RC_MEMORY_TYPE_VIDEO_RAM, /* memory reserved for graphical processing */
|
||||
RC_MEMORY_TYPE_READONLY, /* memory that maps to read only data */
|
||||
RC_MEMORY_TYPE_HARDWARE_CONTROLLER, /* memory for interacting with system components */
|
||||
RC_MEMORY_TYPE_VIRTUAL_RAM, /* secondary address space that maps to real memory in system RAM */
|
||||
RC_MEMORY_TYPE_UNUSED /* these addresses don't really exist */
|
||||
};
|
||||
|
||||
typedef struct rc_memory_region_t {
|
||||
uint32_t start_address; /* first address of block as queried by RetroAchievements */
|
||||
uint32_t end_address; /* last address of block as queried by RetroAchievements */
|
||||
uint32_t real_address; /* real address for first address of block */
|
||||
uint8_t type; /* RC_MEMORY_TYPE_ for block */
|
||||
const char* description; /* short description of block */
|
||||
}
|
||||
rc_memory_region_t;
|
||||
|
||||
typedef struct rc_memory_regions_t {
|
||||
const rc_memory_region_t* region;
|
||||
uint32_t num_regions;
|
||||
}
|
||||
rc_memory_regions_t;
|
||||
|
||||
RC_EXPORT const rc_memory_regions_t* RC_CCONV rc_console_memory_regions(uint32_t console_id);
|
||||
|
||||
RC_END_C_DECLS
|
||||
|
||||
#endif /* RC_CONSOLES_H */
|
|
@ -0,0 +1,55 @@
|
|||
#ifndef RC_ERROR_H
|
||||
#define RC_ERROR_H
|
||||
|
||||
#include "rc_export.h"
|
||||
|
||||
RC_BEGIN_C_DECLS
|
||||
|
||||
/*****************************************************************************\
|
||||
| Return values |
|
||||
\*****************************************************************************/
|
||||
|
||||
enum {
|
||||
RC_OK = 0,
|
||||
RC_INVALID_LUA_OPERAND = -1,
|
||||
RC_INVALID_MEMORY_OPERAND = -2,
|
||||
RC_INVALID_CONST_OPERAND = -3,
|
||||
RC_INVALID_FP_OPERAND = -4,
|
||||
RC_INVALID_CONDITION_TYPE = -5,
|
||||
RC_INVALID_OPERATOR = -6,
|
||||
RC_INVALID_REQUIRED_HITS = -7,
|
||||
RC_DUPLICATED_START = -8,
|
||||
RC_DUPLICATED_CANCEL = -9,
|
||||
RC_DUPLICATED_SUBMIT = -10,
|
||||
RC_DUPLICATED_VALUE = -11,
|
||||
RC_DUPLICATED_PROGRESS = -12,
|
||||
RC_MISSING_START = -13,
|
||||
RC_MISSING_CANCEL = -14,
|
||||
RC_MISSING_SUBMIT = -15,
|
||||
RC_MISSING_VALUE = -16,
|
||||
RC_INVALID_LBOARD_FIELD = -17,
|
||||
RC_MISSING_DISPLAY_STRING = -18,
|
||||
RC_OUT_OF_MEMORY = -19,
|
||||
RC_INVALID_VALUE_FLAG = -20,
|
||||
RC_MISSING_VALUE_MEASURED = -21,
|
||||
RC_MULTIPLE_MEASURED = -22,
|
||||
RC_INVALID_MEASURED_TARGET = -23,
|
||||
RC_INVALID_COMPARISON = -24,
|
||||
RC_INVALID_STATE = -25,
|
||||
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,
|
||||
RC_ACCESS_DENIED = -33,
|
||||
RC_INVALID_CREDENTIALS = -34,
|
||||
RC_EXPIRED_TOKEN = -35
|
||||
};
|
||||
|
||||
RC_EXPORT const char* RC_CCONV rc_error_str(int ret);
|
||||
|
||||
RC_END_C_DECLS
|
||||
|
||||
#endif /* RC_ERROR_H */
|
|
@ -0,0 +1,100 @@
|
|||
#ifndef RC_EXPORT_H
|
||||
#define RC_EXPORT_H
|
||||
|
||||
/* These macros control how callbacks and public functions are defined */
|
||||
|
||||
/* RC_SHARED should be defined when building rcheevos as a shared library (e.g. dll/dylib/so). External code should not define this macro. */
|
||||
/* RC_STATIC should be defined when building rcheevos as a static library. External code should also define this macro. */
|
||||
/* RC_IMPORT should be defined for external code using rcheevos as a shared library. */
|
||||
|
||||
/* For compatibility, if none of these three macros are defined, then the build is assumed to be RC_STATIC */
|
||||
|
||||
#if !defined(RC_SHARED) && !defined(RC_STATIC) && !defined(RC_IMPORT)
|
||||
#define RC_STATIC
|
||||
#endif
|
||||
|
||||
#if (defined(RC_SHARED) && defined(RC_STATIC)) || (defined(RC_SHARED) && defined(RC_IMPORT)) || (defined(RC_STATIC) && defined(RC_IMPORT))
|
||||
#error RC_SHARED, RC_STATIC, and RC_IMPORT are mutually exclusive
|
||||
#endif
|
||||
|
||||
/* RC_BEGIN_C_DECLS and RC_END_C_DECLS should be used for all headers, to enforce C linkage and the C calling convention */
|
||||
/* RC_BEGIN_C_DECLS should be placed after #include's and before header declarations */
|
||||
/* RC_END_C_DECLS should be placed after header declarations */
|
||||
|
||||
/* example usage
|
||||
*
|
||||
* #ifndef RC_HEADER_H
|
||||
* #define RC_HEADER_H
|
||||
*
|
||||
* #include <stdint.h>
|
||||
*
|
||||
* RC_BEGIN_C_DECLS
|
||||
*
|
||||
* uint8_t rc_function(void);
|
||||
*
|
||||
* RC_END_C_DECLS
|
||||
*
|
||||
* #endif
|
||||
*/
|
||||
|
||||
#ifdef __cplusplus
|
||||
#define RC_BEGIN_C_DECLS extern "C" {
|
||||
#define RC_END_C_DECLS }
|
||||
#else
|
||||
#define RC_BEGIN_C_DECLS
|
||||
#define RC_END_C_DECLS
|
||||
#endif
|
||||
|
||||
/* RC_CCONV should be used for public functions and callbacks, to enforce the cdecl calling convention, if applicable */
|
||||
/* RC_CCONV should be placed after the return type, and between the ( and * for callbacks */
|
||||
|
||||
/* example usage */
|
||||
/* void RC_CCONV rc_function(void) */
|
||||
/* void (RC_CCONV *rc_callback)(void) */
|
||||
|
||||
#if defined(_WIN32)
|
||||
/* Windows compilers will ignore __cdecl when not applicable */
|
||||
#define RC_CCONV __cdecl
|
||||
#elif defined(__GNUC__) && defined(__i386__)
|
||||
/* GNU C compilers will warn if cdecl is defined on an unsupported platform */
|
||||
#define RC_CCONV __attribute__((cdecl))
|
||||
#else
|
||||
#define RC_CCONV
|
||||
#endif
|
||||
|
||||
/* RC_EXPORT should be used for public functions */
|
||||
/* RC_EXPORT will provide necessary hints for shared library usage, if applicable */
|
||||
/* RC_EXPORT should be placed before the return type */
|
||||
|
||||
/* example usage */
|
||||
/* RC_EXPORT void rc_function(void) */
|
||||
|
||||
#ifdef RC_SHARED
|
||||
#if defined(_WIN32)
|
||||
#define RC_EXPORT __declspec(dllexport)
|
||||
#elif defined(__GNUC__) && __GNUC__ >= 4
|
||||
#define RC_EXPORT __attribute__((visibility("default")))
|
||||
#else
|
||||
#define RC_EXPORT
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef RC_IMPORT
|
||||
#if defined(_WIN32)
|
||||
#define RC_EXPORT __declspec(dllimport)
|
||||
#elif defined(__GNUC__) && __GNUC__ >= 4
|
||||
#define RC_EXPORT __attribute__((visibility("default")))
|
||||
#else
|
||||
#define RC_EXPORT
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef RC_STATIC
|
||||
#if defined(__GNUC__) && __GNUC__ >= 4
|
||||
#define RC_EXPORT __attribute__((visibility("default")))
|
||||
#else
|
||||
#define RC_EXPORT
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#endif /* RC_EXPORT_H */
|
|
@ -0,0 +1,130 @@
|
|||
#ifndef RC_HASH_H
|
||||
#define RC_HASH_H
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "rc_consoles.h"
|
||||
|
||||
RC_BEGIN_C_DECLS
|
||||
|
||||
/* ===================================================== */
|
||||
|
||||
/* generates a hash from a block of memory.
|
||||
* returns non-zero on success, or zero on failure.
|
||||
*/
|
||||
RC_EXPORT int RC_CCONV rc_hash_generate_from_buffer(char hash[33], uint32_t console_id, const uint8_t* buffer, size_t buffer_size);
|
||||
|
||||
/* generates a hash from a file.
|
||||
* returns non-zero on success, or zero on failure.
|
||||
*/
|
||||
RC_EXPORT int RC_CCONV rc_hash_generate_from_file(char hash[33], uint32_t console_id, const char* path);
|
||||
|
||||
/* ===================================================== */
|
||||
|
||||
/* data for rc_hash_iterate
|
||||
*/
|
||||
typedef struct rc_hash_iterator
|
||||
{
|
||||
const uint8_t* buffer;
|
||||
size_t buffer_size;
|
||||
uint8_t consoles[12];
|
||||
int index;
|
||||
const char* path;
|
||||
} rc_hash_iterator_t;
|
||||
|
||||
/* initializes a rc_hash_iterator
|
||||
* - path must be provided
|
||||
* - if buffer and buffer_size are provided, path may be a filename (i.e. for something extracted from a zip file)
|
||||
*/
|
||||
RC_EXPORT void RC_CCONV 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
|
||||
*/
|
||||
RC_EXPORT void RC_CCONV rc_hash_destroy_iterator(struct rc_hash_iterator* iterator);
|
||||
|
||||
/* generates the next hash for the data in the rc_hash_iterator.
|
||||
* returns non-zero if a hash was generated, or zero if no more hashes can be generated for the data.
|
||||
*/
|
||||
RC_EXPORT int RC_CCONV rc_hash_iterate(char hash[33], struct rc_hash_iterator* iterator);
|
||||
|
||||
/* ===================================================== */
|
||||
|
||||
/* specifies a function to call when an error occurs to display the error message */
|
||||
typedef void (RC_CCONV *rc_hash_message_callback)(const char*);
|
||||
RC_EXPORT void RC_CCONV rc_hash_init_error_message_callback(rc_hash_message_callback callback);
|
||||
|
||||
/* specifies a function to call for verbose logging */
|
||||
RC_EXPORT void rc_hash_init_verbose_message_callback(rc_hash_message_callback callback);
|
||||
|
||||
/* ===================================================== */
|
||||
|
||||
/* opens a file */
|
||||
typedef void* (RC_CCONV *rc_hash_filereader_open_file_handler)(const char* path_utf8);
|
||||
|
||||
/* moves the file pointer - standard fseek parameters */
|
||||
typedef void (RC_CCONV *rc_hash_filereader_seek_handler)(void* file_handle, int64_t offset, int origin);
|
||||
|
||||
/* locates the file pointer */
|
||||
typedef int64_t (RC_CCONV *rc_hash_filereader_tell_handler)(void* file_handle);
|
||||
|
||||
/* reads the specified number of bytes from the file starting at the read pointer.
|
||||
* returns the number of bytes actually read.
|
||||
*/
|
||||
typedef size_t (RC_CCONV *rc_hash_filereader_read_handler)(void* file_handle, void* buffer, size_t requested_bytes);
|
||||
|
||||
/* closes the file */
|
||||
typedef void (RC_CCONV *rc_hash_filereader_close_file_handler)(void* file_handle);
|
||||
|
||||
struct rc_hash_filereader
|
||||
{
|
||||
rc_hash_filereader_open_file_handler open;
|
||||
rc_hash_filereader_seek_handler seek;
|
||||
rc_hash_filereader_tell_handler tell;
|
||||
rc_hash_filereader_read_handler read;
|
||||
rc_hash_filereader_close_file_handler close;
|
||||
};
|
||||
|
||||
RC_EXPORT void RC_CCONV rc_hash_init_custom_filereader(struct rc_hash_filereader* reader);
|
||||
|
||||
/* ===================================================== */
|
||||
|
||||
#define RC_HASH_CDTRACK_FIRST_DATA ((uint32_t)-1) /* the first data track (skip audio tracks) */
|
||||
#define RC_HASH_CDTRACK_LAST ((uint32_t)-2) /* the last data/audio track */
|
||||
#define RC_HASH_CDTRACK_LARGEST ((uint32_t)-3) /* the largest data/audio track */
|
||||
#define RC_HASH_CDTRACK_FIRST_OF_SECOND_SESSION ((uint32_t)-4) /* the first data/audio track of the second session */
|
||||
|
||||
/* opens a track from the specified file. see the RC_HASH_CDTRACK_ defines for special tracks.
|
||||
* returns a handle to be passed to the other functions, or NULL if the track could not be opened.
|
||||
*/
|
||||
typedef void* (RC_CCONV *rc_hash_cdreader_open_track_handler)(const char* path, uint32_t track);
|
||||
|
||||
/* attempts to read the specified number of bytes from the file starting at the specified absolute sector.
|
||||
* returns the number of bytes actually read.
|
||||
*/
|
||||
typedef size_t (RC_CCONV *rc_hash_cdreader_read_sector_handler)(void* track_handle, uint32_t sector, void* buffer, size_t requested_bytes);
|
||||
|
||||
/* closes the track handle */
|
||||
typedef void (RC_CCONV *rc_hash_cdreader_close_track_handler)(void* track_handle);
|
||||
|
||||
/* gets the absolute sector index for the first sector of a track */
|
||||
typedef uint32_t(RC_CCONV *rc_hash_cdreader_first_track_sector_handler)(void* track_handle);
|
||||
|
||||
struct rc_hash_cdreader
|
||||
{
|
||||
rc_hash_cdreader_open_track_handler open_track;
|
||||
rc_hash_cdreader_read_sector_handler read_sector;
|
||||
rc_hash_cdreader_close_track_handler close_track;
|
||||
rc_hash_cdreader_first_track_sector_handler first_track_sector;
|
||||
};
|
||||
|
||||
RC_EXPORT void RC_CCONV rc_hash_get_default_cdreader(struct rc_hash_cdreader* cdreader);
|
||||
RC_EXPORT void RC_CCONV rc_hash_init_default_cdreader(void);
|
||||
RC_EXPORT void RC_CCONV rc_hash_init_custom_cdreader(struct rc_hash_cdreader* reader);
|
||||
|
||||
/* ===================================================== */
|
||||
|
||||
RC_END_C_DECLS
|
||||
|
||||
#endif /* RC_HASH_H */
|
|
@ -0,0 +1,152 @@
|
|||
#ifndef RC_RUNTIME_H
|
||||
#define RC_RUNTIME_H
|
||||
|
||||
#include "rc_error.h"
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
RC_BEGIN_C_DECLS
|
||||
|
||||
/*****************************************************************************\
|
||||
| Forward Declarations (defined in rc_runtime_types.h) |
|
||||
\*****************************************************************************/
|
||||
|
||||
#ifndef RC_RUNTIME_TYPES_H /* prevents pedantic redefinition error */
|
||||
|
||||
typedef struct lua_State lua_State;
|
||||
|
||||
typedef struct rc_trigger_t rc_trigger_t;
|
||||
typedef struct rc_lboard_t rc_lboard_t;
|
||||
typedef struct rc_richpresence_t rc_richpresence_t;
|
||||
typedef struct rc_memref_t rc_memref_t;
|
||||
typedef struct rc_value_t rc_value_t;
|
||||
|
||||
#endif
|
||||
|
||||
/*****************************************************************************\
|
||||
| Callbacks |
|
||||
\*****************************************************************************/
|
||||
|
||||
/**
|
||||
* Callback used to read num_bytes bytes from memory starting at address. If
|
||||
* num_bytes is greater than 1, the value is read in little-endian from
|
||||
* memory.
|
||||
*/
|
||||
typedef uint32_t(RC_CCONV *rc_runtime_peek_t)(uint32_t address, uint32_t num_bytes, void* ud);
|
||||
|
||||
/*****************************************************************************\
|
||||
| Runtime |
|
||||
\*****************************************************************************/
|
||||
|
||||
typedef struct rc_runtime_trigger_t {
|
||||
uint32_t id;
|
||||
rc_trigger_t* trigger;
|
||||
void* buffer;
|
||||
rc_memref_t* invalid_memref;
|
||||
uint8_t md5[16];
|
||||
int32_t serialized_size;
|
||||
uint8_t owns_memrefs;
|
||||
}
|
||||
rc_runtime_trigger_t;
|
||||
|
||||
typedef struct rc_runtime_lboard_t {
|
||||
uint32_t id;
|
||||
int32_t value;
|
||||
rc_lboard_t* lboard;
|
||||
void* buffer;
|
||||
rc_memref_t* invalid_memref;
|
||||
uint8_t md5[16];
|
||||
uint32_t serialized_size;
|
||||
uint8_t owns_memrefs;
|
||||
}
|
||||
rc_runtime_lboard_t;
|
||||
|
||||
typedef struct rc_runtime_richpresence_t {
|
||||
rc_richpresence_t* richpresence;
|
||||
void* buffer;
|
||||
struct rc_runtime_richpresence_t* previous;
|
||||
uint8_t md5[16];
|
||||
uint8_t owns_memrefs;
|
||||
}
|
||||
rc_runtime_richpresence_t;
|
||||
|
||||
typedef struct rc_runtime_t {
|
||||
rc_runtime_trigger_t* triggers;
|
||||
uint32_t trigger_count;
|
||||
uint32_t trigger_capacity;
|
||||
|
||||
rc_runtime_lboard_t* lboards;
|
||||
uint32_t lboard_count;
|
||||
uint32_t lboard_capacity;
|
||||
|
||||
rc_runtime_richpresence_t* richpresence;
|
||||
|
||||
rc_memref_t* memrefs;
|
||||
rc_memref_t** next_memref;
|
||||
|
||||
rc_value_t* variables;
|
||||
rc_value_t** next_variable;
|
||||
|
||||
uint8_t owns_self;
|
||||
}
|
||||
rc_runtime_t;
|
||||
|
||||
RC_EXPORT rc_runtime_t* RC_CCONV rc_runtime_alloc(void);
|
||||
RC_EXPORT void RC_CCONV rc_runtime_init(rc_runtime_t* runtime);
|
||||
RC_EXPORT void RC_CCONV rc_runtime_destroy(rc_runtime_t* runtime);
|
||||
|
||||
RC_EXPORT int RC_CCONV rc_runtime_activate_achievement(rc_runtime_t* runtime, uint32_t id, const char* memaddr, lua_State* L, int funcs_idx);
|
||||
RC_EXPORT void RC_CCONV rc_runtime_deactivate_achievement(rc_runtime_t* runtime, uint32_t id);
|
||||
RC_EXPORT rc_trigger_t* RC_CCONV rc_runtime_get_achievement(const rc_runtime_t* runtime, uint32_t id);
|
||||
RC_EXPORT int RC_CCONV rc_runtime_get_achievement_measured(const rc_runtime_t* runtime, uint32_t id, unsigned* measured_value, unsigned* measured_target);
|
||||
RC_EXPORT int RC_CCONV rc_runtime_format_achievement_measured(const rc_runtime_t* runtime, uint32_t id, char *buffer, size_t buffer_size);
|
||||
|
||||
RC_EXPORT int RC_CCONV rc_runtime_activate_lboard(rc_runtime_t* runtime, uint32_t id, const char* memaddr, lua_State* L, int funcs_idx);
|
||||
RC_EXPORT void RC_CCONV rc_runtime_deactivate_lboard(rc_runtime_t* runtime, uint32_t id);
|
||||
RC_EXPORT rc_lboard_t* RC_CCONV rc_runtime_get_lboard(const rc_runtime_t* runtime, uint32_t id);
|
||||
RC_EXPORT int RC_CCONV rc_runtime_format_lboard_value(char* buffer, int size, int32_t value, int format);
|
||||
|
||||
|
||||
RC_EXPORT int RC_CCONV rc_runtime_activate_richpresence(rc_runtime_t* runtime, const char* script, lua_State* L, int funcs_idx);
|
||||
RC_EXPORT int RC_CCONV rc_runtime_get_richpresence(const rc_runtime_t* runtime, char* buffer, size_t buffersize, rc_runtime_peek_t peek, void* peek_ud, lua_State* L);
|
||||
|
||||
enum {
|
||||
RC_RUNTIME_EVENT_ACHIEVEMENT_ACTIVATED, /* from WAITING, PAUSED, or PRIMED to ACTIVE */
|
||||
RC_RUNTIME_EVENT_ACHIEVEMENT_PAUSED,
|
||||
RC_RUNTIME_EVENT_ACHIEVEMENT_RESET,
|
||||
RC_RUNTIME_EVENT_ACHIEVEMENT_TRIGGERED,
|
||||
RC_RUNTIME_EVENT_ACHIEVEMENT_PRIMED,
|
||||
RC_RUNTIME_EVENT_LBOARD_STARTED,
|
||||
RC_RUNTIME_EVENT_LBOARD_CANCELED,
|
||||
RC_RUNTIME_EVENT_LBOARD_UPDATED,
|
||||
RC_RUNTIME_EVENT_LBOARD_TRIGGERED,
|
||||
RC_RUNTIME_EVENT_ACHIEVEMENT_DISABLED,
|
||||
RC_RUNTIME_EVENT_LBOARD_DISABLED,
|
||||
RC_RUNTIME_EVENT_ACHIEVEMENT_UNPRIMED,
|
||||
RC_RUNTIME_EVENT_ACHIEVEMENT_PROGRESS_UPDATED
|
||||
};
|
||||
|
||||
typedef struct rc_runtime_event_t {
|
||||
uint32_t id;
|
||||
int32_t value;
|
||||
uint8_t type;
|
||||
}
|
||||
rc_runtime_event_t;
|
||||
|
||||
typedef void (RC_CCONV *rc_runtime_event_handler_t)(const rc_runtime_event_t* runtime_event);
|
||||
|
||||
RC_EXPORT void RC_CCONV rc_runtime_do_frame(rc_runtime_t* runtime, rc_runtime_event_handler_t event_handler, rc_runtime_peek_t peek, void* ud, lua_State* L);
|
||||
RC_EXPORT void RC_CCONV rc_runtime_reset(rc_runtime_t* runtime);
|
||||
|
||||
typedef int (RC_CCONV *rc_runtime_validate_address_t)(uint32_t address);
|
||||
RC_EXPORT void RC_CCONV rc_runtime_validate_addresses(rc_runtime_t* runtime, rc_runtime_event_handler_t event_handler, rc_runtime_validate_address_t validate_handler);
|
||||
RC_EXPORT void RC_CCONV rc_runtime_invalidate_address(rc_runtime_t* runtime, uint32_t address);
|
||||
|
||||
RC_EXPORT int RC_CCONV rc_runtime_progress_size(const rc_runtime_t* runtime, lua_State* L);
|
||||
RC_EXPORT int RC_CCONV rc_runtime_serialize_progress(void* buffer, const rc_runtime_t* runtime, lua_State* L);
|
||||
RC_EXPORT int RC_CCONV rc_runtime_deserialize_progress(rc_runtime_t* runtime, const uint8_t* serialized, lua_State* L);
|
||||
|
||||
RC_END_C_DECLS
|
||||
|
||||
#endif /* RC_RUNTIME_H */
|
|
@ -0,0 +1,429 @@
|
|||
#ifndef RC_RUNTIME_TYPES_H
|
||||
#define RC_RUNTIME_TYPES_H
|
||||
|
||||
#include "rc_error.h"
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
RC_BEGIN_C_DECLS
|
||||
|
||||
#ifndef RC_RUNTIME_H /* prevents pedantic redefiniton error */
|
||||
|
||||
typedef struct lua_State lua_State;
|
||||
|
||||
typedef struct rc_trigger_t rc_trigger_t;
|
||||
typedef struct rc_lboard_t rc_lboard_t;
|
||||
typedef struct rc_richpresence_t rc_richpresence_t;
|
||||
typedef struct rc_memref_t rc_memref_t;
|
||||
typedef struct rc_value_t rc_value_t;
|
||||
|
||||
#endif
|
||||
|
||||
/*****************************************************************************\
|
||||
| Callbacks |
|
||||
\*****************************************************************************/
|
||||
|
||||
/**
|
||||
* Callback used to read num_bytes bytes from memory starting at address. If
|
||||
* num_bytes is greater than 1, the value is read in little-endian from
|
||||
* memory.
|
||||
*/
|
||||
typedef uint32_t(RC_CCONV *rc_peek_t)(uint32_t address, uint32_t num_bytes, void* ud);
|
||||
|
||||
/*****************************************************************************\
|
||||
| Memory References |
|
||||
\*****************************************************************************/
|
||||
|
||||
/* Sizes. */
|
||||
enum {
|
||||
RC_MEMSIZE_8_BITS,
|
||||
RC_MEMSIZE_16_BITS,
|
||||
RC_MEMSIZE_24_BITS,
|
||||
RC_MEMSIZE_32_BITS,
|
||||
RC_MEMSIZE_LOW,
|
||||
RC_MEMSIZE_HIGH,
|
||||
RC_MEMSIZE_BIT_0,
|
||||
RC_MEMSIZE_BIT_1,
|
||||
RC_MEMSIZE_BIT_2,
|
||||
RC_MEMSIZE_BIT_3,
|
||||
RC_MEMSIZE_BIT_4,
|
||||
RC_MEMSIZE_BIT_5,
|
||||
RC_MEMSIZE_BIT_6,
|
||||
RC_MEMSIZE_BIT_7,
|
||||
RC_MEMSIZE_BITCOUNT,
|
||||
RC_MEMSIZE_16_BITS_BE,
|
||||
RC_MEMSIZE_24_BITS_BE,
|
||||
RC_MEMSIZE_32_BITS_BE,
|
||||
RC_MEMSIZE_FLOAT,
|
||||
RC_MEMSIZE_MBF32,
|
||||
RC_MEMSIZE_MBF32_LE,
|
||||
RC_MEMSIZE_FLOAT_BE,
|
||||
RC_MEMSIZE_VARIABLE
|
||||
};
|
||||
|
||||
typedef struct rc_memref_value_t {
|
||||
/* The current value of this memory reference. */
|
||||
uint32_t value;
|
||||
/* The last differing value of this memory reference. */
|
||||
uint32_t prior;
|
||||
|
||||
/* The size of the value. */
|
||||
uint8_t size;
|
||||
/* True if the value changed this frame. */
|
||||
uint8_t changed;
|
||||
/* The value type of the value (for variables) */
|
||||
uint8_t type;
|
||||
/* True if the reference will be used in indirection.
|
||||
* NOTE: This is actually a property of the rc_memref_t, but we put it here to save space */
|
||||
uint8_t is_indirect;
|
||||
}
|
||||
rc_memref_value_t;
|
||||
|
||||
struct rc_memref_t {
|
||||
/* The current value at the specified memory address. */
|
||||
rc_memref_value_t value;
|
||||
|
||||
/* The memory address of this variable. */
|
||||
uint32_t address;
|
||||
|
||||
/* The next memory reference in the chain. */
|
||||
rc_memref_t* next;
|
||||
};
|
||||
|
||||
/*****************************************************************************\
|
||||
| Operands |
|
||||
\*****************************************************************************/
|
||||
|
||||
/* types */
|
||||
enum {
|
||||
RC_OPERAND_ADDRESS, /* The value of a live address in RAM. */
|
||||
RC_OPERAND_DELTA, /* The value last known at this address. */
|
||||
RC_OPERAND_CONST, /* A 32-bit unsigned integer. */
|
||||
RC_OPERAND_FP, /* A floating point value. */
|
||||
RC_OPERAND_LUA, /* A Lua function that provides the value. */
|
||||
RC_OPERAND_PRIOR, /* The last differing value at this address. */
|
||||
RC_OPERAND_BCD, /* The BCD-decoded value of a live address in RAM. */
|
||||
RC_OPERAND_INVERTED /* The twos-complement value of a live address in RAM. */
|
||||
};
|
||||
|
||||
typedef struct rc_operand_t {
|
||||
union {
|
||||
/* A value read from memory. */
|
||||
rc_memref_t* memref;
|
||||
|
||||
/* An integer value. */
|
||||
uint32_t num;
|
||||
|
||||
/* A floating point value. */
|
||||
double dbl;
|
||||
|
||||
/* A reference to the Lua function that provides the value. */
|
||||
int luafunc;
|
||||
} value;
|
||||
|
||||
/* specifies which member of the value union is being used */
|
||||
uint8_t type;
|
||||
|
||||
/* the actual RC_MEMSIZE of the operand - memref.size may differ */
|
||||
uint8_t size;
|
||||
}
|
||||
rc_operand_t;
|
||||
|
||||
RC_EXPORT int RC_CCONV rc_operand_is_memref(const rc_operand_t* operand);
|
||||
|
||||
/*****************************************************************************\
|
||||
| Conditions |
|
||||
\*****************************************************************************/
|
||||
|
||||
/* types */
|
||||
enum {
|
||||
/* NOTE: this enum is ordered to optimize the switch statements in rc_test_condset_internal. the values may change between releases */
|
||||
|
||||
/* non-combining conditions (third switch) */
|
||||
RC_CONDITION_STANDARD, /* this should always be 0 */
|
||||
RC_CONDITION_PAUSE_IF,
|
||||
RC_CONDITION_RESET_IF,
|
||||
RC_CONDITION_MEASURED_IF,
|
||||
RC_CONDITION_TRIGGER,
|
||||
RC_CONDITION_MEASURED, /* measured also appears in the first switch, so place it at the border between them */
|
||||
|
||||
/* modifiers (first switch) */
|
||||
RC_CONDITION_ADD_SOURCE, /* everything from this point on affects the condition after it */
|
||||
RC_CONDITION_SUB_SOURCE,
|
||||
RC_CONDITION_ADD_ADDRESS,
|
||||
|
||||
/* logic flags (second switch) */
|
||||
RC_CONDITION_ADD_HITS,
|
||||
RC_CONDITION_SUB_HITS,
|
||||
RC_CONDITION_RESET_NEXT_IF,
|
||||
RC_CONDITION_AND_NEXT,
|
||||
RC_CONDITION_OR_NEXT
|
||||
};
|
||||
|
||||
/* operators */
|
||||
enum {
|
||||
RC_OPERATOR_EQ,
|
||||
RC_OPERATOR_LT,
|
||||
RC_OPERATOR_LE,
|
||||
RC_OPERATOR_GT,
|
||||
RC_OPERATOR_GE,
|
||||
RC_OPERATOR_NE,
|
||||
RC_OPERATOR_NONE,
|
||||
RC_OPERATOR_MULT,
|
||||
RC_OPERATOR_DIV,
|
||||
RC_OPERATOR_AND,
|
||||
RC_OPERATOR_XOR
|
||||
};
|
||||
|
||||
typedef struct rc_condition_t rc_condition_t;
|
||||
|
||||
struct rc_condition_t {
|
||||
/* The condition's operands. */
|
||||
rc_operand_t operand1;
|
||||
rc_operand_t operand2;
|
||||
|
||||
/* Required hits to fire this condition. */
|
||||
uint32_t required_hits;
|
||||
/* Number of hits so far. */
|
||||
uint32_t current_hits;
|
||||
|
||||
/* The next condition in the chain. */
|
||||
rc_condition_t* next;
|
||||
|
||||
/* The type of the condition. (RC_CONDITION_*) */
|
||||
uint8_t type;
|
||||
|
||||
/* The comparison operator to use. (RC_OPERATOR_*) */
|
||||
uint8_t oper; /* operator is a reserved word in C++. */
|
||||
|
||||
/* Set if the condition needs to processed as part of the "check if paused" pass. (bool) */
|
||||
uint8_t pause;
|
||||
|
||||
/* Whether or not the condition evaluated true on the last check. (bool) */
|
||||
uint8_t is_true;
|
||||
|
||||
/* Unique identifier of optimized comparator to use. (RC_PROCESSING_COMPARE_*) */
|
||||
uint8_t optimized_comparator;
|
||||
};
|
||||
|
||||
/*****************************************************************************\
|
||||
| Condition sets |
|
||||
\*****************************************************************************/
|
||||
|
||||
typedef struct rc_condset_t rc_condset_t;
|
||||
|
||||
struct rc_condset_t {
|
||||
/* The next condition set in the chain. */
|
||||
rc_condset_t* next;
|
||||
|
||||
/* The list of conditions in this condition set. */
|
||||
rc_condition_t* conditions;
|
||||
|
||||
/* True if any condition in the set is a pause condition. */
|
||||
uint8_t has_pause;
|
||||
|
||||
/* True if the set is currently paused. */
|
||||
uint8_t is_paused;
|
||||
|
||||
/* True if the set has indirect memory references. */
|
||||
uint8_t has_indirect_memrefs;
|
||||
};
|
||||
|
||||
/*****************************************************************************\
|
||||
| Trigger |
|
||||
\*****************************************************************************/
|
||||
|
||||
enum {
|
||||
RC_TRIGGER_STATE_INACTIVE, /* achievement is not being processed */
|
||||
RC_TRIGGER_STATE_WAITING, /* achievement cannot trigger until it has been false for at least one frame */
|
||||
RC_TRIGGER_STATE_ACTIVE, /* achievement is active and may trigger */
|
||||
RC_TRIGGER_STATE_PAUSED, /* achievement is currently paused and will not trigger */
|
||||
RC_TRIGGER_STATE_RESET, /* achievement hit counts were reset */
|
||||
RC_TRIGGER_STATE_TRIGGERED, /* achievement has triggered */
|
||||
RC_TRIGGER_STATE_PRIMED, /* all non-Trigger conditions are true */
|
||||
RC_TRIGGER_STATE_DISABLED /* achievement cannot be processed at this time */
|
||||
};
|
||||
|
||||
struct rc_trigger_t {
|
||||
/* The main condition set. */
|
||||
rc_condset_t* requirement;
|
||||
|
||||
/* The list of sub condition sets in this test. */
|
||||
rc_condset_t* alternative;
|
||||
|
||||
/* The memory references required by the trigger. */
|
||||
rc_memref_t* memrefs;
|
||||
|
||||
/* The current state of the MEASURED condition. */
|
||||
uint32_t measured_value;
|
||||
|
||||
/* The target state of the MEASURED condition */
|
||||
uint32_t measured_target;
|
||||
|
||||
/* The current state of the trigger */
|
||||
uint8_t state;
|
||||
|
||||
/* True if at least one condition has a non-zero hit count */
|
||||
uint8_t has_hits;
|
||||
|
||||
/* True if at least one condition has a non-zero required hit count */
|
||||
uint8_t has_required_hits;
|
||||
|
||||
/* True if the measured value should be displayed as a percentage */
|
||||
uint8_t measured_as_percent;
|
||||
};
|
||||
|
||||
RC_EXPORT int RC_CCONV rc_trigger_size(const char* memaddr);
|
||||
RC_EXPORT rc_trigger_t* RC_CCONV rc_parse_trigger(void* buffer, const char* memaddr, lua_State* L, int funcs_ndx);
|
||||
RC_EXPORT int RC_CCONV rc_evaluate_trigger(rc_trigger_t* trigger, rc_peek_t peek, void* ud, lua_State* L);
|
||||
RC_EXPORT int RC_CCONV rc_test_trigger(rc_trigger_t* trigger, rc_peek_t peek, void* ud, lua_State* L);
|
||||
RC_EXPORT void RC_CCONV rc_reset_trigger(rc_trigger_t* self);
|
||||
|
||||
/*****************************************************************************\
|
||||
| Values |
|
||||
\*****************************************************************************/
|
||||
|
||||
struct rc_value_t {
|
||||
/* The current value of the variable. */
|
||||
rc_memref_value_t value;
|
||||
|
||||
/* The list of conditions to evaluate. */
|
||||
rc_condset_t* conditions;
|
||||
|
||||
/* The memory references required by the variable. */
|
||||
rc_memref_t* memrefs;
|
||||
|
||||
/* The name of the variable. */
|
||||
const char* name;
|
||||
|
||||
/* The next variable in the chain. */
|
||||
rc_value_t* next;
|
||||
};
|
||||
|
||||
RC_EXPORT int RC_CCONV rc_value_size(const char* memaddr);
|
||||
RC_EXPORT rc_value_t* RC_CCONV rc_parse_value(void* buffer, const char* memaddr, lua_State* L, int funcs_ndx);
|
||||
RC_EXPORT int32_t RC_CCONV rc_evaluate_value(rc_value_t* value, rc_peek_t peek, void* ud, lua_State* L);
|
||||
|
||||
/*****************************************************************************\
|
||||
| Leaderboards |
|
||||
\*****************************************************************************/
|
||||
|
||||
/* Return values for rc_evaluate_lboard. */
|
||||
enum {
|
||||
RC_LBOARD_STATE_INACTIVE, /* leaderboard is not being processed */
|
||||
RC_LBOARD_STATE_WAITING, /* leaderboard cannot activate until the start condition has been false for at least one frame */
|
||||
RC_LBOARD_STATE_ACTIVE, /* leaderboard is active and may start */
|
||||
RC_LBOARD_STATE_STARTED, /* leaderboard attempt in progress */
|
||||
RC_LBOARD_STATE_CANCELED, /* leaderboard attempt canceled */
|
||||
RC_LBOARD_STATE_TRIGGERED, /* leaderboard attempt complete, value should be submitted */
|
||||
RC_LBOARD_STATE_DISABLED /* leaderboard cannot be processed at this time */
|
||||
};
|
||||
|
||||
struct rc_lboard_t {
|
||||
rc_trigger_t start;
|
||||
rc_trigger_t submit;
|
||||
rc_trigger_t cancel;
|
||||
rc_value_t value;
|
||||
rc_value_t* progress;
|
||||
rc_memref_t* memrefs;
|
||||
|
||||
uint8_t state;
|
||||
};
|
||||
|
||||
RC_EXPORT int RC_CCONV rc_lboard_size(const char* memaddr);
|
||||
RC_EXPORT rc_lboard_t* RC_CCONV rc_parse_lboard(void* buffer, const char* memaddr, lua_State* L, int funcs_ndx);
|
||||
RC_EXPORT int RC_CCONV rc_evaluate_lboard(rc_lboard_t* lboard, int32_t* value, rc_peek_t peek, void* peek_ud, lua_State* L);
|
||||
RC_EXPORT void RC_CCONV rc_reset_lboard(rc_lboard_t* lboard);
|
||||
|
||||
/*****************************************************************************\
|
||||
| Value formatting |
|
||||
\*****************************************************************************/
|
||||
|
||||
/* Supported formats. */
|
||||
enum {
|
||||
RC_FORMAT_FRAMES,
|
||||
RC_FORMAT_SECONDS,
|
||||
RC_FORMAT_CENTISECS,
|
||||
RC_FORMAT_SCORE,
|
||||
RC_FORMAT_VALUE,
|
||||
RC_FORMAT_MINUTES,
|
||||
RC_FORMAT_SECONDS_AS_MINUTES,
|
||||
RC_FORMAT_FLOAT1,
|
||||
RC_FORMAT_FLOAT2,
|
||||
RC_FORMAT_FLOAT3,
|
||||
RC_FORMAT_FLOAT4,
|
||||
RC_FORMAT_FLOAT5,
|
||||
RC_FORMAT_FLOAT6,
|
||||
RC_FORMAT_FIXED1,
|
||||
RC_FORMAT_FIXED2,
|
||||
RC_FORMAT_FIXED3,
|
||||
RC_FORMAT_TENS,
|
||||
RC_FORMAT_HUNDREDS,
|
||||
RC_FORMAT_THOUSANDS,
|
||||
RC_FORMAT_UNSIGNED_VALUE
|
||||
};
|
||||
|
||||
RC_EXPORT int RC_CCONV rc_parse_format(const char* format_str);
|
||||
RC_EXPORT int RC_CCONV rc_format_value(char* buffer, int size, int32_t value, int format);
|
||||
|
||||
/*****************************************************************************\
|
||||
| Rich Presence |
|
||||
\*****************************************************************************/
|
||||
|
||||
typedef struct rc_richpresence_lookup_item_t rc_richpresence_lookup_item_t;
|
||||
|
||||
struct rc_richpresence_lookup_item_t {
|
||||
uint32_t first;
|
||||
uint32_t last;
|
||||
rc_richpresence_lookup_item_t* left;
|
||||
rc_richpresence_lookup_item_t* right;
|
||||
const char* label;
|
||||
};
|
||||
|
||||
typedef struct rc_richpresence_lookup_t rc_richpresence_lookup_t;
|
||||
|
||||
struct rc_richpresence_lookup_t {
|
||||
rc_richpresence_lookup_item_t* root;
|
||||
rc_richpresence_lookup_t* next;
|
||||
const char* name;
|
||||
const char* default_label;
|
||||
uint8_t format;
|
||||
};
|
||||
|
||||
typedef struct rc_richpresence_display_part_t rc_richpresence_display_part_t;
|
||||
|
||||
struct rc_richpresence_display_part_t {
|
||||
rc_richpresence_display_part_t* next;
|
||||
const char* text;
|
||||
rc_richpresence_lookup_t* lookup;
|
||||
rc_memref_value_t *value;
|
||||
uint8_t display_type;
|
||||
};
|
||||
|
||||
typedef struct rc_richpresence_display_t rc_richpresence_display_t;
|
||||
|
||||
struct rc_richpresence_display_t {
|
||||
rc_trigger_t trigger;
|
||||
rc_richpresence_display_t* next;
|
||||
rc_richpresence_display_part_t* display;
|
||||
};
|
||||
|
||||
struct rc_richpresence_t {
|
||||
rc_richpresence_display_t* first_display;
|
||||
rc_richpresence_lookup_t* first_lookup;
|
||||
rc_memref_t* memrefs;
|
||||
rc_value_t* variables;
|
||||
};
|
||||
|
||||
RC_EXPORT int RC_CCONV rc_richpresence_size(const char* script);
|
||||
RC_EXPORT int RC_CCONV rc_richpresence_size_lines(const char* script, int* lines_read);
|
||||
RC_EXPORT rc_richpresence_t* RC_CCONV rc_parse_richpresence(void* buffer, const char* script, lua_State* L, int funcs_ndx);
|
||||
RC_EXPORT int RC_CCONV rc_evaluate_richpresence(rc_richpresence_t* richpresence, char* buffer, size_t buffersize, rc_peek_t peek, void* peek_ud, lua_State* L);
|
||||
RC_EXPORT void RC_CCONV rc_update_richpresence(rc_richpresence_t* richpresence, rc_peek_t peek, void* peek_ud, lua_State* L);
|
||||
RC_EXPORT int RC_CCONV rc_get_richpresence_display_string(rc_richpresence_t* richpresence, char* buffer, size_t buffersize, rc_peek_t peek, void* peek_ud, lua_State* L);
|
||||
RC_EXPORT void RC_CCONV rc_reset_richpresence(rc_richpresence_t* self);
|
||||
|
||||
RC_END_C_DECLS
|
||||
|
||||
#endif /* RC_RUNTIME_TYPES_H */
|
|
@ -0,0 +1,36 @@
|
|||
#ifndef RC_URL_H
|
||||
#define RC_URL_H
|
||||
|
||||
#include "rc_export.h"
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
RC_BEGIN_C_DECLS
|
||||
|
||||
RC_EXPORT int RC_CCONV rc_url_award_cheevo(char* buffer, size_t size, const char* user_name, const char* login_token, unsigned cheevo_id, int hardcore, const char* game_hash);
|
||||
|
||||
RC_EXPORT int RC_CCONV rc_url_submit_lboard(char* buffer, size_t size, const char* user_name, const char* login_token, unsigned lboard_id, int value);
|
||||
|
||||
RC_EXPORT int RC_CCONV rc_url_get_lboard_entries(char* buffer, size_t size, unsigned lboard_id, unsigned first_index, unsigned count);
|
||||
RC_EXPORT int RC_CCONV rc_url_get_lboard_entries_near_user(char* buffer, size_t size, unsigned lboard_id, const char* user_name, unsigned count);
|
||||
|
||||
RC_EXPORT int RC_CCONV rc_url_get_gameid(char* buffer, size_t size, const char* hash);
|
||||
|
||||
RC_EXPORT int RC_CCONV rc_url_get_patch(char* buffer, size_t size, const char* user_name, const char* login_token, unsigned gameid);
|
||||
|
||||
RC_EXPORT int RC_CCONV rc_url_get_badge_image(char* buffer, size_t size, const char* badge_name);
|
||||
|
||||
RC_EXPORT int RC_CCONV rc_url_login_with_password(char* buffer, size_t size, const char* user_name, const char* password);
|
||||
|
||||
RC_EXPORT int RC_CCONV rc_url_login_with_token(char* buffer, size_t size, const char* user_name, const char* login_token);
|
||||
|
||||
RC_EXPORT int RC_CCONV rc_url_get_unlock_list(char* buffer, size_t size, const char* user_name, const char* login_token, unsigned gameid, int hardcore);
|
||||
|
||||
RC_EXPORT int RC_CCONV rc_url_post_playing(char* buffer, size_t size, const char* user_name, const char* login_token, unsigned gameid);
|
||||
|
||||
RC_EXPORT int RC_CCONV rc_url_ping(char* url_buffer, size_t url_buffer_size, char* post_buffer, size_t post_buffer_size,
|
||||
const char* user_name, const char* login_token, unsigned gameid, const char* rich_presence);
|
||||
|
||||
RC_END_C_DECLS
|
||||
|
||||
#endif /* RC_URL_H */
|
|
@ -0,0 +1,51 @@
|
|||
#ifndef RC_UTIL_H
|
||||
#define RC_UTIL_H
|
||||
|
||||
#include "rc_export.h"
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
RC_BEGIN_C_DECLS
|
||||
|
||||
/**
|
||||
* A block of memory for variable length data (like strings and arrays).
|
||||
*/
|
||||
typedef struct rc_buffer_chunk_t {
|
||||
/* The current location where data is being written */
|
||||
uint8_t* write;
|
||||
/* The first byte past the end of data where writing cannot occur */
|
||||
uint8_t* end;
|
||||
/* The first byte of the data */
|
||||
uint8_t* start;
|
||||
/* The next block in the allocated memory chain */
|
||||
struct rc_buffer_chunk_t* next;
|
||||
}
|
||||
rc_buffer_chunk_t;
|
||||
|
||||
/**
|
||||
* A preallocated block of memory for variable length data (like strings and arrays).
|
||||
*/
|
||||
typedef struct rc_buffer_t {
|
||||
/* The chunk data (will point at the local data member) */
|
||||
struct rc_buffer_chunk_t chunk;
|
||||
/* Small chunk of memory pre-allocated for the chunk */
|
||||
uint8_t data[256];
|
||||
}
|
||||
rc_buffer_t;
|
||||
|
||||
void rc_buffer_init(rc_buffer_t* buffer);
|
||||
void rc_buffer_destroy(rc_buffer_t* buffer);
|
||||
uint8_t* rc_buffer_reserve(rc_buffer_t* buffer, size_t amount);
|
||||
void rc_buffer_consume(rc_buffer_t* buffer, const uint8_t* start, uint8_t* end);
|
||||
void* rc_buffer_alloc(rc_buffer_t* buffer, size_t amount);
|
||||
char* rc_buffer_strcpy(rc_buffer_t* buffer, const char* src);
|
||||
char* rc_buffer_strncpy(rc_buffer_t* buffer, const char* src, size_t len);
|
||||
|
||||
uint32_t rc_djb2(const char* input);
|
||||
|
||||
void rc_format_md5(char checksum[33], const uint8_t digest[16]);
|
||||
|
||||
RC_END_C_DECLS
|
||||
|
||||
#endif /* RC_UTIL_H */
|
|
@ -0,0 +1,8 @@
|
|||
#ifndef RCHEEVOS_H
|
||||
#define RCHEEVOS_H
|
||||
|
||||
#include "rc_runtime.h"
|
||||
#include "rc_runtime_types.h"
|
||||
#include "rc_consoles.h"
|
||||
|
||||
#endif /* RCHEEVOS_H */
|
|
@ -1 +0,0 @@
|
|||
Subproject commit 3cadf84c30bbc050c0fec79d26e1c8ff504bda42
|
|
@ -30,59 +30,59 @@
|
|||
<CodeAnalysisRuleSet>AllRules.ruleset</CodeAnalysisRuleSet>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="rcheevos\src\rapi\rc_api_common.c" />
|
||||
<ClCompile Include="rcheevos\src\rapi\rc_api_editor.c" />
|
||||
<ClCompile Include="rcheevos\src\rapi\rc_api_info.c" />
|
||||
<ClCompile Include="rcheevos\src\rapi\rc_api_runtime.c" />
|
||||
<ClCompile Include="rcheevos\src\rapi\rc_api_user.c" />
|
||||
<ClCompile Include="rcheevos\src\rcheevos\alloc.c" />
|
||||
<ClCompile Include="rcheevos\src\rcheevos\condition.c" />
|
||||
<ClCompile Include="rcheevos\src\rcheevos\condset.c" />
|
||||
<ClCompile Include="rcheevos\src\rcheevos\consoleinfo.c" />
|
||||
<ClCompile Include="rcheevos\src\rcheevos\format.c" />
|
||||
<ClCompile Include="rcheevos\src\rcheevos\lboard.c" />
|
||||
<ClCompile Include="rcheevos\src\rcheevos\memref.c" />
|
||||
<ClCompile Include="rcheevos\src\rcheevos\operand.c" />
|
||||
<ClCompile Include="rcheevos\src\rcheevos\rc_validate.c" />
|
||||
<ClCompile Include="rcheevos\src\rcheevos\richpresence.c" />
|
||||
<ClCompile Include="rcheevos\src\rcheevos\runtime.c" />
|
||||
<ClCompile Include="rcheevos\src\rcheevos\runtime_progress.c" />
|
||||
<ClCompile Include="rcheevos\src\rcheevos\trigger.c" />
|
||||
<ClCompile Include="rcheevos\src\rcheevos\value.c" />
|
||||
<ClCompile Include="rcheevos\src\rc_client.c" />
|
||||
<ClCompile Include="rcheevos\src\rc_compat.c" />
|
||||
<ClCompile Include="rcheevos\src\rc_util.c" />
|
||||
<ClCompile Include="rcheevos\src\rhash\hash.c" />
|
||||
<ClCompile Include="rcheevos\src\rhash\md5.c" />
|
||||
<ClCompile Include="rcheevos\src\rurl\url.c" />
|
||||
<ClCompile Include="src\rapi\rc_api_common.c" />
|
||||
<ClCompile Include="src\rapi\rc_api_editor.c" />
|
||||
<ClCompile Include="src\rapi\rc_api_info.c" />
|
||||
<ClCompile Include="src\rapi\rc_api_runtime.c" />
|
||||
<ClCompile Include="src\rapi\rc_api_user.c" />
|
||||
<ClCompile Include="src\rcheevos\alloc.c" />
|
||||
<ClCompile Include="src\rcheevos\condition.c" />
|
||||
<ClCompile Include="src\rcheevos\condset.c" />
|
||||
<ClCompile Include="src\rcheevos\consoleinfo.c" />
|
||||
<ClCompile Include="src\rcheevos\format.c" />
|
||||
<ClCompile Include="src\rcheevos\lboard.c" />
|
||||
<ClCompile Include="src\rcheevos\memref.c" />
|
||||
<ClCompile Include="src\rcheevos\operand.c" />
|
||||
<ClCompile Include="src\rcheevos\rc_validate.c" />
|
||||
<ClCompile Include="src\rcheevos\richpresence.c" />
|
||||
<ClCompile Include="src\rcheevos\runtime.c" />
|
||||
<ClCompile Include="src\rcheevos\runtime_progress.c" />
|
||||
<ClCompile Include="src\rcheevos\trigger.c" />
|
||||
<ClCompile Include="src\rcheevos\value.c" />
|
||||
<ClCompile Include="src\rc_client.c" />
|
||||
<ClCompile Include="src\rc_compat.c" />
|
||||
<ClCompile Include="src\rc_util.c" />
|
||||
<ClCompile Include="src\rhash\hash.c" />
|
||||
<ClCompile Include="src\rhash\md5.c" />
|
||||
<ClCompile Include="src\rurl\url.c" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="rcheevos\include\rcheevos.h" />
|
||||
<ClInclude Include="rcheevos\include\rc_api_editor.h" />
|
||||
<ClInclude Include="rcheevos\include\rc_api_info.h" />
|
||||
<ClInclude Include="rcheevos\include\rc_api_request.h" />
|
||||
<ClInclude Include="rcheevos\include\rc_api_runtime.h" />
|
||||
<ClInclude Include="rcheevos\include\rc_api_user.h" />
|
||||
<ClInclude Include="rcheevos\include\rc_client.h" />
|
||||
<ClInclude Include="rcheevos\include\rc_consoles.h" />
|
||||
<ClInclude Include="rcheevos\include\rc_error.h" />
|
||||
<ClInclude Include="rcheevos\include\rc_hash.h" />
|
||||
<ClInclude Include="rcheevos\include\rc_runtime.h" />
|
||||
<ClInclude Include="rcheevos\include\rc_runtime_types.h" />
|
||||
<ClInclude Include="rcheevos\include\rc_url.h" />
|
||||
<ClInclude Include="rcheevos\src\rapi\rc_api_common.h" />
|
||||
<ClInclude Include="rcheevos\src\rcheevos\rc_internal.h" />
|
||||
<ClInclude Include="rcheevos\src\rcheevos\rc_validate.h" />
|
||||
<ClInclude Include="rcheevos\src\rc_client_internal.h" />
|
||||
<ClInclude Include="rcheevos\src\rc_compat.h" />
|
||||
<ClInclude Include="rcheevos\src\rc_util.h" />
|
||||
<ClInclude Include="rcheevos\src\rc_version.h" />
|
||||
<ClInclude Include="rcheevos\src\rhash\md5.h" />
|
||||
<ClInclude Include="include\rcheevos.h" />
|
||||
<ClInclude Include="include\rc_api_editor.h" />
|
||||
<ClInclude Include="include\rc_api_info.h" />
|
||||
<ClInclude Include="include\rc_api_request.h" />
|
||||
<ClInclude Include="include\rc_api_runtime.h" />
|
||||
<ClInclude Include="include\rc_api_user.h" />
|
||||
<ClInclude Include="include\rc_client.h" />
|
||||
<ClInclude Include="include\rc_consoles.h" />
|
||||
<ClInclude Include="include\rc_error.h" />
|
||||
<ClInclude Include="include\rc_hash.h" />
|
||||
<ClInclude Include="include\rc_runtime.h" />
|
||||
<ClInclude Include="include\rc_runtime_types.h" />
|
||||
<ClInclude Include="include\rc_url.h" />
|
||||
<ClInclude Include="include\rc_util.h" />
|
||||
<ClInclude Include="src\rapi\rc_api_common.h" />
|
||||
<ClInclude Include="src\rcheevos\rc_internal.h" />
|
||||
<ClInclude Include="src\rcheevos\rc_validate.h" />
|
||||
<ClInclude Include="src\rc_client_internal.h" />
|
||||
<ClInclude Include="src\rc_compat.h" />
|
||||
<ClInclude Include="src\rc_version.h" />
|
||||
<ClInclude Include="src\rhash\md5.h" />
|
||||
</ItemGroup>
|
||||
<ItemDefinitionGroup>
|
||||
<ClCompile>
|
||||
<PreprocessorDefinitions>_CRT_NONSTDC_NO_WARNINGS;RC_DISABLE_LUA;RCHEEVOS_URL_SSL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<AdditionalIncludeDirectories>$(ProjectDir)rcheevos\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<AdditionalIncludeDirectories>$(ProjectDir)include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<WarningLevel>TurnOffAllWarnings</WarningLevel>
|
||||
</ClCompile>
|
||||
</ItemDefinitionGroup>
|
||||
|
|
|
@ -18,130 +18,132 @@
|
|||
</Filter>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="rcheevos\src\rapi\rc_api_editor.c">
|
||||
<ClCompile Include="src\rapi\rc_api_editor.c">
|
||||
<Filter>rapi</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="rcheevos\src\rapi\rc_api_info.c">
|
||||
<ClCompile Include="src\rapi\rc_api_info.c">
|
||||
<Filter>rapi</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="rcheevos\src\rapi\rc_api_runtime.c">
|
||||
<ClCompile Include="src\rapi\rc_api_runtime.c">
|
||||
<Filter>rapi</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="rcheevos\src\rapi\rc_api_user.c">
|
||||
<ClCompile Include="src\rapi\rc_api_user.c">
|
||||
<Filter>rapi</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="rcheevos\src\rapi\rc_api_common.c">
|
||||
<ClCompile Include="src\rapi\rc_api_common.c">
|
||||
<Filter>rapi</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="rcheevos\src\rhash\hash.c">
|
||||
<ClCompile Include="src\rhash\hash.c">
|
||||
<Filter>rhash</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="rcheevos\src\rhash\md5.c">
|
||||
<ClCompile Include="src\rhash\md5.c">
|
||||
<Filter>rhash</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="rcheevos\src\rurl\url.c">
|
||||
<ClCompile Include="src\rurl\url.c">
|
||||
<Filter>rurl</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="rcheevos\src\rc_client.c" />
|
||||
<ClCompile Include="rcheevos\src\rc_compat.c" />
|
||||
<ClCompile Include="rcheevos\src\rc_util.c" />
|
||||
<ClCompile Include="rcheevos\src\rcheevos\richpresence.c">
|
||||
<ClCompile Include="src\rc_client.c" />
|
||||
<ClCompile Include="src\rc_compat.c" />
|
||||
<ClCompile Include="src\rc_util.c" />
|
||||
<ClCompile Include="src\rcheevos\richpresence.c">
|
||||
<Filter>rcheevos</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="rcheevos\src\rcheevos\runtime.c">
|
||||
<ClCompile Include="src\rcheevos\runtime.c">
|
||||
<Filter>rcheevos</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="rcheevos\src\rcheevos\runtime_progress.c">
|
||||
<ClCompile Include="src\rcheevos\runtime_progress.c">
|
||||
<Filter>rcheevos</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="rcheevos\src\rcheevos\trigger.c">
|
||||
<ClCompile Include="src\rcheevos\trigger.c">
|
||||
<Filter>rcheevos</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="rcheevos\src\rcheevos\value.c">
|
||||
<ClCompile Include="src\rcheevos\value.c">
|
||||
<Filter>rcheevos</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="rcheevos\src\rcheevos\alloc.c">
|
||||
<ClCompile Include="src\rcheevos\alloc.c">
|
||||
<Filter>rcheevos</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="rcheevos\src\rcheevos\condition.c">
|
||||
<ClCompile Include="src\rcheevos\condition.c">
|
||||
<Filter>rcheevos</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="rcheevos\src\rcheevos\condset.c">
|
||||
<ClCompile Include="src\rcheevos\condset.c">
|
||||
<Filter>rcheevos</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="rcheevos\src\rcheevos\consoleinfo.c">
|
||||
<ClCompile Include="src\rcheevos\consoleinfo.c">
|
||||
<Filter>rcheevos</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="rcheevos\src\rcheevos\format.c">
|
||||
<ClCompile Include="src\rcheevos\format.c">
|
||||
<Filter>rcheevos</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="rcheevos\src\rcheevos\lboard.c">
|
||||
<ClCompile Include="src\rcheevos\lboard.c">
|
||||
<Filter>rcheevos</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="rcheevos\src\rcheevos\memref.c">
|
||||
<ClCompile Include="src\rcheevos\memref.c">
|
||||
<Filter>rcheevos</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="rcheevos\src\rcheevos\operand.c">
|
||||
<ClCompile Include="src\rcheevos\operand.c">
|
||||
<Filter>rcheevos</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="rcheevos\src\rcheevos\rc_validate.c">
|
||||
<ClCompile Include="src\rcheevos\rc_validate.c">
|
||||
<Filter>rcheevos</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="rcheevos\src\rapi\rc_api_common.h">
|
||||
<ClInclude Include="src\rapi\rc_api_common.h">
|
||||
<Filter>rapi</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="rcheevos\src\rhash\md5.h">
|
||||
<ClInclude Include="src\rhash\md5.h">
|
||||
<Filter>rhash</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="rcheevos\include\rc_api_runtime.h">
|
||||
<ClInclude Include="include\rc_api_runtime.h">
|
||||
<Filter>include</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="rcheevos\include\rc_api_user.h">
|
||||
<ClInclude Include="include\rc_api_user.h">
|
||||
<Filter>include</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="rcheevos\include\rc_consoles.h">
|
||||
<ClInclude Include="include\rc_consoles.h">
|
||||
<Filter>include</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="rcheevos\include\rc_error.h">
|
||||
<ClInclude Include="include\rc_error.h">
|
||||
<Filter>include</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="rcheevos\include\rc_hash.h">
|
||||
<ClInclude Include="include\rc_hash.h">
|
||||
<Filter>include</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="rcheevos\include\rc_runtime.h">
|
||||
<ClInclude Include="include\rc_runtime.h">
|
||||
<Filter>include</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="rcheevos\include\rc_runtime_types.h">
|
||||
<ClInclude Include="include\rc_runtime_types.h">
|
||||
<Filter>include</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="rcheevos\include\rc_url.h">
|
||||
<ClInclude Include="include\rc_url.h">
|
||||
<Filter>include</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="rcheevos\include\rcheevos.h">
|
||||
<ClInclude Include="include\rcheevos.h">
|
||||
<Filter>include</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="rcheevos\include\rc_api_editor.h">
|
||||
<ClInclude Include="include\rc_api_editor.h">
|
||||
<Filter>include</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="rcheevos\include\rc_api_info.h">
|
||||
<ClInclude Include="include\rc_api_info.h">
|
||||
<Filter>include</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="rcheevos\include\rc_api_request.h">
|
||||
<ClInclude Include="include\rc_api_request.h">
|
||||
<Filter>include</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="rcheevos\include\rc_client.h">
|
||||
<ClInclude Include="include\rc_client.h">
|
||||
<Filter>include</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="rcheevos\src\rc_client_internal.h" />
|
||||
<ClInclude Include="rcheevos\src\rc_compat.h" />
|
||||
<ClInclude Include="rcheevos\src\rc_util.h" />
|
||||
<ClInclude Include="rcheevos\src\rc_version.h" />
|
||||
<ClInclude Include="rcheevos\src\rcheevos\rc_validate.h">
|
||||
<ClInclude Include="src\rc_client_internal.h" />
|
||||
<ClInclude Include="src\rc_compat.h" />
|
||||
<ClInclude Include="include\rc_util.h">
|
||||
<Filter>include</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="src\rc_version.h" />
|
||||
<ClInclude Include="src\rcheevos\rc_validate.h">
|
||||
<Filter>rcheevos</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="rcheevos\src\rcheevos\rc_internal.h">
|
||||
<ClInclude Include="src\rcheevos\rc_internal.h">
|
||||
<Filter>rcheevos</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,80 @@
|
|||
#ifndef RC_API_COMMON_H
|
||||
#define RC_API_COMMON_H
|
||||
|
||||
#include "rc_api_request.h"
|
||||
|
||||
#include <stddef.h>
|
||||
#include <time.h>
|
||||
|
||||
RC_BEGIN_C_DECLS
|
||||
|
||||
#define RC_CONTENT_TYPE_URLENCODED "application/x-www-form-urlencoded"
|
||||
|
||||
typedef struct rc_api_url_builder_t {
|
||||
char* write;
|
||||
char* start;
|
||||
char* end;
|
||||
/* pointer to a preallocated rc_buffer_t */
|
||||
rc_buffer_t* buffer;
|
||||
int result;
|
||||
}
|
||||
rc_api_url_builder_t;
|
||||
|
||||
void rc_url_builder_init(rc_api_url_builder_t* builder, rc_buffer_t* buffer, size_t estimated_size);
|
||||
void rc_url_builder_append(rc_api_url_builder_t* builder, const char* data, size_t len);
|
||||
const char* rc_url_builder_finalize(rc_api_url_builder_t* builder);
|
||||
|
||||
#define RC_JSON_NEW_FIELD(n) {NULL,NULL,n,sizeof(n)-1,0}
|
||||
|
||||
typedef struct rc_json_field_t {
|
||||
const char* value_start;
|
||||
const char* value_end;
|
||||
const char* name;
|
||||
size_t name_len;
|
||||
uint32_t array_size;
|
||||
}
|
||||
rc_json_field_t;
|
||||
|
||||
typedef struct rc_json_iterator_t {
|
||||
const char* json;
|
||||
const char* end;
|
||||
}
|
||||
rc_json_iterator_t;
|
||||
|
||||
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_buffer_t* buffer, const rc_json_field_t* field, const char* field_name);
|
||||
int rc_json_get_num(int32_t* out, const rc_json_field_t* field, const char* field_name);
|
||||
int rc_json_get_unum(uint32_t* out, const rc_json_field_t* field, const char* field_name);
|
||||
int rc_json_get_float(float* out, const rc_json_field_t* field, const char* field_name);
|
||||
int rc_json_get_bool(int* out, const rc_json_field_t* field, const char* field_name);
|
||||
int rc_json_get_datetime(time_t* out, const rc_json_field_t* field, const char* field_name);
|
||||
void rc_json_get_optional_string(const char** out, rc_api_response_t* response, const rc_json_field_t* field, const char* field_name, const char* default_value);
|
||||
void rc_json_get_optional_num(int32_t* out, const rc_json_field_t* field, const char* field_name, int default_value);
|
||||
void rc_json_get_optional_unum(uint32_t* out, const rc_json_field_t* field, const char* field_name, uint32_t default_value);
|
||||
void rc_json_get_optional_float(float* out, const rc_json_field_t* field, const char* field_name, float default_value);
|
||||
void rc_json_get_optional_bool(int* out, const rc_json_field_t* field, const char* field_name, int default_value);
|
||||
int rc_json_get_optional_array(uint32_t* num_entries, rc_json_field_t* iterator, const rc_json_field_t* field, const char* field_name);
|
||||
int rc_json_get_required_string(const char** out, rc_api_response_t* response, const rc_json_field_t* field, const char* field_name);
|
||||
int rc_json_get_required_num(int32_t* out, rc_api_response_t* response, const rc_json_field_t* field, const char* field_name);
|
||||
int rc_json_get_required_unum(uint32_t* out, rc_api_response_t* response, const rc_json_field_t* field, const char* field_name);
|
||||
int rc_json_get_required_float(float* out, rc_api_response_t* response, const rc_json_field_t* field, const char* field_name);
|
||||
int rc_json_get_required_bool(int* 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_unum_array(uint32_t** entries, uint32_t* num_entries, rc_api_response_t* response, const rc_json_field_t* field, const char* field_name);
|
||||
int rc_json_get_required_array(uint32_t* num_entries, rc_json_field_t* array_field, rc_api_response_t* response, const rc_json_field_t* field, const char* field_name);
|
||||
int rc_json_get_array_entry_object(rc_json_field_t* fields, size_t field_count, rc_json_iterator_t* iterator);
|
||||
int rc_json_get_next_object_field(rc_json_iterator_t* iterator, rc_json_field_t* field);
|
||||
int rc_json_get_object_string_length(const char* json);
|
||||
|
||||
void rc_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, int32_t value);
|
||||
void rc_url_builder_append_unum_param(rc_api_url_builder_t* builder, const char* param, uint32_t value);
|
||||
void rc_url_builder_append_str_param(rc_api_url_builder_t* builder, const char* param, const char* value);
|
||||
|
||||
void rc_api_url_build_dorequest_url(rc_api_request_t* request);
|
||||
int rc_api_url_build_dorequest(rc_api_url_builder_t* builder, const char* api, const char* username, const char* api_token);
|
||||
|
||||
RC_END_C_DECLS
|
||||
|
||||
#endif /* RC_API_COMMON_H */
|
|
@ -0,0 +1,529 @@
|
|||
#include "rc_api_editor.h"
|
||||
#include "rc_api_common.h"
|
||||
#include "rc_api_runtime.h"
|
||||
|
||||
#include "../rc_compat.h"
|
||||
#include "../rhash/md5.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
/* --- Fetch Code Notes --- */
|
||||
|
||||
int rc_api_init_fetch_code_notes_request(rc_api_request_t* request, const rc_api_fetch_code_notes_request_t* api_params) {
|
||||
rc_api_url_builder_t builder;
|
||||
|
||||
rc_api_url_build_dorequest_url(request);
|
||||
|
||||
if (api_params->game_id == 0)
|
||||
return RC_INVALID_STATE;
|
||||
|
||||
rc_url_builder_init(&builder, &request->buffer, 48);
|
||||
rc_url_builder_append_str_param(&builder, "r", "codenotes2");
|
||||
rc_url_builder_append_unum_param(&builder, "g", api_params->game_id);
|
||||
|
||||
request->post_data = rc_url_builder_finalize(&builder);
|
||||
request->content_type = RC_CONTENT_TYPE_URLENCODED;
|
||||
|
||||
return builder.result;
|
||||
}
|
||||
|
||||
int rc_api_process_fetch_code_notes_response(rc_api_fetch_code_notes_response_t* response, const char* server_response) {
|
||||
rc_api_server_response_t response_obj;
|
||||
|
||||
memset(&response_obj, 0, sizeof(response_obj));
|
||||
response_obj.body = server_response;
|
||||
response_obj.body_length = rc_json_get_object_string_length(server_response);
|
||||
|
||||
return rc_api_process_fetch_code_notes_server_response(response, &response_obj);
|
||||
}
|
||||
|
||||
int rc_api_process_fetch_code_notes_server_response(rc_api_fetch_code_notes_response_t* response, const rc_api_server_response_t* server_response) {
|
||||
rc_json_field_t array_field;
|
||||
rc_json_iterator_t iterator;
|
||||
rc_api_code_note_t* note;
|
||||
const char* address_str;
|
||||
const char* last_author = "";
|
||||
size_t last_author_len = 0;
|
||||
size_t len;
|
||||
int result;
|
||||
|
||||
rc_json_field_t fields[] = {
|
||||
RC_JSON_NEW_FIELD("Success"),
|
||||
RC_JSON_NEW_FIELD("Error"),
|
||||
RC_JSON_NEW_FIELD("CodeNotes")
|
||||
};
|
||||
|
||||
rc_json_field_t note_fields[] = {
|
||||
RC_JSON_NEW_FIELD("Address"),
|
||||
RC_JSON_NEW_FIELD("User"),
|
||||
RC_JSON_NEW_FIELD("Note")
|
||||
};
|
||||
|
||||
memset(response, 0, sizeof(*response));
|
||||
rc_buffer_init(&response->response.buffer);
|
||||
|
||||
result = rc_json_parse_server_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0]));
|
||||
if (result != RC_OK || !response->response.succeeded)
|
||||
return result;
|
||||
|
||||
if (!rc_json_get_required_array(&response->num_notes, &array_field, &response->response, &fields[2], "CodeNotes"))
|
||||
return RC_MISSING_VALUE;
|
||||
|
||||
if (response->num_notes) {
|
||||
response->notes = (rc_api_code_note_t*)rc_buffer_alloc(&response->response.buffer, response->num_notes * sizeof(rc_api_code_note_t));
|
||||
if (!response->notes)
|
||||
return RC_OUT_OF_MEMORY;
|
||||
|
||||
memset(&iterator, 0, sizeof(iterator));
|
||||
iterator.json = array_field.value_start;
|
||||
iterator.end = array_field.value_end;
|
||||
|
||||
note = response->notes;
|
||||
while (rc_json_get_array_entry_object(note_fields, sizeof(note_fields) / sizeof(note_fields[0]), &iterator)) {
|
||||
/* an empty note represents a record that was deleted on the server */
|
||||
/* a note set to '' also represents a deleted note (remnant of a bug) */
|
||||
/* NOTE: len will include the quotes */
|
||||
if (note_fields[2].value_start) {
|
||||
len = note_fields[2].value_end - note_fields[2].value_start;
|
||||
if (len == 2 || (len == 4 && note_fields[2].value_start[1] == '\'' && note_fields[2].value_start[2] == '\'')) {
|
||||
--response->num_notes;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (!rc_json_get_required_string(&address_str, &response->response, ¬e_fields[0], "Address"))
|
||||
return RC_MISSING_VALUE;
|
||||
note->address = (unsigned)strtol(address_str, NULL, 16);
|
||||
if (!rc_json_get_required_string(¬e->note, &response->response, ¬e_fields[2], "Note"))
|
||||
return RC_MISSING_VALUE;
|
||||
|
||||
len = note_fields[1].value_end - note_fields[1].value_start;
|
||||
if (len == last_author_len && memcmp(note_fields[1].value_start, last_author, len) == 0) {
|
||||
note->author = last_author;
|
||||
}
|
||||
else {
|
||||
if (!rc_json_get_required_string(¬e->author, &response->response, ¬e_fields[1], "User"))
|
||||
return RC_MISSING_VALUE;
|
||||
|
||||
last_author = note->author;
|
||||
last_author_len = len;
|
||||
}
|
||||
|
||||
++note;
|
||||
}
|
||||
}
|
||||
|
||||
return RC_OK;
|
||||
}
|
||||
|
||||
void rc_api_destroy_fetch_code_notes_response(rc_api_fetch_code_notes_response_t* response) {
|
||||
rc_buffer_destroy(&response->response.buffer);
|
||||
}
|
||||
|
||||
/* --- Update Code Note --- */
|
||||
|
||||
int rc_api_init_update_code_note_request(rc_api_request_t* request, const rc_api_update_code_note_request_t* api_params) {
|
||||
rc_api_url_builder_t builder;
|
||||
|
||||
rc_api_url_build_dorequest_url(request);
|
||||
|
||||
if (api_params->game_id == 0)
|
||||
return RC_INVALID_STATE;
|
||||
|
||||
rc_url_builder_init(&builder, &request->buffer, 128);
|
||||
if (!rc_api_url_build_dorequest(&builder, "submitcodenote", api_params->username, api_params->api_token))
|
||||
return builder.result;
|
||||
|
||||
rc_url_builder_append_unum_param(&builder, "g", api_params->game_id);
|
||||
rc_url_builder_append_unum_param(&builder, "m", api_params->address);
|
||||
|
||||
if (api_params->note && *api_params->note)
|
||||
rc_url_builder_append_str_param(&builder, "n", api_params->note);
|
||||
|
||||
request->post_data = rc_url_builder_finalize(&builder);
|
||||
request->content_type = RC_CONTENT_TYPE_URLENCODED;
|
||||
|
||||
return builder.result;
|
||||
}
|
||||
|
||||
int rc_api_process_update_code_note_response(rc_api_update_code_note_response_t* response, const char* server_response) {
|
||||
rc_api_server_response_t response_obj;
|
||||
|
||||
memset(&response_obj, 0, sizeof(response_obj));
|
||||
response_obj.body = server_response;
|
||||
response_obj.body_length = rc_json_get_object_string_length(server_response);
|
||||
|
||||
return rc_api_process_update_code_note_server_response(response, &response_obj);
|
||||
}
|
||||
|
||||
int rc_api_process_update_code_note_server_response(rc_api_update_code_note_response_t* response, const rc_api_server_response_t* server_response) {
|
||||
int result;
|
||||
|
||||
rc_json_field_t fields[] = {
|
||||
RC_JSON_NEW_FIELD("Success"),
|
||||
RC_JSON_NEW_FIELD("Error")
|
||||
/* unused fields
|
||||
RC_JSON_NEW_FIELD("GameID"),
|
||||
RC_JSON_NEW_FIELD("Address"),
|
||||
RC_JSON_NEW_FIELD("Note")
|
||||
*/
|
||||
};
|
||||
|
||||
memset(response, 0, sizeof(*response));
|
||||
rc_buffer_init(&response->response.buffer);
|
||||
|
||||
result = rc_json_parse_server_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0]));
|
||||
if (result != RC_OK || !response->response.succeeded)
|
||||
return result;
|
||||
|
||||
return RC_OK;
|
||||
}
|
||||
|
||||
void rc_api_destroy_update_code_note_response(rc_api_update_code_note_response_t* response) {
|
||||
rc_buffer_destroy(&response->response.buffer);
|
||||
}
|
||||
|
||||
/* --- Update Achievement --- */
|
||||
|
||||
static const char* rc_type_string(uint32_t type) {
|
||||
switch (type) {
|
||||
case RC_ACHIEVEMENT_TYPE_MISSABLE: return "missable";
|
||||
case RC_ACHIEVEMENT_TYPE_PROGRESSION: return "progression";
|
||||
case RC_ACHIEVEMENT_TYPE_WIN: return "win_condition";
|
||||
default: return "";
|
||||
}
|
||||
}
|
||||
|
||||
int rc_api_init_update_achievement_request(rc_api_request_t* request, const rc_api_update_achievement_request_t* api_params) {
|
||||
rc_api_url_builder_t builder;
|
||||
char buffer[33];
|
||||
md5_state_t md5;
|
||||
md5_byte_t hash[16];
|
||||
|
||||
rc_api_url_build_dorequest_url(request);
|
||||
|
||||
if (api_params->game_id == 0 || api_params->category == 0)
|
||||
return RC_INVALID_STATE;
|
||||
if (!api_params->title || !*api_params->title)
|
||||
return RC_INVALID_STATE;
|
||||
if (!api_params->description || !*api_params->description)
|
||||
return RC_INVALID_STATE;
|
||||
if (!api_params->trigger || !*api_params->trigger)
|
||||
return RC_INVALID_STATE;
|
||||
|
||||
rc_url_builder_init(&builder, &request->buffer, 128);
|
||||
if (!rc_api_url_build_dorequest(&builder, "uploadachievement", api_params->username, api_params->api_token))
|
||||
return builder.result;
|
||||
|
||||
if (api_params->achievement_id)
|
||||
rc_url_builder_append_unum_param(&builder, "a", api_params->achievement_id);
|
||||
rc_url_builder_append_unum_param(&builder, "g", api_params->game_id);
|
||||
rc_url_builder_append_str_param(&builder, "n", api_params->title);
|
||||
rc_url_builder_append_str_param(&builder, "d", api_params->description);
|
||||
rc_url_builder_append_str_param(&builder, "m", api_params->trigger);
|
||||
rc_url_builder_append_unum_param(&builder, "z", api_params->points);
|
||||
rc_url_builder_append_unum_param(&builder, "f", api_params->category);
|
||||
if (api_params->badge)
|
||||
rc_url_builder_append_str_param(&builder, "b", api_params->badge);
|
||||
rc_url_builder_append_str_param(&builder, "x", rc_type_string(api_params->type));
|
||||
|
||||
/* Evaluate the signature. */
|
||||
md5_init(&md5);
|
||||
md5_append(&md5, (md5_byte_t*)api_params->username, (int)strlen(api_params->username));
|
||||
md5_append(&md5, (md5_byte_t*)"SECRET", 6);
|
||||
snprintf(buffer, sizeof(buffer), "%u", api_params->achievement_id);
|
||||
md5_append(&md5, (md5_byte_t*)buffer, (int)strlen(buffer));
|
||||
md5_append(&md5, (md5_byte_t*)"SEC", 3);
|
||||
md5_append(&md5, (md5_byte_t*)api_params->trigger, (int)strlen(api_params->trigger));
|
||||
snprintf(buffer, sizeof(buffer), "%u", api_params->points);
|
||||
md5_append(&md5, (md5_byte_t*)buffer, (int)strlen(buffer));
|
||||
md5_append(&md5, (md5_byte_t*)"RE2", 3);
|
||||
snprintf(buffer, sizeof(buffer), "%u", api_params->points * 3);
|
||||
md5_append(&md5, (md5_byte_t*)buffer, (int)strlen(buffer));
|
||||
md5_finish(&md5, hash);
|
||||
rc_format_md5(buffer, hash);
|
||||
rc_url_builder_append_str_param(&builder, "h", buffer);
|
||||
|
||||
request->post_data = rc_url_builder_finalize(&builder);
|
||||
request->content_type = RC_CONTENT_TYPE_URLENCODED;
|
||||
|
||||
return builder.result;
|
||||
}
|
||||
|
||||
int rc_api_process_update_achievement_response(rc_api_update_achievement_response_t* response, const char* server_response) {
|
||||
rc_api_server_response_t response_obj;
|
||||
|
||||
memset(&response_obj, 0, sizeof(response_obj));
|
||||
response_obj.body = server_response;
|
||||
response_obj.body_length = rc_json_get_object_string_length(server_response);
|
||||
|
||||
return rc_api_process_update_achievement_server_response(response, &response_obj);
|
||||
}
|
||||
|
||||
int rc_api_process_update_achievement_server_response(rc_api_update_achievement_response_t* response, const rc_api_server_response_t* server_response) {
|
||||
int result;
|
||||
|
||||
rc_json_field_t fields[] = {
|
||||
RC_JSON_NEW_FIELD("Success"),
|
||||
RC_JSON_NEW_FIELD("Error"),
|
||||
RC_JSON_NEW_FIELD("AchievementID")
|
||||
};
|
||||
|
||||
memset(response, 0, sizeof(*response));
|
||||
rc_buffer_init(&response->response.buffer);
|
||||
|
||||
result = rc_json_parse_server_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0]));
|
||||
if (result != RC_OK || !response->response.succeeded)
|
||||
return result;
|
||||
|
||||
if (!rc_json_get_required_unum(&response->achievement_id, &response->response, &fields[2], "AchievementID"))
|
||||
return RC_MISSING_VALUE;
|
||||
|
||||
return RC_OK;
|
||||
}
|
||||
|
||||
void rc_api_destroy_update_achievement_response(rc_api_update_achievement_response_t* response) {
|
||||
rc_buffer_destroy(&response->response.buffer);
|
||||
}
|
||||
|
||||
/* --- Update Leaderboard --- */
|
||||
|
||||
int rc_api_init_update_leaderboard_request(rc_api_request_t* request, const rc_api_update_leaderboard_request_t* api_params) {
|
||||
rc_api_url_builder_t builder;
|
||||
char buffer[33];
|
||||
md5_state_t md5;
|
||||
md5_byte_t hash[16];
|
||||
|
||||
rc_api_url_build_dorequest_url(request);
|
||||
|
||||
if (api_params->game_id == 0)
|
||||
return RC_INVALID_STATE;
|
||||
if (!api_params->title || !*api_params->title)
|
||||
return RC_INVALID_STATE;
|
||||
if (!api_params->description)
|
||||
return RC_INVALID_STATE;
|
||||
if (!api_params->start_trigger || !*api_params->start_trigger)
|
||||
return RC_INVALID_STATE;
|
||||
if (!api_params->submit_trigger || !*api_params->submit_trigger)
|
||||
return RC_INVALID_STATE;
|
||||
if (!api_params->cancel_trigger || !*api_params->cancel_trigger)
|
||||
return RC_INVALID_STATE;
|
||||
if (!api_params->value_definition || !*api_params->value_definition)
|
||||
return RC_INVALID_STATE;
|
||||
if (!api_params->format || !*api_params->format)
|
||||
return RC_INVALID_STATE;
|
||||
|
||||
rc_url_builder_init(&builder, &request->buffer, 128);
|
||||
if (!rc_api_url_build_dorequest(&builder, "uploadleaderboard", api_params->username, api_params->api_token))
|
||||
return builder.result;
|
||||
|
||||
if (api_params->leaderboard_id)
|
||||
rc_url_builder_append_unum_param(&builder, "i", api_params->leaderboard_id);
|
||||
rc_url_builder_append_unum_param(&builder, "g", api_params->game_id);
|
||||
rc_url_builder_append_str_param(&builder, "n", api_params->title);
|
||||
rc_url_builder_append_str_param(&builder, "d", api_params->description);
|
||||
rc_url_builder_append_str_param(&builder, "s", api_params->start_trigger);
|
||||
rc_url_builder_append_str_param(&builder, "b", api_params->submit_trigger);
|
||||
rc_url_builder_append_str_param(&builder, "c", api_params->cancel_trigger);
|
||||
rc_url_builder_append_str_param(&builder, "l", api_params->value_definition);
|
||||
rc_url_builder_append_num_param(&builder, "w", api_params->lower_is_better);
|
||||
rc_url_builder_append_str_param(&builder, "f", api_params->format);
|
||||
|
||||
/* Evaluate the signature. */
|
||||
md5_init(&md5);
|
||||
md5_append(&md5, (md5_byte_t*)api_params->username, (int)strlen(api_params->username));
|
||||
md5_append(&md5, (md5_byte_t*)"SECRET", 6);
|
||||
snprintf(buffer, sizeof(buffer), "%u", api_params->leaderboard_id);
|
||||
md5_append(&md5, (md5_byte_t*)buffer, (int)strlen(buffer));
|
||||
md5_append(&md5, (md5_byte_t*)"SEC", 3);
|
||||
md5_append(&md5, (md5_byte_t*)api_params->start_trigger, (int)strlen(api_params->start_trigger));
|
||||
md5_append(&md5, (md5_byte_t*)api_params->submit_trigger, (int)strlen(api_params->submit_trigger));
|
||||
md5_append(&md5, (md5_byte_t*)api_params->cancel_trigger, (int)strlen(api_params->cancel_trigger));
|
||||
md5_append(&md5, (md5_byte_t*)api_params->value_definition, (int)strlen(api_params->value_definition));
|
||||
md5_append(&md5, (md5_byte_t*)"RE2", 3);
|
||||
md5_append(&md5, (md5_byte_t*)api_params->format, (int)strlen(api_params->format));
|
||||
md5_finish(&md5, hash);
|
||||
rc_format_md5(buffer, hash);
|
||||
rc_url_builder_append_str_param(&builder, "h", buffer);
|
||||
|
||||
request->post_data = rc_url_builder_finalize(&builder);
|
||||
request->content_type = RC_CONTENT_TYPE_URLENCODED;
|
||||
|
||||
return builder.result;
|
||||
}
|
||||
|
||||
int rc_api_process_update_leaderboard_response(rc_api_update_leaderboard_response_t* response, const char* server_response) {
|
||||
rc_api_server_response_t response_obj;
|
||||
|
||||
memset(&response_obj, 0, sizeof(response_obj));
|
||||
response_obj.body = server_response;
|
||||
response_obj.body_length = rc_json_get_object_string_length(server_response);
|
||||
|
||||
return rc_api_process_update_leaderboard_server_response(response, &response_obj);
|
||||
}
|
||||
|
||||
int rc_api_process_update_leaderboard_server_response(rc_api_update_leaderboard_response_t* response, const rc_api_server_response_t* server_response) {
|
||||
int result;
|
||||
|
||||
rc_json_field_t fields[] = {
|
||||
RC_JSON_NEW_FIELD("Success"),
|
||||
RC_JSON_NEW_FIELD("Error"),
|
||||
RC_JSON_NEW_FIELD("LeaderboardID")
|
||||
};
|
||||
|
||||
memset(response, 0, sizeof(*response));
|
||||
rc_buffer_init(&response->response.buffer);
|
||||
|
||||
result = rc_json_parse_server_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0]));
|
||||
if (result != RC_OK || !response->response.succeeded)
|
||||
return result;
|
||||
|
||||
if (!rc_json_get_required_unum(&response->leaderboard_id, &response->response, &fields[2], "LeaderboardID"))
|
||||
return RC_MISSING_VALUE;
|
||||
|
||||
return RC_OK;
|
||||
}
|
||||
|
||||
void rc_api_destroy_update_leaderboard_response(rc_api_update_leaderboard_response_t* response) {
|
||||
rc_buffer_destroy(&response->response.buffer);
|
||||
}
|
||||
|
||||
/* --- Fetch Badge Range --- */
|
||||
|
||||
int rc_api_init_fetch_badge_range_request(rc_api_request_t* request, const rc_api_fetch_badge_range_request_t* api_params) {
|
||||
rc_api_url_builder_t builder;
|
||||
|
||||
rc_api_url_build_dorequest_url(request);
|
||||
|
||||
rc_url_builder_init(&builder, &request->buffer, 48);
|
||||
rc_url_builder_append_str_param(&builder, "r", "badgeiter");
|
||||
|
||||
request->post_data = rc_url_builder_finalize(&builder);
|
||||
request->content_type = RC_CONTENT_TYPE_URLENCODED;
|
||||
|
||||
(void)api_params;
|
||||
|
||||
return builder.result;
|
||||
}
|
||||
|
||||
int rc_api_process_fetch_badge_range_response(rc_api_fetch_badge_range_response_t* response, const char* server_response) {
|
||||
rc_api_server_response_t response_obj;
|
||||
|
||||
memset(&response_obj, 0, sizeof(response_obj));
|
||||
response_obj.body = server_response;
|
||||
response_obj.body_length = rc_json_get_object_string_length(server_response);
|
||||
|
||||
return rc_api_process_fetch_badge_range_server_response(response, &response_obj);
|
||||
}
|
||||
|
||||
int rc_api_process_fetch_badge_range_server_response(rc_api_fetch_badge_range_response_t* response, const rc_api_server_response_t* server_response) {
|
||||
int result;
|
||||
|
||||
rc_json_field_t fields[] = {
|
||||
RC_JSON_NEW_FIELD("Success"),
|
||||
RC_JSON_NEW_FIELD("Error"),
|
||||
RC_JSON_NEW_FIELD("FirstBadge"),
|
||||
RC_JSON_NEW_FIELD("NextBadge")
|
||||
};
|
||||
|
||||
memset(response, 0, sizeof(*response));
|
||||
rc_buffer_init(&response->response.buffer);
|
||||
|
||||
result = rc_json_parse_server_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0]));
|
||||
if (result != RC_OK || !response->response.succeeded)
|
||||
return result;
|
||||
|
||||
if (!rc_json_get_required_unum(&response->first_badge_id, &response->response, &fields[2], "FirstBadge"))
|
||||
return RC_MISSING_VALUE;
|
||||
if (!rc_json_get_required_unum(&response->next_badge_id, &response->response, &fields[3], "NextBadge"))
|
||||
return RC_MISSING_VALUE;
|
||||
|
||||
return RC_OK;
|
||||
}
|
||||
|
||||
void rc_api_destroy_fetch_badge_range_response(rc_api_fetch_badge_range_response_t* response) {
|
||||
rc_buffer_destroy(&response->response.buffer);
|
||||
}
|
||||
|
||||
/* --- Add Game Hash --- */
|
||||
|
||||
int rc_api_init_add_game_hash_request(rc_api_request_t* request, const rc_api_add_game_hash_request_t* api_params) {
|
||||
rc_api_url_builder_t builder;
|
||||
|
||||
rc_api_url_build_dorequest_url(request);
|
||||
|
||||
if (api_params->console_id == 0)
|
||||
return RC_INVALID_STATE;
|
||||
if (!api_params->hash || !*api_params->hash)
|
||||
return RC_INVALID_STATE;
|
||||
if (api_params->game_id == 0 && (!api_params->title || !*api_params->title))
|
||||
return RC_INVALID_STATE;
|
||||
|
||||
rc_url_builder_init(&builder, &request->buffer, 128);
|
||||
if (!rc_api_url_build_dorequest(&builder, "submitgametitle", api_params->username, api_params->api_token))
|
||||
return builder.result;
|
||||
|
||||
rc_url_builder_append_unum_param(&builder, "c", api_params->console_id);
|
||||
rc_url_builder_append_str_param(&builder, "m", api_params->hash);
|
||||
if (api_params->title)
|
||||
rc_url_builder_append_str_param(&builder, "i", api_params->title);
|
||||
if (api_params->game_id)
|
||||
rc_url_builder_append_unum_param(&builder, "g", api_params->game_id);
|
||||
if (api_params->hash_description && *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->content_type = RC_CONTENT_TYPE_URLENCODED;
|
||||
|
||||
return builder.result;
|
||||
}
|
||||
|
||||
int rc_api_process_add_game_hash_response(rc_api_add_game_hash_response_t* response, const char* server_response) {
|
||||
rc_api_server_response_t response_obj;
|
||||
|
||||
memset(&response_obj, 0, sizeof(response_obj));
|
||||
response_obj.body = server_response;
|
||||
response_obj.body_length = rc_json_get_object_string_length(server_response);
|
||||
|
||||
return rc_api_process_add_game_hash_server_response(response, &response_obj);
|
||||
}
|
||||
|
||||
int rc_api_process_add_game_hash_server_response(rc_api_add_game_hash_response_t* response, const rc_api_server_response_t* server_response) {
|
||||
int result;
|
||||
|
||||
rc_json_field_t fields[] = {
|
||||
RC_JSON_NEW_FIELD("Success"),
|
||||
RC_JSON_NEW_FIELD("Error"),
|
||||
RC_JSON_NEW_FIELD("Response")
|
||||
};
|
||||
|
||||
rc_json_field_t response_fields[] = {
|
||||
RC_JSON_NEW_FIELD("GameID")
|
||||
/* unused fields
|
||||
RC_JSON_NEW_FIELD("MD5"),
|
||||
RC_JSON_NEW_FIELD("ConsoleID"),
|
||||
RC_JSON_NEW_FIELD("GameTitle"),
|
||||
RC_JSON_NEW_FIELD("Success")
|
||||
*/
|
||||
};
|
||||
|
||||
memset(response, 0, sizeof(*response));
|
||||
rc_buffer_init(&response->response.buffer);
|
||||
|
||||
result = rc_json_parse_server_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0]));
|
||||
if (result != RC_OK || !response->response.succeeded)
|
||||
return result;
|
||||
|
||||
if (!rc_json_get_required_object(response_fields, sizeof(response_fields) / sizeof(response_fields[0]), &response->response, &fields[2], "Response"))
|
||||
return RC_MISSING_VALUE;
|
||||
|
||||
if (!rc_json_get_required_unum(&response->game_id, &response->response, &response_fields[0], "GameID"))
|
||||
return RC_MISSING_VALUE;
|
||||
|
||||
return RC_OK;
|
||||
}
|
||||
|
||||
void rc_api_destroy_add_game_hash_response(rc_api_add_game_hash_response_t* response) {
|
||||
rc_buffer_destroy(&response->response.buffer);
|
||||
}
|
|
@ -0,0 +1,373 @@
|
|||
#include "rc_api_info.h"
|
||||
#include "rc_api_common.h"
|
||||
|
||||
#include "rc_runtime_types.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
/* --- Fetch Achievement Info --- */
|
||||
|
||||
int rc_api_init_fetch_achievement_info_request(rc_api_request_t* request, const rc_api_fetch_achievement_info_request_t* api_params) {
|
||||
rc_api_url_builder_t builder;
|
||||
|
||||
rc_api_url_build_dorequest_url(request);
|
||||
|
||||
if (api_params->achievement_id == 0)
|
||||
return RC_INVALID_STATE;
|
||||
|
||||
rc_url_builder_init(&builder, &request->buffer, 48);
|
||||
if (rc_api_url_build_dorequest(&builder, "achievementwondata", api_params->username, api_params->api_token)) {
|
||||
rc_url_builder_append_unum_param(&builder, "a", api_params->achievement_id);
|
||||
|
||||
if (api_params->friends_only)
|
||||
rc_url_builder_append_unum_param(&builder, "f", 1);
|
||||
if (api_params->first_entry > 1)
|
||||
rc_url_builder_append_unum_param(&builder, "o", api_params->first_entry - 1); /* number of entries to skip */
|
||||
rc_url_builder_append_unum_param(&builder, "c", api_params->count);
|
||||
|
||||
request->post_data = rc_url_builder_finalize(&builder);
|
||||
request->content_type = RC_CONTENT_TYPE_URLENCODED;
|
||||
}
|
||||
|
||||
return builder.result;
|
||||
}
|
||||
|
||||
int rc_api_process_fetch_achievement_info_response(rc_api_fetch_achievement_info_response_t* response, const char* server_response) {
|
||||
rc_api_server_response_t response_obj;
|
||||
|
||||
memset(&response_obj, 0, sizeof(response_obj));
|
||||
response_obj.body = server_response;
|
||||
response_obj.body_length = rc_json_get_object_string_length(server_response);
|
||||
|
||||
return rc_api_process_fetch_achievement_info_server_response(response, &response_obj);
|
||||
}
|
||||
|
||||
int rc_api_process_fetch_achievement_info_server_response(rc_api_fetch_achievement_info_response_t* response, const rc_api_server_response_t* server_response) {
|
||||
rc_api_achievement_awarded_entry_t* entry;
|
||||
rc_json_field_t array_field;
|
||||
rc_json_iterator_t iterator;
|
||||
uint32_t timet;
|
||||
int result;
|
||||
|
||||
rc_json_field_t fields[] = {
|
||||
RC_JSON_NEW_FIELD("Success"),
|
||||
RC_JSON_NEW_FIELD("Error"),
|
||||
RC_JSON_NEW_FIELD("AchievementID"),
|
||||
RC_JSON_NEW_FIELD("Response")
|
||||
/* unused fields
|
||||
RC_JSON_NEW_FIELD("Offset"),
|
||||
RC_JSON_NEW_FIELD("Count"),
|
||||
RC_JSON_NEW_FIELD("FriendsOnly")
|
||||
* unused fields */
|
||||
};
|
||||
|
||||
rc_json_field_t response_fields[] = {
|
||||
RC_JSON_NEW_FIELD("NumEarned"),
|
||||
RC_JSON_NEW_FIELD("TotalPlayers"),
|
||||
RC_JSON_NEW_FIELD("GameID"),
|
||||
RC_JSON_NEW_FIELD("RecentWinner") /* array */
|
||||
};
|
||||
|
||||
rc_json_field_t entry_fields[] = {
|
||||
RC_JSON_NEW_FIELD("User"),
|
||||
RC_JSON_NEW_FIELD("DateAwarded")
|
||||
};
|
||||
|
||||
memset(response, 0, sizeof(*response));
|
||||
rc_buffer_init(&response->response.buffer);
|
||||
|
||||
result = rc_json_parse_server_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0]));
|
||||
if (result != RC_OK)
|
||||
return result;
|
||||
|
||||
if (!rc_json_get_required_unum(&response->id, &response->response, &fields[2], "AchievementID"))
|
||||
return RC_MISSING_VALUE;
|
||||
if (!rc_json_get_required_object(response_fields, sizeof(response_fields) / sizeof(response_fields[0]), &response->response, &fields[3], "Response"))
|
||||
return RC_MISSING_VALUE;
|
||||
|
||||
if (!rc_json_get_required_unum(&response->num_awarded, &response->response, &response_fields[0], "NumEarned"))
|
||||
return RC_MISSING_VALUE;
|
||||
if (!rc_json_get_required_unum(&response->num_players, &response->response, &response_fields[1], "TotalPlayers"))
|
||||
return RC_MISSING_VALUE;
|
||||
if (!rc_json_get_required_unum(&response->game_id, &response->response, &response_fields[2], "GameID"))
|
||||
return RC_MISSING_VALUE;
|
||||
|
||||
if (!rc_json_get_required_array(&response->num_recently_awarded, &array_field, &response->response, &response_fields[3], "RecentWinner"))
|
||||
return RC_MISSING_VALUE;
|
||||
|
||||
if (response->num_recently_awarded) {
|
||||
response->recently_awarded = (rc_api_achievement_awarded_entry_t*)rc_buffer_alloc(&response->response.buffer, response->num_recently_awarded * sizeof(rc_api_achievement_awarded_entry_t));
|
||||
if (!response->recently_awarded)
|
||||
return RC_OUT_OF_MEMORY;
|
||||
|
||||
memset(&iterator, 0, sizeof(iterator));
|
||||
iterator.json = array_field.value_start;
|
||||
iterator.end = array_field.value_end;
|
||||
|
||||
entry = response->recently_awarded;
|
||||
while (rc_json_get_array_entry_object(entry_fields, sizeof(entry_fields) / sizeof(entry_fields[0]), &iterator)) {
|
||||
if (!rc_json_get_required_string(&entry->username, &response->response, &entry_fields[0], "User"))
|
||||
return RC_MISSING_VALUE;
|
||||
|
||||
if (!rc_json_get_required_unum(&timet, &response->response, &entry_fields[1], "DateAwarded"))
|
||||
return RC_MISSING_VALUE;
|
||||
entry->awarded = (time_t)timet;
|
||||
|
||||
++entry;
|
||||
}
|
||||
}
|
||||
|
||||
return RC_OK;
|
||||
}
|
||||
|
||||
void rc_api_destroy_fetch_achievement_info_response(rc_api_fetch_achievement_info_response_t* response) {
|
||||
rc_buffer_destroy(&response->response.buffer);
|
||||
}
|
||||
|
||||
/* --- Fetch Leaderboard Info --- */
|
||||
|
||||
int rc_api_init_fetch_leaderboard_info_request(rc_api_request_t* request, const rc_api_fetch_leaderboard_info_request_t* api_params) {
|
||||
rc_api_url_builder_t builder;
|
||||
|
||||
rc_api_url_build_dorequest_url(request);
|
||||
|
||||
if (api_params->leaderboard_id == 0)
|
||||
return RC_INVALID_STATE;
|
||||
|
||||
rc_url_builder_init(&builder, &request->buffer, 48);
|
||||
rc_url_builder_append_str_param(&builder, "r", "lbinfo");
|
||||
rc_url_builder_append_unum_param(&builder, "i", api_params->leaderboard_id);
|
||||
|
||||
if (api_params->username)
|
||||
rc_url_builder_append_str_param(&builder, "u", api_params->username);
|
||||
else if (api_params->first_entry > 1)
|
||||
rc_url_builder_append_unum_param(&builder, "o", api_params->first_entry - 1); /* number of entries to skip */
|
||||
|
||||
rc_url_builder_append_unum_param(&builder, "c", api_params->count);
|
||||
request->post_data = rc_url_builder_finalize(&builder);
|
||||
request->content_type = RC_CONTENT_TYPE_URLENCODED;
|
||||
|
||||
return builder.result;
|
||||
}
|
||||
|
||||
int rc_api_process_fetch_leaderboard_info_response(rc_api_fetch_leaderboard_info_response_t* response, const char* server_response) {
|
||||
rc_api_server_response_t response_obj;
|
||||
|
||||
memset(&response_obj, 0, sizeof(response_obj));
|
||||
response_obj.body = server_response;
|
||||
response_obj.body_length = rc_json_get_object_string_length(server_response);
|
||||
|
||||
return rc_api_process_fetch_leaderboard_info_server_response(response, &response_obj);
|
||||
}
|
||||
|
||||
int rc_api_process_fetch_leaderboard_info_server_response(rc_api_fetch_leaderboard_info_response_t* response, const rc_api_server_response_t* server_response) {
|
||||
rc_api_lboard_info_entry_t* entry;
|
||||
rc_json_field_t array_field;
|
||||
rc_json_iterator_t iterator;
|
||||
uint32_t timet;
|
||||
int result;
|
||||
size_t len;
|
||||
char format[16];
|
||||
|
||||
rc_json_field_t fields[] = {
|
||||
RC_JSON_NEW_FIELD("Success"),
|
||||
RC_JSON_NEW_FIELD("Error"),
|
||||
RC_JSON_NEW_FIELD("LeaderboardData")
|
||||
};
|
||||
|
||||
rc_json_field_t leaderboarddata_fields[] = {
|
||||
RC_JSON_NEW_FIELD("LBID"),
|
||||
RC_JSON_NEW_FIELD("LBFormat"),
|
||||
RC_JSON_NEW_FIELD("LowerIsBetter"),
|
||||
RC_JSON_NEW_FIELD("LBTitle"),
|
||||
RC_JSON_NEW_FIELD("LBDesc"),
|
||||
RC_JSON_NEW_FIELD("LBMem"),
|
||||
RC_JSON_NEW_FIELD("GameID"),
|
||||
RC_JSON_NEW_FIELD("LBAuthor"),
|
||||
RC_JSON_NEW_FIELD("LBCreated"),
|
||||
RC_JSON_NEW_FIELD("LBUpdated"),
|
||||
RC_JSON_NEW_FIELD("Entries") /* array */
|
||||
/* unused fields
|
||||
RC_JSON_NEW_FIELD("GameTitle"),
|
||||
RC_JSON_NEW_FIELD("ConsoleID"),
|
||||
RC_JSON_NEW_FIELD("ConsoleName"),
|
||||
RC_JSON_NEW_FIELD("ForumTopicID"),
|
||||
RC_JSON_NEW_FIELD("GameIcon")
|
||||
* unused fields */
|
||||
};
|
||||
|
||||
rc_json_field_t entry_fields[] = {
|
||||
RC_JSON_NEW_FIELD("User"),
|
||||
RC_JSON_NEW_FIELD("Rank"),
|
||||
RC_JSON_NEW_FIELD("Index"),
|
||||
RC_JSON_NEW_FIELD("Score"),
|
||||
RC_JSON_NEW_FIELD("DateSubmitted")
|
||||
};
|
||||
|
||||
memset(response, 0, sizeof(*response));
|
||||
rc_buffer_init(&response->response.buffer);
|
||||
|
||||
result = rc_json_parse_server_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0]));
|
||||
if (result != RC_OK)
|
||||
return result;
|
||||
|
||||
if (!rc_json_get_required_object(leaderboarddata_fields, sizeof(leaderboarddata_fields) / sizeof(leaderboarddata_fields[0]), &response->response, &fields[2], "LeaderboardData"))
|
||||
return RC_MISSING_VALUE;
|
||||
|
||||
if (!rc_json_get_required_unum(&response->id, &response->response, &leaderboarddata_fields[0], "LBID"))
|
||||
return RC_MISSING_VALUE;
|
||||
if (!rc_json_get_required_unum(&response->lower_is_better, &response->response, &leaderboarddata_fields[2], "LowerIsBetter"))
|
||||
return RC_MISSING_VALUE;
|
||||
if (!rc_json_get_required_string(&response->title, &response->response, &leaderboarddata_fields[3], "LBTitle"))
|
||||
return RC_MISSING_VALUE;
|
||||
if (!rc_json_get_required_string(&response->description, &response->response, &leaderboarddata_fields[4], "LBDesc"))
|
||||
return RC_MISSING_VALUE;
|
||||
if (!rc_json_get_required_string(&response->definition, &response->response, &leaderboarddata_fields[5], "LBMem"))
|
||||
return RC_MISSING_VALUE;
|
||||
if (!rc_json_get_required_unum(&response->game_id, &response->response, &leaderboarddata_fields[6], "GameID"))
|
||||
return RC_MISSING_VALUE;
|
||||
if (!rc_json_get_required_string(&response->author, &response->response, &leaderboarddata_fields[7], "LBAuthor"))
|
||||
return RC_MISSING_VALUE;
|
||||
if (!rc_json_get_required_datetime(&response->created, &response->response, &leaderboarddata_fields[8], "LBCreated"))
|
||||
return RC_MISSING_VALUE;
|
||||
if (!rc_json_get_required_datetime(&response->updated, &response->response, &leaderboarddata_fields[9], "LBUpdated"))
|
||||
return RC_MISSING_VALUE;
|
||||
|
||||
if (!leaderboarddata_fields[1].value_end)
|
||||
return RC_MISSING_VALUE;
|
||||
len = leaderboarddata_fields[1].value_end - leaderboarddata_fields[1].value_start - 2;
|
||||
if (len < sizeof(format) - 1) {
|
||||
memcpy(format, leaderboarddata_fields[1].value_start + 1, len);
|
||||
format[len] = '\0';
|
||||
response->format = rc_parse_format(format);
|
||||
}
|
||||
else {
|
||||
response->format = RC_FORMAT_VALUE;
|
||||
}
|
||||
|
||||
if (!rc_json_get_required_array(&response->num_entries, &array_field, &response->response, &leaderboarddata_fields[10], "Entries"))
|
||||
return RC_MISSING_VALUE;
|
||||
|
||||
if (response->num_entries) {
|
||||
response->entries = (rc_api_lboard_info_entry_t*)rc_buffer_alloc(&response->response.buffer, response->num_entries * sizeof(rc_api_lboard_info_entry_t));
|
||||
if (!response->entries)
|
||||
return RC_OUT_OF_MEMORY;
|
||||
|
||||
memset(&iterator, 0, sizeof(iterator));
|
||||
iterator.json = array_field.value_start;
|
||||
iterator.end = array_field.value_end;
|
||||
|
||||
entry = response->entries;
|
||||
while (rc_json_get_array_entry_object(entry_fields, sizeof(entry_fields) / sizeof(entry_fields[0]), &iterator)) {
|
||||
if (!rc_json_get_required_string(&entry->username, &response->response, &entry_fields[0], "User"))
|
||||
return RC_MISSING_VALUE;
|
||||
|
||||
if (!rc_json_get_required_unum(&entry->rank, &response->response, &entry_fields[1], "Rank"))
|
||||
return RC_MISSING_VALUE;
|
||||
|
||||
if (!rc_json_get_required_unum(&entry->index, &response->response, &entry_fields[2], "Index"))
|
||||
return RC_MISSING_VALUE;
|
||||
|
||||
if (!rc_json_get_required_num(&entry->score, &response->response, &entry_fields[3], "Score"))
|
||||
return RC_MISSING_VALUE;
|
||||
|
||||
if (!rc_json_get_required_unum(&timet, &response->response, &entry_fields[4], "DateSubmitted"))
|
||||
return RC_MISSING_VALUE;
|
||||
entry->submitted = (time_t)timet;
|
||||
|
||||
++entry;
|
||||
}
|
||||
}
|
||||
|
||||
return RC_OK;
|
||||
}
|
||||
|
||||
void rc_api_destroy_fetch_leaderboard_info_response(rc_api_fetch_leaderboard_info_response_t* response) {
|
||||
rc_buffer_destroy(&response->response.buffer);
|
||||
}
|
||||
|
||||
/* --- Fetch Games List --- */
|
||||
|
||||
int rc_api_init_fetch_games_list_request(rc_api_request_t* request, const rc_api_fetch_games_list_request_t* api_params) {
|
||||
rc_api_url_builder_t builder;
|
||||
|
||||
rc_api_url_build_dorequest_url(request);
|
||||
|
||||
if (api_params->console_id == 0)
|
||||
return RC_INVALID_STATE;
|
||||
|
||||
rc_url_builder_init(&builder, &request->buffer, 48);
|
||||
rc_url_builder_append_str_param(&builder, "r", "gameslist");
|
||||
rc_url_builder_append_unum_param(&builder, "c", api_params->console_id);
|
||||
|
||||
request->post_data = rc_url_builder_finalize(&builder);
|
||||
request->content_type = RC_CONTENT_TYPE_URLENCODED;
|
||||
|
||||
return builder.result;
|
||||
}
|
||||
|
||||
int rc_api_process_fetch_games_list_response(rc_api_fetch_games_list_response_t* response, const char* server_response) {
|
||||
rc_api_server_response_t response_obj;
|
||||
|
||||
memset(&response_obj, 0, sizeof(response_obj));
|
||||
response_obj.body = server_response;
|
||||
response_obj.body_length = rc_json_get_object_string_length(server_response);
|
||||
|
||||
return rc_api_process_fetch_games_list_server_response(response, &response_obj);
|
||||
}
|
||||
|
||||
int rc_api_process_fetch_games_list_server_response(rc_api_fetch_games_list_response_t* response, const rc_api_server_response_t* server_response) {
|
||||
rc_api_game_list_entry_t* entry;
|
||||
rc_json_iterator_t iterator;
|
||||
rc_json_field_t field;
|
||||
int result;
|
||||
char* end;
|
||||
|
||||
rc_json_field_t fields[] = {
|
||||
RC_JSON_NEW_FIELD("Success"),
|
||||
RC_JSON_NEW_FIELD("Error"),
|
||||
RC_JSON_NEW_FIELD("Response")
|
||||
};
|
||||
|
||||
memset(response, 0, sizeof(*response));
|
||||
rc_buffer_init(&response->response.buffer);
|
||||
|
||||
result = rc_json_parse_server_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0]));
|
||||
if (result != RC_OK)
|
||||
return result;
|
||||
|
||||
if (!fields[2].value_start) {
|
||||
/* call rc_json_get_required_object to generate the error message */
|
||||
rc_json_get_required_object(NULL, 0, &response->response, &fields[2], "Response");
|
||||
return RC_MISSING_VALUE;
|
||||
}
|
||||
|
||||
response->num_entries = fields[2].array_size;
|
||||
rc_buffer_reserve(&response->response.buffer, response->num_entries * (32 + sizeof(rc_api_game_list_entry_t)));
|
||||
|
||||
response->entries = (rc_api_game_list_entry_t*)rc_buffer_alloc(&response->response.buffer, response->num_entries * sizeof(rc_api_game_list_entry_t));
|
||||
if (!response->entries)
|
||||
return RC_OUT_OF_MEMORY;
|
||||
|
||||
memset(&iterator, 0, sizeof(iterator));
|
||||
iterator.json = fields[2].value_start;
|
||||
iterator.end = fields[2].value_end;
|
||||
|
||||
entry = response->entries;
|
||||
while (rc_json_get_next_object_field(&iterator, &field)) {
|
||||
entry->id = strtol(field.name, &end, 10);
|
||||
|
||||
field.name = "";
|
||||
if (!rc_json_get_string(&entry->name, &response->response.buffer, &field, ""))
|
||||
return RC_MISSING_VALUE;
|
||||
|
||||
++entry;
|
||||
}
|
||||
|
||||
return RC_OK;
|
||||
}
|
||||
|
||||
void rc_api_destroy_fetch_games_list_response(rc_api_fetch_games_list_response_t* response) {
|
||||
rc_buffer_destroy(&response->response.buffer);
|
||||
}
|
|
@ -0,0 +1,648 @@
|
|||
#include "rc_api_runtime.h"
|
||||
#include "rc_api_common.h"
|
||||
|
||||
#include "rc_runtime.h"
|
||||
#include "rc_runtime_types.h"
|
||||
#include "../rc_compat.h"
|
||||
#include "../rhash/md5.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
/* --- Resolve Hash --- */
|
||||
|
||||
int rc_api_init_resolve_hash_request(rc_api_request_t* request, const rc_api_resolve_hash_request_t* api_params) {
|
||||
rc_api_url_builder_t builder;
|
||||
|
||||
rc_api_url_build_dorequest_url(request);
|
||||
|
||||
if (!api_params->game_hash || !*api_params->game_hash)
|
||||
return RC_INVALID_STATE;
|
||||
|
||||
rc_url_builder_init(&builder, &request->buffer, 48);
|
||||
rc_url_builder_append_str_param(&builder, "r", "gameid");
|
||||
rc_url_builder_append_str_param(&builder, "m", api_params->game_hash);
|
||||
request->post_data = rc_url_builder_finalize(&builder);
|
||||
request->content_type = RC_CONTENT_TYPE_URLENCODED;
|
||||
|
||||
return builder.result;
|
||||
}
|
||||
|
||||
int rc_api_process_resolve_hash_response(rc_api_resolve_hash_response_t* response, const char* server_response) {
|
||||
rc_api_server_response_t response_obj;
|
||||
|
||||
memset(&response_obj, 0, sizeof(response_obj));
|
||||
response_obj.body = server_response;
|
||||
response_obj.body_length = rc_json_get_object_string_length(server_response);
|
||||
|
||||
return rc_api_process_resolve_hash_server_response(response, &response_obj);
|
||||
}
|
||||
|
||||
int rc_api_process_resolve_hash_server_response(rc_api_resolve_hash_response_t* response, const rc_api_server_response_t* server_response) {
|
||||
int result;
|
||||
rc_json_field_t fields[] = {
|
||||
RC_JSON_NEW_FIELD("Success"),
|
||||
RC_JSON_NEW_FIELD("Error"),
|
||||
RC_JSON_NEW_FIELD("GameID")
|
||||
};
|
||||
|
||||
memset(response, 0, sizeof(*response));
|
||||
rc_buffer_init(&response->response.buffer);
|
||||
|
||||
result = rc_json_parse_server_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0]));
|
||||
if (result != RC_OK)
|
||||
return result;
|
||||
|
||||
rc_json_get_required_unum(&response->game_id, &response->response, &fields[2], "GameID");
|
||||
return RC_OK;
|
||||
}
|
||||
|
||||
void rc_api_destroy_resolve_hash_response(rc_api_resolve_hash_response_t* response) {
|
||||
rc_buffer_destroy(&response->response.buffer);
|
||||
}
|
||||
|
||||
/* --- Fetch Game Data --- */
|
||||
|
||||
int rc_api_init_fetch_game_data_request(rc_api_request_t* request, const rc_api_fetch_game_data_request_t* api_params) {
|
||||
rc_api_url_builder_t builder;
|
||||
|
||||
rc_api_url_build_dorequest_url(request);
|
||||
|
||||
if (api_params->game_id == 0)
|
||||
return RC_INVALID_STATE;
|
||||
|
||||
rc_url_builder_init(&builder, &request->buffer, 48);
|
||||
if (rc_api_url_build_dorequest(&builder, "patch", api_params->username, api_params->api_token)) {
|
||||
rc_url_builder_append_unum_param(&builder, "g", api_params->game_id);
|
||||
request->post_data = rc_url_builder_finalize(&builder);
|
||||
request->content_type = RC_CONTENT_TYPE_URLENCODED;
|
||||
}
|
||||
|
||||
return builder.result;
|
||||
}
|
||||
|
||||
int rc_api_process_fetch_game_data_response(rc_api_fetch_game_data_response_t* response, const char* server_response) {
|
||||
rc_api_server_response_t response_obj;
|
||||
|
||||
memset(&response_obj, 0, sizeof(response_obj));
|
||||
response_obj.body = server_response;
|
||||
response_obj.body_length = rc_json_get_object_string_length(server_response);
|
||||
|
||||
return rc_api_process_fetch_game_data_server_response(response, &response_obj);
|
||||
}
|
||||
|
||||
static int rc_parse_achievement_type(const char* type)
|
||||
{
|
||||
if (strcmp(type, "missable") == 0)
|
||||
return RC_ACHIEVEMENT_TYPE_MISSABLE;
|
||||
|
||||
if (strcmp(type, "win_condition") == 0)
|
||||
return RC_ACHIEVEMENT_TYPE_WIN;
|
||||
|
||||
if (strcmp(type, "progression") == 0)
|
||||
return RC_ACHIEVEMENT_TYPE_PROGRESSION;
|
||||
|
||||
return RC_ACHIEVEMENT_TYPE_STANDARD;
|
||||
}
|
||||
|
||||
int rc_api_process_fetch_game_data_server_response(rc_api_fetch_game_data_response_t* response, const rc_api_server_response_t* server_response) {
|
||||
rc_api_achievement_definition_t* achievement;
|
||||
rc_api_leaderboard_definition_t* leaderboard;
|
||||
rc_json_field_t array_field;
|
||||
rc_json_iterator_t iterator;
|
||||
const char* str;
|
||||
const char* last_author = "";
|
||||
const char* last_author_field = "";
|
||||
size_t last_author_len = 0;
|
||||
size_t len;
|
||||
uint32_t timet;
|
||||
int result;
|
||||
char format[16];
|
||||
|
||||
rc_json_field_t fields[] = {
|
||||
RC_JSON_NEW_FIELD("Success"),
|
||||
RC_JSON_NEW_FIELD("Error"),
|
||||
RC_JSON_NEW_FIELD("PatchData") /* nested object */
|
||||
};
|
||||
|
||||
rc_json_field_t patchdata_fields[] = {
|
||||
RC_JSON_NEW_FIELD("ID"),
|
||||
RC_JSON_NEW_FIELD("Title"),
|
||||
RC_JSON_NEW_FIELD("ConsoleID"),
|
||||
RC_JSON_NEW_FIELD("ImageIcon"),
|
||||
RC_JSON_NEW_FIELD("RichPresencePatch"),
|
||||
RC_JSON_NEW_FIELD("Achievements"), /* array */
|
||||
RC_JSON_NEW_FIELD("Leaderboards") /* array */
|
||||
};
|
||||
|
||||
rc_json_field_t achievement_fields[] = {
|
||||
RC_JSON_NEW_FIELD("ID"),
|
||||
RC_JSON_NEW_FIELD("Title"),
|
||||
RC_JSON_NEW_FIELD("Description"),
|
||||
RC_JSON_NEW_FIELD("Flags"),
|
||||
RC_JSON_NEW_FIELD("Points"),
|
||||
RC_JSON_NEW_FIELD("MemAddr"),
|
||||
RC_JSON_NEW_FIELD("Author"),
|
||||
RC_JSON_NEW_FIELD("BadgeName"),
|
||||
RC_JSON_NEW_FIELD("Created"),
|
||||
RC_JSON_NEW_FIELD("Modified"),
|
||||
RC_JSON_NEW_FIELD("Type"),
|
||||
RC_JSON_NEW_FIELD("Rarity"),
|
||||
RC_JSON_NEW_FIELD("RarityHardcore")
|
||||
};
|
||||
|
||||
rc_json_field_t leaderboard_fields[] = {
|
||||
RC_JSON_NEW_FIELD("ID"),
|
||||
RC_JSON_NEW_FIELD("Title"),
|
||||
RC_JSON_NEW_FIELD("Description"),
|
||||
RC_JSON_NEW_FIELD("Mem"),
|
||||
RC_JSON_NEW_FIELD("Format"),
|
||||
RC_JSON_NEW_FIELD("LowerIsBetter"),
|
||||
RC_JSON_NEW_FIELD("Hidden")
|
||||
};
|
||||
|
||||
memset(response, 0, sizeof(*response));
|
||||
rc_buffer_init(&response->response.buffer);
|
||||
|
||||
result = rc_json_parse_server_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0]));
|
||||
if (result != RC_OK || !response->response.succeeded)
|
||||
return result;
|
||||
|
||||
if (!rc_json_get_required_object(patchdata_fields, sizeof(patchdata_fields) / sizeof(patchdata_fields[0]), &response->response, &fields[2], "PatchData"))
|
||||
return RC_MISSING_VALUE;
|
||||
|
||||
if (!rc_json_get_required_unum(&response->id, &response->response, &patchdata_fields[0], "ID"))
|
||||
return RC_MISSING_VALUE;
|
||||
if (!rc_json_get_required_string(&response->title, &response->response, &patchdata_fields[1], "Title"))
|
||||
return RC_MISSING_VALUE;
|
||||
if (!rc_json_get_required_unum(&response->console_id, &response->response, &patchdata_fields[2], "ConsoleID"))
|
||||
return RC_MISSING_VALUE;
|
||||
|
||||
/* ImageIcon will be '/Images/0123456.png' - only return the '0123456' */
|
||||
if (patchdata_fields[3].value_end) {
|
||||
str = patchdata_fields[3].value_end - 5;
|
||||
if (memcmp(str, ".png\"", 5) == 0) {
|
||||
patchdata_fields[3].value_end -= 5;
|
||||
|
||||
while (str > patchdata_fields[3].value_start && str[-1] != '/')
|
||||
--str;
|
||||
|
||||
patchdata_fields[3].value_start = str;
|
||||
}
|
||||
}
|
||||
rc_json_get_optional_string(&response->image_name, &response->response, &patchdata_fields[3], "ImageIcon", "");
|
||||
|
||||
/* estimate the amount of space necessary to store the rich presence script, achievements, and leaderboards.
|
||||
determine how much space each takes as a string in the JSON, then subtract out the non-data (field names, punctuation)
|
||||
and add space for the structures. */
|
||||
len = patchdata_fields[4].value_end - patchdata_fields[4].value_start; /* rich presence */
|
||||
|
||||
len += (patchdata_fields[5].value_end - patchdata_fields[5].value_start) - /* achievements */
|
||||
patchdata_fields[5].array_size * (130 - sizeof(rc_api_achievement_definition_t));
|
||||
|
||||
len += (patchdata_fields[6].value_end - patchdata_fields[6].value_start) - /* leaderboards */
|
||||
patchdata_fields[6].array_size * (60 - sizeof(rc_api_leaderboard_definition_t));
|
||||
|
||||
rc_buffer_reserve(&response->response.buffer, len);
|
||||
/* end estimation */
|
||||
|
||||
rc_json_get_optional_string(&response->rich_presence_script, &response->response, &patchdata_fields[4], "RichPresencePatch", "");
|
||||
if (!response->rich_presence_script)
|
||||
response->rich_presence_script = "";
|
||||
|
||||
if (!rc_json_get_required_array(&response->num_achievements, &array_field, &response->response, &patchdata_fields[5], "Achievements"))
|
||||
return RC_MISSING_VALUE;
|
||||
|
||||
if (response->num_achievements) {
|
||||
response->achievements = (rc_api_achievement_definition_t*)rc_buffer_alloc(&response->response.buffer, response->num_achievements * sizeof(rc_api_achievement_definition_t));
|
||||
if (!response->achievements)
|
||||
return RC_OUT_OF_MEMORY;
|
||||
|
||||
memset(&iterator, 0, sizeof(iterator));
|
||||
iterator.json = array_field.value_start;
|
||||
iterator.end = array_field.value_end;
|
||||
|
||||
achievement = response->achievements;
|
||||
while (rc_json_get_array_entry_object(achievement_fields, sizeof(achievement_fields) / sizeof(achievement_fields[0]), &iterator)) {
|
||||
if (!rc_json_get_required_unum(&achievement->id, &response->response, &achievement_fields[0], "ID"))
|
||||
return RC_MISSING_VALUE;
|
||||
if (!rc_json_get_required_string(&achievement->title, &response->response, &achievement_fields[1], "Title"))
|
||||
return RC_MISSING_VALUE;
|
||||
if (!rc_json_get_required_string(&achievement->description, &response->response, &achievement_fields[2], "Description"))
|
||||
return RC_MISSING_VALUE;
|
||||
if (!rc_json_get_required_unum(&achievement->category, &response->response, &achievement_fields[3], "Flags"))
|
||||
return RC_MISSING_VALUE;
|
||||
if (!rc_json_get_required_unum(&achievement->points, &response->response, &achievement_fields[4], "Points"))
|
||||
return RC_MISSING_VALUE;
|
||||
if (!rc_json_get_required_string(&achievement->definition, &response->response, &achievement_fields[5], "MemAddr"))
|
||||
return RC_MISSING_VALUE;
|
||||
if (!rc_json_get_required_string(&achievement->badge_name, &response->response, &achievement_fields[7], "BadgeName"))
|
||||
return RC_MISSING_VALUE;
|
||||
|
||||
len = achievement_fields[6].value_end - achievement_fields[6].value_start;
|
||||
if (len == last_author_len && memcmp(achievement_fields[6].value_start, last_author_field, len) == 0) {
|
||||
achievement->author = last_author;
|
||||
}
|
||||
else {
|
||||
if (!rc_json_get_required_string(&achievement->author, &response->response, &achievement_fields[6], "Author"))
|
||||
return RC_MISSING_VALUE;
|
||||
|
||||
last_author = achievement->author;
|
||||
last_author_field = achievement_fields[6].value_start;
|
||||
last_author_len = len;
|
||||
}
|
||||
|
||||
if (!rc_json_get_required_unum(&timet, &response->response, &achievement_fields[8], "Created"))
|
||||
return RC_MISSING_VALUE;
|
||||
achievement->created = (time_t)timet;
|
||||
if (!rc_json_get_required_unum(&timet, &response->response, &achievement_fields[9], "Modified"))
|
||||
return RC_MISSING_VALUE;
|
||||
achievement->updated = (time_t)timet;
|
||||
|
||||
achievement->type = RC_ACHIEVEMENT_TYPE_STANDARD;
|
||||
if (achievement_fields[10].value_end) {
|
||||
len = achievement_fields[10].value_end - achievement_fields[10].value_start - 2;
|
||||
if (len < sizeof(format) - 1) {
|
||||
memcpy(format, achievement_fields[10].value_start + 1, len);
|
||||
format[len] = '\0';
|
||||
achievement->type = rc_parse_achievement_type(format);
|
||||
}
|
||||
}
|
||||
|
||||
/* legacy support : if title contains[m], change type to missable and remove[m] from title */
|
||||
if (memcmp(achievement->title, "[m]", 3) == 0) {
|
||||
len = 3;
|
||||
while (achievement->title[len] == ' ')
|
||||
++len;
|
||||
achievement->title += len;
|
||||
achievement->type = RC_ACHIEVEMENT_TYPE_MISSABLE;
|
||||
}
|
||||
else if (achievement_fields[1].value_end && memcmp(achievement_fields[1].value_end - 4, "[m]", 3) == 0) {
|
||||
len = strlen(achievement->title) - 3;
|
||||
while (achievement->title[len - 1] == ' ')
|
||||
--len;
|
||||
((char*)achievement->title)[len] = '\0';
|
||||
achievement->type = RC_ACHIEVEMENT_TYPE_MISSABLE;
|
||||
}
|
||||
|
||||
rc_json_get_optional_float(&achievement->rarity, &achievement_fields[11], "Rarity", 100.0);
|
||||
rc_json_get_optional_float(&achievement->rarity_hardcore, &achievement_fields[12], "RarityHardcore", 100.0);
|
||||
|
||||
++achievement;
|
||||
}
|
||||
}
|
||||
|
||||
if (!rc_json_get_required_array(&response->num_leaderboards, &array_field, &response->response, &patchdata_fields[6], "Leaderboards"))
|
||||
return RC_MISSING_VALUE;
|
||||
|
||||
if (response->num_leaderboards) {
|
||||
response->leaderboards = (rc_api_leaderboard_definition_t*)rc_buffer_alloc(&response->response.buffer, response->num_leaderboards * sizeof(rc_api_leaderboard_definition_t));
|
||||
if (!response->leaderboards)
|
||||
return RC_OUT_OF_MEMORY;
|
||||
|
||||
memset(&iterator, 0, sizeof(iterator));
|
||||
iterator.json = array_field.value_start;
|
||||
iterator.end = array_field.value_end;
|
||||
|
||||
leaderboard = response->leaderboards;
|
||||
while (rc_json_get_array_entry_object(leaderboard_fields, sizeof(leaderboard_fields) / sizeof(leaderboard_fields[0]), &iterator)) {
|
||||
if (!rc_json_get_required_unum(&leaderboard->id, &response->response, &leaderboard_fields[0], "ID"))
|
||||
return RC_MISSING_VALUE;
|
||||
if (!rc_json_get_required_string(&leaderboard->title, &response->response, &leaderboard_fields[1], "Title"))
|
||||
return RC_MISSING_VALUE;
|
||||
if (!rc_json_get_required_string(&leaderboard->description, &response->response, &leaderboard_fields[2], "Description"))
|
||||
return RC_MISSING_VALUE;
|
||||
if (!rc_json_get_required_string(&leaderboard->definition, &response->response, &leaderboard_fields[3], "Mem"))
|
||||
return RC_MISSING_VALUE;
|
||||
rc_json_get_optional_bool(&result, &leaderboard_fields[5], "LowerIsBetter", 0);
|
||||
leaderboard->lower_is_better = (uint8_t)result;
|
||||
rc_json_get_optional_bool(&result, &leaderboard_fields[6], "Hidden", 0);
|
||||
leaderboard->hidden = (uint8_t)result;
|
||||
|
||||
if (!leaderboard_fields[4].value_end)
|
||||
return RC_MISSING_VALUE;
|
||||
len = leaderboard_fields[4].value_end - leaderboard_fields[4].value_start - 2;
|
||||
if (len < sizeof(format) - 1) {
|
||||
memcpy(format, leaderboard_fields[4].value_start + 1, len);
|
||||
format[len] = '\0';
|
||||
leaderboard->format = rc_parse_format(format);
|
||||
}
|
||||
else {
|
||||
leaderboard->format = RC_FORMAT_VALUE;
|
||||
}
|
||||
|
||||
++leaderboard;
|
||||
}
|
||||
}
|
||||
|
||||
return RC_OK;
|
||||
}
|
||||
|
||||
void rc_api_destroy_fetch_game_data_response(rc_api_fetch_game_data_response_t* response) {
|
||||
rc_buffer_destroy(&response->response.buffer);
|
||||
}
|
||||
|
||||
/* --- Ping --- */
|
||||
|
||||
int rc_api_init_ping_request(rc_api_request_t* request, const rc_api_ping_request_t* api_params) {
|
||||
rc_api_url_builder_t builder;
|
||||
|
||||
rc_api_url_build_dorequest_url(request);
|
||||
|
||||
if (api_params->game_id == 0)
|
||||
return RC_INVALID_STATE;
|
||||
|
||||
rc_url_builder_init(&builder, &request->buffer, 48);
|
||||
if (rc_api_url_build_dorequest(&builder, "ping", api_params->username, api_params->api_token)) {
|
||||
rc_url_builder_append_unum_param(&builder, "g", api_params->game_id);
|
||||
|
||||
if (api_params->rich_presence && *api_params->rich_presence)
|
||||
rc_url_builder_append_str_param(&builder, "m", api_params->rich_presence);
|
||||
|
||||
if (api_params->game_hash && *api_params->game_hash) {
|
||||
rc_url_builder_append_unum_param(&builder, "h", api_params->hardcore);
|
||||
rc_url_builder_append_str_param(&builder, "x", api_params->game_hash);
|
||||
}
|
||||
|
||||
request->post_data = rc_url_builder_finalize(&builder);
|
||||
request->content_type = RC_CONTENT_TYPE_URLENCODED;
|
||||
}
|
||||
|
||||
return builder.result;
|
||||
}
|
||||
|
||||
int rc_api_process_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_NEW_FIELD("Success"),
|
||||
RC_JSON_NEW_FIELD("Error")
|
||||
};
|
||||
|
||||
memset(response, 0, sizeof(*response));
|
||||
rc_buffer_init(&response->response.buffer);
|
||||
|
||||
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) {
|
||||
rc_buffer_destroy(&response->response.buffer);
|
||||
}
|
||||
|
||||
/* --- Award Achievement --- */
|
||||
|
||||
int rc_api_init_award_achievement_request(rc_api_request_t* request, const rc_api_award_achievement_request_t* api_params) {
|
||||
rc_api_url_builder_t builder;
|
||||
char buffer[33];
|
||||
md5_state_t md5;
|
||||
md5_byte_t digest[16];
|
||||
|
||||
rc_api_url_build_dorequest_url(request);
|
||||
|
||||
if (api_params->achievement_id == 0)
|
||||
return RC_INVALID_STATE;
|
||||
|
||||
rc_url_builder_init(&builder, &request->buffer, 96);
|
||||
if (rc_api_url_build_dorequest(&builder, "awardachievement", api_params->username, api_params->api_token)) {
|
||||
rc_url_builder_append_unum_param(&builder, "a", api_params->achievement_id);
|
||||
rc_url_builder_append_unum_param(&builder, "h", api_params->hardcore ? 1 : 0);
|
||||
if (api_params->game_hash && *api_params->game_hash)
|
||||
rc_url_builder_append_str_param(&builder, "m", api_params->game_hash);
|
||||
|
||||
/* Evaluate the signature. */
|
||||
md5_init(&md5);
|
||||
snprintf(buffer, sizeof(buffer), "%u", api_params->achievement_id);
|
||||
md5_append(&md5, (md5_byte_t*)buffer, (int)strlen(buffer));
|
||||
md5_append(&md5, (md5_byte_t*)api_params->username, (int)strlen(api_params->username));
|
||||
snprintf(buffer, sizeof(buffer), "%d", api_params->hardcore ? 1 : 0);
|
||||
md5_append(&md5, (md5_byte_t*)buffer, (int)strlen(buffer));
|
||||
md5_finish(&md5, digest);
|
||||
rc_format_md5(buffer, digest);
|
||||
rc_url_builder_append_str_param(&builder, "v", buffer);
|
||||
|
||||
request->post_data = rc_url_builder_finalize(&builder);
|
||||
request->content_type = RC_CONTENT_TYPE_URLENCODED;
|
||||
}
|
||||
|
||||
return builder.result;
|
||||
}
|
||||
|
||||
int rc_api_process_award_achievement_response(rc_api_award_achievement_response_t* response, const char* server_response) {
|
||||
rc_api_server_response_t response_obj;
|
||||
|
||||
memset(&response_obj, 0, sizeof(response_obj));
|
||||
response_obj.body = server_response;
|
||||
response_obj.body_length = rc_json_get_object_string_length(server_response);
|
||||
|
||||
return rc_api_process_award_achievement_server_response(response, &response_obj);
|
||||
}
|
||||
|
||||
int rc_api_process_award_achievement_server_response(rc_api_award_achievement_response_t* response, const rc_api_server_response_t* server_response) {
|
||||
int result;
|
||||
rc_json_field_t fields[] = {
|
||||
RC_JSON_NEW_FIELD("Success"),
|
||||
RC_JSON_NEW_FIELD("Error"),
|
||||
RC_JSON_NEW_FIELD("Score"),
|
||||
RC_JSON_NEW_FIELD("SoftcoreScore"),
|
||||
RC_JSON_NEW_FIELD("AchievementID"),
|
||||
RC_JSON_NEW_FIELD("AchievementsRemaining")
|
||||
};
|
||||
|
||||
memset(response, 0, sizeof(*response));
|
||||
rc_buffer_init(&response->response.buffer);
|
||||
|
||||
result = rc_json_parse_server_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0]));
|
||||
if (result != RC_OK)
|
||||
return result;
|
||||
|
||||
if (!response->response.succeeded) {
|
||||
if (response->response.error_message &&
|
||||
memcmp(response->response.error_message, "User already has", 16) == 0) {
|
||||
/* not really an error, the achievement is unlocked, just not by the current call.
|
||||
* hardcore: User already has hardcore and regular achievements awarded.
|
||||
* non-hardcore: User already has this achievement awarded.
|
||||
*/
|
||||
response->response.succeeded = 1;
|
||||
} else {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
rc_json_get_optional_unum(&response->new_player_score, &fields[2], "Score", 0);
|
||||
rc_json_get_optional_unum(&response->new_player_score_softcore, &fields[3], "SoftcoreScore", 0);
|
||||
rc_json_get_optional_unum(&response->awarded_achievement_id, &fields[4], "AchievementID", 0);
|
||||
rc_json_get_optional_unum(&response->achievements_remaining, &fields[5], "AchievementsRemaining", (unsigned)-1);
|
||||
|
||||
return RC_OK;
|
||||
}
|
||||
|
||||
void rc_api_destroy_award_achievement_response(rc_api_award_achievement_response_t* response) {
|
||||
rc_buffer_destroy(&response->response.buffer);
|
||||
}
|
||||
|
||||
/* --- Submit Leaderboard Entry --- */
|
||||
|
||||
int rc_api_init_submit_lboard_entry_request(rc_api_request_t* request, const rc_api_submit_lboard_entry_request_t* api_params) {
|
||||
rc_api_url_builder_t builder;
|
||||
char buffer[33];
|
||||
md5_state_t md5;
|
||||
md5_byte_t digest[16];
|
||||
|
||||
rc_api_url_build_dorequest_url(request);
|
||||
|
||||
if (api_params->leaderboard_id == 0)
|
||||
return RC_INVALID_STATE;
|
||||
|
||||
rc_url_builder_init(&builder, &request->buffer, 96);
|
||||
if (rc_api_url_build_dorequest(&builder, "submitlbentry", api_params->username, api_params->api_token)) {
|
||||
rc_url_builder_append_unum_param(&builder, "i", api_params->leaderboard_id);
|
||||
rc_url_builder_append_num_param(&builder, "s", api_params->score);
|
||||
|
||||
if (api_params->game_hash && *api_params->game_hash)
|
||||
rc_url_builder_append_str_param(&builder, "m", api_params->game_hash);
|
||||
|
||||
/* Evaluate the signature. */
|
||||
md5_init(&md5);
|
||||
snprintf(buffer, sizeof(buffer), "%u", api_params->leaderboard_id);
|
||||
md5_append(&md5, (md5_byte_t*)buffer, (int)strlen(buffer));
|
||||
md5_append(&md5, (md5_byte_t*)api_params->username, (int)strlen(api_params->username));
|
||||
snprintf(buffer, sizeof(buffer), "%d", api_params->score);
|
||||
md5_append(&md5, (md5_byte_t*)buffer, (int)strlen(buffer));
|
||||
md5_finish(&md5, digest);
|
||||
rc_format_md5(buffer, digest);
|
||||
rc_url_builder_append_str_param(&builder, "v", buffer);
|
||||
|
||||
request->post_data = rc_url_builder_finalize(&builder);
|
||||
request->content_type = RC_CONTENT_TYPE_URLENCODED;
|
||||
}
|
||||
|
||||
return builder.result;
|
||||
}
|
||||
|
||||
int rc_api_process_submit_lboard_entry_response(rc_api_submit_lboard_entry_response_t* response, const char* server_response) {
|
||||
rc_api_server_response_t response_obj;
|
||||
|
||||
memset(&response_obj, 0, sizeof(response_obj));
|
||||
response_obj.body = server_response;
|
||||
response_obj.body_length = rc_json_get_object_string_length(server_response);
|
||||
|
||||
return rc_api_process_submit_lboard_entry_server_response(response, &response_obj);
|
||||
}
|
||||
|
||||
int rc_api_process_submit_lboard_entry_server_response(rc_api_submit_lboard_entry_response_t* response, const rc_api_server_response_t* server_response) {
|
||||
rc_api_lboard_entry_t* entry;
|
||||
rc_json_field_t array_field;
|
||||
rc_json_iterator_t iterator;
|
||||
const char* str;
|
||||
int result;
|
||||
|
||||
rc_json_field_t fields[] = {
|
||||
RC_JSON_NEW_FIELD("Success"),
|
||||
RC_JSON_NEW_FIELD("Error"),
|
||||
RC_JSON_NEW_FIELD("Response") /* nested object */
|
||||
};
|
||||
|
||||
rc_json_field_t response_fields[] = {
|
||||
RC_JSON_NEW_FIELD("Score"),
|
||||
RC_JSON_NEW_FIELD("BestScore"),
|
||||
RC_JSON_NEW_FIELD("RankInfo"), /* nested object */
|
||||
RC_JSON_NEW_FIELD("TopEntries") /* array */
|
||||
/* unused fields
|
||||
RC_JSON_NEW_FIELD("LBData"), / * array * /
|
||||
RC_JSON_NEW_FIELD("ScoreFormatted"),
|
||||
RC_JSON_NEW_FIELD("TopEntriesFriends") / * array * /
|
||||
* unused fields */
|
||||
};
|
||||
|
||||
/* unused fields
|
||||
rc_json_field_t lbdata_fields[] = {
|
||||
RC_JSON_NEW_FIELD("Format"),
|
||||
RC_JSON_NEW_FIELD("LeaderboardID"),
|
||||
RC_JSON_NEW_FIELD("GameID"),
|
||||
RC_JSON_NEW_FIELD("Title"),
|
||||
RC_JSON_NEW_FIELD("LowerIsBetter")
|
||||
};
|
||||
* unused fields */
|
||||
|
||||
rc_json_field_t entry_fields[] = {
|
||||
RC_JSON_NEW_FIELD("User"),
|
||||
RC_JSON_NEW_FIELD("Rank"),
|
||||
RC_JSON_NEW_FIELD("Score")
|
||||
/* unused fields
|
||||
RC_JSON_NEW_FIELD("DateSubmitted")
|
||||
* unused fields */
|
||||
};
|
||||
|
||||
rc_json_field_t rank_info_fields[] = {
|
||||
RC_JSON_NEW_FIELD("Rank"),
|
||||
RC_JSON_NEW_FIELD("NumEntries")
|
||||
/* unused fields
|
||||
RC_JSON_NEW_FIELD("LowerIsBetter"),
|
||||
RC_JSON_NEW_FIELD("UserRank")
|
||||
* unused fields */
|
||||
};
|
||||
|
||||
memset(response, 0, sizeof(*response));
|
||||
rc_buffer_init(&response->response.buffer);
|
||||
|
||||
result = rc_json_parse_server_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0]));
|
||||
if (result != RC_OK || !response->response.succeeded)
|
||||
return result;
|
||||
|
||||
if (!rc_json_get_required_object(response_fields, sizeof(response_fields) / sizeof(response_fields[0]), &response->response, &fields[2], "Response"))
|
||||
return RC_MISSING_VALUE;
|
||||
if (!rc_json_get_required_num(&response->submitted_score, &response->response, &response_fields[0], "Score"))
|
||||
return RC_MISSING_VALUE;
|
||||
if (!rc_json_get_required_num(&response->best_score, &response->response, &response_fields[1], "BestScore"))
|
||||
return RC_MISSING_VALUE;
|
||||
|
||||
if (!rc_json_get_required_object(rank_info_fields, sizeof(rank_info_fields) / sizeof(rank_info_fields[0]), &response->response, &response_fields[2], "RankInfo"))
|
||||
return RC_MISSING_VALUE;
|
||||
if (!rc_json_get_required_unum(&response->new_rank, &response->response, &rank_info_fields[0], "Rank"))
|
||||
return RC_MISSING_VALUE;
|
||||
if (!rc_json_get_required_string(&str, &response->response, &rank_info_fields[1], "NumEntries"))
|
||||
return RC_MISSING_VALUE;
|
||||
response->num_entries = (unsigned)atoi(str);
|
||||
|
||||
if (!rc_json_get_required_array(&response->num_top_entries, &array_field, &response->response, &response_fields[3], "TopEntries"))
|
||||
return RC_MISSING_VALUE;
|
||||
|
||||
if (response->num_top_entries) {
|
||||
response->top_entries = (rc_api_lboard_entry_t*)rc_buffer_alloc(&response->response.buffer, response->num_top_entries * sizeof(rc_api_lboard_entry_t));
|
||||
if (!response->top_entries)
|
||||
return RC_OUT_OF_MEMORY;
|
||||
|
||||
memset(&iterator, 0, sizeof(iterator));
|
||||
iterator.json = array_field.value_start;
|
||||
iterator.end = array_field.value_end;
|
||||
|
||||
entry = response->top_entries;
|
||||
while (rc_json_get_array_entry_object(entry_fields, sizeof(entry_fields) / sizeof(entry_fields[0]), &iterator)) {
|
||||
if (!rc_json_get_required_string(&entry->username, &response->response, &entry_fields[0], "User"))
|
||||
return RC_MISSING_VALUE;
|
||||
|
||||
if (!rc_json_get_required_unum(&entry->rank, &response->response, &entry_fields[1], "Rank"))
|
||||
return RC_MISSING_VALUE;
|
||||
|
||||
if (!rc_json_get_required_num(&entry->score, &response->response, &entry_fields[2], "Score"))
|
||||
return RC_MISSING_VALUE;
|
||||
|
||||
++entry;
|
||||
}
|
||||
}
|
||||
|
||||
return RC_OK;
|
||||
}
|
||||
|
||||
void rc_api_destroy_submit_lboard_entry_response(rc_api_submit_lboard_entry_response_t* response) {
|
||||
rc_buffer_destroy(&response->response.buffer);
|
||||
}
|
|
@ -0,0 +1,254 @@
|
|||
#include "rc_api_user.h"
|
||||
#include "rc_api_common.h"
|
||||
|
||||
#include "../rc_version.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
/* --- Login --- */
|
||||
|
||||
int rc_api_init_login_request(rc_api_request_t* request, const rc_api_login_request_t* api_params) {
|
||||
rc_api_url_builder_t builder;
|
||||
|
||||
rc_api_url_build_dorequest_url(request);
|
||||
|
||||
if (!api_params->username || !*api_params->username)
|
||||
return RC_INVALID_STATE;
|
||||
|
||||
rc_url_builder_init(&builder, &request->buffer, 48);
|
||||
rc_url_builder_append_str_param(&builder, "r", "login2");
|
||||
rc_url_builder_append_str_param(&builder, "u", api_params->username);
|
||||
|
||||
if (api_params->password && api_params->password[0])
|
||||
rc_url_builder_append_str_param(&builder, "p", api_params->password);
|
||||
else if (api_params->api_token && api_params->api_token[0])
|
||||
rc_url_builder_append_str_param(&builder, "t", api_params->api_token);
|
||||
else
|
||||
return RC_INVALID_STATE;
|
||||
|
||||
request->post_data = rc_url_builder_finalize(&builder);
|
||||
request->content_type = RC_CONTENT_TYPE_URLENCODED;
|
||||
|
||||
return builder.result;
|
||||
}
|
||||
|
||||
int rc_api_process_login_response(rc_api_login_response_t* response, const char* server_response) {
|
||||
rc_api_server_response_t response_obj;
|
||||
|
||||
memset(&response_obj, 0, sizeof(response_obj));
|
||||
response_obj.body = server_response;
|
||||
response_obj.body_length = rc_json_get_object_string_length(server_response);
|
||||
|
||||
return rc_api_process_login_server_response(response, &response_obj);
|
||||
}
|
||||
|
||||
int rc_api_process_login_server_response(rc_api_login_response_t* response, const rc_api_server_response_t* server_response) {
|
||||
int result;
|
||||
rc_json_field_t fields[] = {
|
||||
RC_JSON_NEW_FIELD("Success"),
|
||||
RC_JSON_NEW_FIELD("Error"),
|
||||
RC_JSON_NEW_FIELD("Code"),
|
||||
RC_JSON_NEW_FIELD("User"),
|
||||
RC_JSON_NEW_FIELD("Token"),
|
||||
RC_JSON_NEW_FIELD("Score"),
|
||||
RC_JSON_NEW_FIELD("SoftcoreScore"),
|
||||
RC_JSON_NEW_FIELD("Messages"),
|
||||
RC_JSON_NEW_FIELD("DisplayName")
|
||||
};
|
||||
|
||||
memset(response, 0, sizeof(*response));
|
||||
rc_buffer_init(&response->response.buffer);
|
||||
|
||||
result = rc_json_parse_server_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0]));
|
||||
if (result != RC_OK || !response->response.succeeded)
|
||||
return result;
|
||||
|
||||
if (!rc_json_get_required_string(&response->username, &response->response, &fields[3], "User"))
|
||||
return RC_MISSING_VALUE;
|
||||
if (!rc_json_get_required_string(&response->api_token, &response->response, &fields[4], "Token"))
|
||||
return RC_MISSING_VALUE;
|
||||
|
||||
rc_json_get_optional_unum(&response->score, &fields[5], "Score", 0);
|
||||
rc_json_get_optional_unum(&response->score_softcore, &fields[6], "SoftcoreScore", 0);
|
||||
rc_json_get_optional_unum(&response->num_unread_messages, &fields[7], "Messages", 0);
|
||||
|
||||
rc_json_get_optional_string(&response->display_name, &response->response, &fields[8], "DisplayName", response->username);
|
||||
|
||||
return RC_OK;
|
||||
}
|
||||
|
||||
void rc_api_destroy_login_response(rc_api_login_response_t* response) {
|
||||
rc_buffer_destroy(&response->response.buffer);
|
||||
}
|
||||
|
||||
/* --- Start Session --- */
|
||||
|
||||
int rc_api_init_start_session_request(rc_api_request_t* request, const rc_api_start_session_request_t* api_params) {
|
||||
rc_api_url_builder_t builder;
|
||||
|
||||
rc_api_url_build_dorequest_url(request);
|
||||
|
||||
if (api_params->game_id == 0)
|
||||
return RC_INVALID_STATE;
|
||||
|
||||
rc_url_builder_init(&builder, &request->buffer, 48);
|
||||
if (rc_api_url_build_dorequest(&builder, "startsession", api_params->username, api_params->api_token)) {
|
||||
rc_url_builder_append_unum_param(&builder, "g", api_params->game_id);
|
||||
|
||||
if (api_params->game_hash && *api_params->game_hash) {
|
||||
rc_url_builder_append_unum_param(&builder, "h", api_params->hardcore);
|
||||
rc_url_builder_append_str_param(&builder, "m", api_params->game_hash);
|
||||
}
|
||||
|
||||
rc_url_builder_append_str_param(&builder, "l", RCHEEVOS_VERSION_STRING);
|
||||
|
||||
request->post_data = rc_url_builder_finalize(&builder);
|
||||
request->content_type = RC_CONTENT_TYPE_URLENCODED;
|
||||
}
|
||||
|
||||
return builder.result;
|
||||
}
|
||||
|
||||
int rc_api_process_start_session_response(rc_api_start_session_response_t* response, const char* server_response) {
|
||||
rc_api_server_response_t response_obj;
|
||||
|
||||
memset(&response_obj, 0, sizeof(response_obj));
|
||||
response_obj.body = server_response;
|
||||
response_obj.body_length = rc_json_get_object_string_length(server_response);
|
||||
|
||||
return rc_api_process_start_session_server_response(response, &response_obj);
|
||||
}
|
||||
|
||||
int rc_api_process_start_session_server_response(rc_api_start_session_response_t* response, const rc_api_server_response_t* server_response) {
|
||||
rc_api_unlock_entry_t* unlock;
|
||||
rc_json_field_t array_field;
|
||||
rc_json_iterator_t iterator;
|
||||
uint32_t timet;
|
||||
int result;
|
||||
|
||||
rc_json_field_t fields[] = {
|
||||
RC_JSON_NEW_FIELD("Success"),
|
||||
RC_JSON_NEW_FIELD("Error"),
|
||||
RC_JSON_NEW_FIELD("Unlocks"),
|
||||
RC_JSON_NEW_FIELD("HardcoreUnlocks"),
|
||||
RC_JSON_NEW_FIELD("ServerNow")
|
||||
};
|
||||
|
||||
rc_json_field_t unlock_entry_fields[] = {
|
||||
RC_JSON_NEW_FIELD("ID"),
|
||||
RC_JSON_NEW_FIELD("When")
|
||||
};
|
||||
|
||||
memset(response, 0, sizeof(*response));
|
||||
rc_buffer_init(&response->response.buffer);
|
||||
|
||||
result = rc_json_parse_server_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0]));
|
||||
if (result != RC_OK || !response->response.succeeded)
|
||||
return result;
|
||||
|
||||
if (rc_json_get_optional_array(&response->num_unlocks, &array_field, &fields[2], "Unlocks") && response->num_unlocks) {
|
||||
response->unlocks = (rc_api_unlock_entry_t*)rc_buffer_alloc(&response->response.buffer, response->num_unlocks * sizeof(rc_api_unlock_entry_t));
|
||||
if (!response->unlocks)
|
||||
return RC_OUT_OF_MEMORY;
|
||||
|
||||
memset(&iterator, 0, sizeof(iterator));
|
||||
iterator.json = array_field.value_start;
|
||||
iterator.end = array_field.value_end;
|
||||
|
||||
unlock = response->unlocks;
|
||||
while (rc_json_get_array_entry_object(unlock_entry_fields, sizeof(unlock_entry_fields) / sizeof(unlock_entry_fields[0]), &iterator)) {
|
||||
if (!rc_json_get_required_unum(&unlock->achievement_id, &response->response, &unlock_entry_fields[0], "ID"))
|
||||
return RC_MISSING_VALUE;
|
||||
if (!rc_json_get_required_unum(&timet, &response->response, &unlock_entry_fields[1], "When"))
|
||||
return RC_MISSING_VALUE;
|
||||
unlock->when = (time_t)timet;
|
||||
|
||||
++unlock;
|
||||
}
|
||||
}
|
||||
|
||||
if (rc_json_get_optional_array(&response->num_hardcore_unlocks, &array_field, &fields[3], "HardcoreUnlocks") && response->num_hardcore_unlocks) {
|
||||
response->hardcore_unlocks = (rc_api_unlock_entry_t*)rc_buffer_alloc(&response->response.buffer, response->num_hardcore_unlocks * sizeof(rc_api_unlock_entry_t));
|
||||
if (!response->hardcore_unlocks)
|
||||
return RC_OUT_OF_MEMORY;
|
||||
|
||||
memset(&iterator, 0, sizeof(iterator));
|
||||
iterator.json = array_field.value_start;
|
||||
iterator.end = array_field.value_end;
|
||||
|
||||
unlock = response->hardcore_unlocks;
|
||||
while (rc_json_get_array_entry_object(unlock_entry_fields, sizeof(unlock_entry_fields) / sizeof(unlock_entry_fields[0]), &iterator)) {
|
||||
if (!rc_json_get_required_unum(&unlock->achievement_id, &response->response, &unlock_entry_fields[0], "ID"))
|
||||
return RC_MISSING_VALUE;
|
||||
if (!rc_json_get_required_unum(&timet, &response->response, &unlock_entry_fields[1], "When"))
|
||||
return RC_MISSING_VALUE;
|
||||
unlock->when = (time_t)timet;
|
||||
|
||||
++unlock;
|
||||
}
|
||||
}
|
||||
|
||||
rc_json_get_optional_unum(&timet, &fields[4], "ServerNow", 0);
|
||||
response->server_now = (time_t)timet;
|
||||
|
||||
return RC_OK;
|
||||
}
|
||||
|
||||
void rc_api_destroy_start_session_response(rc_api_start_session_response_t* response) {
|
||||
rc_buffer_destroy(&response->response.buffer);
|
||||
}
|
||||
|
||||
/* --- Fetch User Unlocks --- */
|
||||
|
||||
int rc_api_init_fetch_user_unlocks_request(rc_api_request_t* request, const rc_api_fetch_user_unlocks_request_t* api_params) {
|
||||
rc_api_url_builder_t builder;
|
||||
|
||||
rc_api_url_build_dorequest_url(request);
|
||||
|
||||
rc_url_builder_init(&builder, &request->buffer, 48);
|
||||
if (rc_api_url_build_dorequest(&builder, "unlocks", 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, "h", api_params->hardcore ? 1 : 0);
|
||||
request->post_data = rc_url_builder_finalize(&builder);
|
||||
request->content_type = RC_CONTENT_TYPE_URLENCODED;
|
||||
}
|
||||
|
||||
return builder.result;
|
||||
}
|
||||
|
||||
int rc_api_process_fetch_user_unlocks_response(rc_api_fetch_user_unlocks_response_t* response, const char* server_response) {
|
||||
rc_api_server_response_t response_obj;
|
||||
|
||||
memset(&response_obj, 0, sizeof(response_obj));
|
||||
response_obj.body = server_response;
|
||||
response_obj.body_length = rc_json_get_object_string_length(server_response);
|
||||
|
||||
return rc_api_process_fetch_user_unlocks_server_response(response, &response_obj);
|
||||
}
|
||||
|
||||
int rc_api_process_fetch_user_unlocks_server_response(rc_api_fetch_user_unlocks_response_t* response, const rc_api_server_response_t* server_response) {
|
||||
int result;
|
||||
rc_json_field_t fields[] = {
|
||||
RC_JSON_NEW_FIELD("Success"),
|
||||
RC_JSON_NEW_FIELD("Error"),
|
||||
RC_JSON_NEW_FIELD("UserUnlocks")
|
||||
/* unused fields
|
||||
RC_JSON_NEW_FIELD("GameID"),
|
||||
RC_JSON_NEW_FIELD("HardcoreMode")
|
||||
* unused fields */
|
||||
};
|
||||
|
||||
memset(response, 0, sizeof(*response));
|
||||
rc_buffer_init(&response->response.buffer);
|
||||
|
||||
result = rc_json_parse_server_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0]));
|
||||
if (result != RC_OK || !response->response.succeeded)
|
||||
return result;
|
||||
|
||||
result = rc_json_get_required_unum_array(&response->achievement_ids, &response->num_achievement_ids, &response->response, &fields[2], "UserUnlocks");
|
||||
return result;
|
||||
}
|
||||
|
||||
void rc_api_destroy_fetch_user_unlocks_response(rc_api_fetch_user_unlocks_response_t* response) {
|
||||
rc_buffer_destroy(&response->response.buffer);
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,132 @@
|
|||
#ifndef RC_CLIENT_EXTERNAL_H
|
||||
#define RC_CLIENT_EXTERNAL_H
|
||||
|
||||
#include "rc_client.h"
|
||||
|
||||
RC_BEGIN_C_DECLS
|
||||
|
||||
/* NOTE: any function that is passed a callback also needs to be passed a client instance to pass
|
||||
* to the callback, and the external interface has to capture both. */
|
||||
|
||||
typedef void (RC_CCONV *rc_client_external_enable_logging_func_t)(rc_client_t* client, int level, rc_client_message_callback_t callback);
|
||||
typedef void (RC_CCONV *rc_client_external_set_event_handler_func_t)(rc_client_t* client, rc_client_event_handler_t handler);
|
||||
typedef void (RC_CCONV *rc_client_external_set_read_memory_func_t)(rc_client_t* client, rc_client_read_memory_func_t handler);
|
||||
typedef void (RC_CCONV *rc_client_external_set_get_time_millisecs_func_t)(rc_client_t* client, rc_get_time_millisecs_func_t handler);
|
||||
typedef int (RC_CCONV *rc_client_external_can_pause_func_t)(uint32_t* frames_remaining);
|
||||
|
||||
typedef void (RC_CCONV *rc_client_external_set_int_func_t)(int value);
|
||||
typedef int (RC_CCONV *rc_client_external_get_int_func_t)(void);
|
||||
typedef void (RC_CCONV *rc_client_external_set_string_func_t)(const char* value);
|
||||
typedef size_t (RC_CCONV *rc_client_external_copy_string_func_t)(char buffer[], size_t buffer_size);
|
||||
typedef void (RC_CCONV *rc_client_external_action_func_t)(void);
|
||||
|
||||
typedef void (RC_CCONV *rc_client_external_async_handle_func_t)(rc_client_async_handle_t* handle);
|
||||
|
||||
typedef rc_client_async_handle_t* (RC_CCONV *rc_client_external_begin_login_func_t)(rc_client_t* client,
|
||||
const char* username, const char* pass_token, rc_client_callback_t callback, void* callback_userdata);
|
||||
typedef const rc_client_user_t* (RC_CCONV *rc_client_external_get_user_info_func_t)(void);
|
||||
|
||||
typedef rc_client_async_handle_t* (RC_CCONV *rc_client_external_begin_identify_and_load_game_func_t)(
|
||||
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);
|
||||
typedef rc_client_async_handle_t* (RC_CCONV *rc_client_external_begin_load_game_func_t)(rc_client_t* client,
|
||||
const char* hash, rc_client_callback_t callback, void* callback_userdata);
|
||||
typedef rc_client_async_handle_t* (RC_CCONV *rc_client_external_begin_load_subset_t)(rc_client_t* client,
|
||||
uint32_t subset_id, rc_client_callback_t callback, void* callback_userdata);
|
||||
typedef const rc_client_game_t* (RC_CCONV *rc_client_external_get_game_info_func_t)(void);
|
||||
typedef const rc_client_subset_t* (RC_CCONV *rc_client_external_get_subset_info_func_t)(uint32_t subset_id);
|
||||
typedef void (RC_CCONV *rc_client_external_get_user_game_summary_func_t)(rc_client_user_game_summary_t* summary);
|
||||
typedef rc_client_async_handle_t* (RC_CCONV *rc_client_external_begin_change_media_func_t)(rc_client_t* client, const char* file_path,
|
||||
const uint8_t* data, size_t data_size, rc_client_callback_t callback, void* callback_userdata);
|
||||
|
||||
/* NOTE: rc_client_external_create_achievement_list_func_t returns an internal wrapper structure which contains the public list
|
||||
* and a destructor function. */
|
||||
struct rc_client_achievement_list_info_t;
|
||||
typedef struct rc_client_achievement_list_info_t* (RC_CCONV *rc_client_external_create_achievement_list_func_t)(int category, int grouping);
|
||||
typedef const rc_client_achievement_t* (RC_CCONV *rc_client_external_get_achievement_info_func_t)(uint32_t id);
|
||||
|
||||
/* NOTE: rc_client_external_create_leaderboard_list_func_t returns an internal wrapper structure which contains the public list
|
||||
* and a destructor function. */
|
||||
struct rc_client_leaderboard_list_info_t;
|
||||
typedef struct rc_client_leaderboard_list_info_t* (RC_CCONV *rc_client_external_create_leaderboard_list_func_t)(int grouping);
|
||||
typedef const rc_client_leaderboard_t* (RC_CCONV *rc_client_external_get_leaderboard_info_func_t)(uint32_t id);
|
||||
|
||||
/* NOTE: rc_client_external_begin_fetch_leaderboard_entries_func_t and rc_client_external_begin_fetch_leaderboard_entries_around_user_func_t
|
||||
* pass an internal wrapper structure around the list, which contains the public list and a destructor function. */
|
||||
typedef rc_client_async_handle_t* (RC_CCONV *rc_client_external_begin_fetch_leaderboard_entries_func_t)(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);
|
||||
typedef rc_client_async_handle_t* (RC_CCONV *rc_client_external_begin_fetch_leaderboard_entries_around_user_func_t)(rc_client_t* client,
|
||||
uint32_t leaderboard_id, uint32_t count, rc_client_fetch_leaderboard_entries_callback_t callback, void* callback_userdata);
|
||||
|
||||
|
||||
typedef size_t (RC_CCONV *rc_client_external_progress_size_func_t)(void);
|
||||
typedef int (RC_CCONV *rc_client_external_serialize_progress_func_t)(uint8_t* buffer);
|
||||
typedef int (RC_CCONV *rc_client_external_deserialize_progress_func_t)(const uint8_t* buffer);
|
||||
|
||||
typedef struct rc_client_external_t
|
||||
{
|
||||
rc_client_external_action_func_t destroy;
|
||||
|
||||
rc_client_external_enable_logging_func_t enable_logging;
|
||||
rc_client_external_set_event_handler_func_t set_event_handler;
|
||||
rc_client_external_set_read_memory_func_t set_read_memory;
|
||||
rc_client_external_set_get_time_millisecs_func_t set_get_time_millisecs;
|
||||
rc_client_external_set_string_func_t set_host;
|
||||
rc_client_external_copy_string_func_t get_user_agent_clause;
|
||||
|
||||
rc_client_external_set_int_func_t set_hardcore_enabled;
|
||||
rc_client_external_get_int_func_t get_hardcore_enabled;
|
||||
rc_client_external_set_int_func_t set_unofficial_enabled;
|
||||
rc_client_external_get_int_func_t get_unofficial_enabled;
|
||||
rc_client_external_set_int_func_t set_encore_mode_enabled;
|
||||
rc_client_external_get_int_func_t get_encore_mode_enabled;
|
||||
rc_client_external_set_int_func_t set_spectator_mode_enabled;
|
||||
rc_client_external_get_int_func_t get_spectator_mode_enabled;
|
||||
|
||||
rc_client_external_async_handle_func_t abort_async;
|
||||
|
||||
rc_client_external_begin_login_func_t begin_login_with_password;
|
||||
rc_client_external_begin_login_func_t begin_login_with_token;
|
||||
rc_client_external_action_func_t logout;
|
||||
rc_client_external_get_user_info_func_t get_user_info;
|
||||
|
||||
rc_client_external_begin_identify_and_load_game_func_t begin_identify_and_load_game;
|
||||
rc_client_external_begin_load_game_func_t begin_load_game;
|
||||
rc_client_external_get_game_info_func_t get_game_info;
|
||||
rc_client_external_begin_load_subset_t begin_load_subset;
|
||||
rc_client_external_get_subset_info_func_t get_subset_info;
|
||||
rc_client_external_action_func_t unload_game;
|
||||
rc_client_external_get_user_game_summary_func_t get_user_game_summary;
|
||||
rc_client_external_begin_change_media_func_t begin_change_media;
|
||||
|
||||
rc_client_external_create_achievement_list_func_t create_achievement_list;
|
||||
rc_client_external_get_int_func_t has_achievements;
|
||||
rc_client_external_get_achievement_info_func_t get_achievement_info;
|
||||
|
||||
rc_client_external_create_leaderboard_list_func_t create_leaderboard_list;
|
||||
rc_client_external_get_int_func_t has_leaderboards;
|
||||
rc_client_external_get_leaderboard_info_func_t get_leaderboard_info;
|
||||
rc_client_external_begin_fetch_leaderboard_entries_func_t begin_fetch_leaderboard_entries;
|
||||
rc_client_external_begin_fetch_leaderboard_entries_around_user_func_t begin_fetch_leaderboard_entries_around_user;
|
||||
|
||||
rc_client_external_copy_string_func_t get_rich_presence_message;
|
||||
rc_client_external_get_int_func_t has_rich_presence;
|
||||
|
||||
rc_client_external_action_func_t do_frame;
|
||||
rc_client_external_action_func_t idle;
|
||||
rc_client_external_get_int_func_t is_processing_required;
|
||||
rc_client_external_can_pause_func_t can_pause;
|
||||
rc_client_external_action_func_t reset;
|
||||
|
||||
rc_client_external_progress_size_func_t progress_size;
|
||||
rc_client_external_serialize_progress_func_t serialize_progress;
|
||||
rc_client_external_deserialize_progress_func_t deserialize_progress;
|
||||
|
||||
} rc_client_external_t;
|
||||
|
||||
#define RC_CLIENT_EXTERNAL_VERSION 1
|
||||
|
||||
RC_END_C_DECLS
|
||||
|
||||
#endif /* RC_CLIENT_EXTERNAL_H */
|
|
@ -0,0 +1,394 @@
|
|||
#ifndef RC_CLIENT_INTERNAL_H
|
||||
#define RC_CLIENT_INTERNAL_H
|
||||
|
||||
#include "rc_client.h"
|
||||
|
||||
#ifdef RC_CLIENT_SUPPORTS_RAINTEGRATION
|
||||
#include "rc_client_raintegration_internal.h"
|
||||
#endif
|
||||
#ifdef RC_CLIENT_SUPPORTS_EXTERNAL
|
||||
#include "rc_client_external.h"
|
||||
#endif
|
||||
|
||||
#include "rc_compat.h"
|
||||
#include "rc_runtime.h"
|
||||
#include "rc_runtime_types.h"
|
||||
|
||||
RC_BEGIN_C_DECLS
|
||||
|
||||
/*****************************************************************************\
|
||||
| Callbacks |
|
||||
\*****************************************************************************/
|
||||
|
||||
struct rc_api_fetch_game_data_response_t;
|
||||
typedef void (RC_CCONV *rc_client_post_process_game_data_response_t)(const rc_api_server_response_t* server_response,
|
||||
struct rc_api_fetch_game_data_response_t* game_data_response, rc_client_t* client, void* userdata);
|
||||
typedef int (RC_CCONV *rc_client_can_submit_achievement_unlock_t)(uint32_t achievement_id, rc_client_t* client);
|
||||
typedef int (RC_CCONV *rc_client_can_submit_leaderboard_entry_t)(uint32_t leaderboard_id, rc_client_t* client);
|
||||
typedef int (RC_CCONV *rc_client_rich_presence_override_t)(rc_client_t* client, char buffer[], size_t buffersize);
|
||||
|
||||
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;
|
||||
rc_get_time_millisecs_func_t get_time_millisecs;
|
||||
rc_client_post_process_game_data_response_t post_process_game_data_response;
|
||||
rc_client_can_submit_achievement_unlock_t can_submit_achievement_unlock;
|
||||
rc_client_can_submit_leaderboard_entry_t can_submit_leaderboard_entry;
|
||||
rc_client_rich_presence_override_t rich_presence_override;
|
||||
|
||||
void* client_data;
|
||||
} rc_client_callbacks_t;
|
||||
|
||||
struct rc_client_scheduled_callback_data_t;
|
||||
typedef void (RC_CCONV *rc_client_scheduled_callback_t)(struct rc_client_scheduled_callback_data_t* callback_data, rc_client_t* client, rc_clock_t now);
|
||||
|
||||
typedef struct rc_client_scheduled_callback_data_t
|
||||
{
|
||||
rc_clock_t when;
|
||||
uint32_t 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);
|
||||
|
||||
struct rc_client_async_handle_t {
|
||||
uint8_t aborted;
|
||||
};
|
||||
|
||||
int rc_client_async_handle_aborted(rc_client_t* client, rc_client_async_handle_t* async_handle);
|
||||
|
||||
/*****************************************************************************\
|
||||
| Achievements |
|
||||
\*****************************************************************************/
|
||||
|
||||
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;
|
||||
|
||||
struct rc_client_achievement_list_info_t;
|
||||
typedef void (RC_CCONV *rc_client_destroy_achievement_list_func_t)(struct rc_client_achievement_list_info_t* list);
|
||||
|
||||
typedef struct rc_client_achievement_list_info_t {
|
||||
rc_client_achievement_list_t public_;
|
||||
rc_client_destroy_achievement_list_func_t destroy_func;
|
||||
} rc_client_achievement_list_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;
|
||||
|
||||
/*****************************************************************************\
|
||||
| Leaderboard Trackers |
|
||||
\*****************************************************************************/
|
||||
|
||||
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;
|
||||
int32_t 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;
|
||||
|
||||
/*****************************************************************************\
|
||||
| Leaderboards |
|
||||
\*****************************************************************************/
|
||||
|
||||
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;
|
||||
int32_t value;
|
||||
|
||||
uint8_t format;
|
||||
uint8_t pending_events;
|
||||
uint8_t bucket;
|
||||
uint8_t hidden;
|
||||
} rc_client_leaderboard_info_t;
|
||||
|
||||
struct rc_client_leaderboard_list_info_t;
|
||||
typedef void (RC_CCONV *rc_client_destroy_leaderboard_list_func_t)(struct rc_client_leaderboard_list_info_t* list);
|
||||
|
||||
typedef struct rc_client_leaderboard_list_info_t {
|
||||
rc_client_leaderboard_list_t public_;
|
||||
rc_client_destroy_leaderboard_list_func_t destroy_func;
|
||||
} rc_client_leaderboard_list_info_t;
|
||||
|
||||
struct rc_client_leaderboard_entry_list_info_t;
|
||||
typedef void (RC_CCONV *rc_client_destroy_leaderboard_entry_list_func_t)(struct rc_client_leaderboard_entry_list_info_t* list);
|
||||
|
||||
typedef struct rc_client_leaderboard_entry_list_info_t {
|
||||
rc_client_leaderboard_entry_list_t public_;
|
||||
rc_client_destroy_leaderboard_entry_list_func_t destroy_func;
|
||||
} rc_client_leaderboard_entry_list_info_t;
|
||||
|
||||
uint8_t rc_client_map_leaderboard_format(int format);
|
||||
|
||||
/*****************************************************************************\
|
||||
| Subsets |
|
||||
\*****************************************************************************/
|
||||
|
||||
enum {
|
||||
RC_CLIENT_SUBSET_PENDING_EVENT_NONE = 0,
|
||||
RC_CLIENT_SUBSET_PENDING_EVENT_ACHIEVEMENT = (1 << 1),
|
||||
RC_CLIENT_SUBSET_PENDING_EVENT_LEADERBOARD = (1 << 2)
|
||||
};
|
||||
|
||||
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;
|
||||
|
||||
rc_client_async_handle_t* rc_client_begin_load_subset(rc_client_t* client, uint32_t subset_id, rc_client_callback_t callback, void* callback_userdata);
|
||||
|
||||
/*****************************************************************************\
|
||||
| Game |
|
||||
\*****************************************************************************/
|
||||
|
||||
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;
|
||||
|
||||
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_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_buffer_t buffer;
|
||||
} rc_client_game_info_t;
|
||||
|
||||
void rc_client_update_active_achievements(rc_client_game_info_t* game);
|
||||
void rc_client_update_active_leaderboards(rc_client_game_info_t* game);
|
||||
|
||||
/*****************************************************************************\
|
||||
| Client |
|
||||
\*****************************************************************************/
|
||||
|
||||
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
|
||||
};
|
||||
|
||||
enum {
|
||||
RC_CLIENT_DISCONNECT_HIDDEN = 0,
|
||||
RC_CLIENT_DISCONNECT_VISIBLE = (1 << 0),
|
||||
RC_CLIENT_DISCONNECT_SHOW_PENDING = (1 << 1),
|
||||
RC_CLIENT_DISCONNECT_HIDE_PENDING = (1 << 2)
|
||||
};
|
||||
|
||||
struct rc_client_load_state_t;
|
||||
|
||||
typedef struct rc_client_state_t {
|
||||
rc_mutex_t mutex;
|
||||
rc_buffer_t buffer;
|
||||
|
||||
rc_client_scheduled_callback_data_t* scheduled_callbacks;
|
||||
|
||||
#ifdef RC_CLIENT_SUPPORTS_EXTERNAL
|
||||
rc_client_external_t* external_client;
|
||||
#endif
|
||||
#ifdef RC_CLIENT_SUPPORTS_RAINTEGRATION
|
||||
rc_client_raintegration_t* raintegration;
|
||||
#endif
|
||||
|
||||
uint16_t unpaused_frame_decay;
|
||||
uint16_t required_unpaused_frames;
|
||||
|
||||
uint8_t hardcore;
|
||||
uint8_t encore_mode;
|
||||
uint8_t spectator_mode;
|
||||
uint8_t unofficial_enabled;
|
||||
uint8_t log_level;
|
||||
uint8_t user;
|
||||
uint8_t disconnect;
|
||||
uint8_t allow_leaderboards_in_softcore;
|
||||
|
||||
struct rc_client_load_state_t* load;
|
||||
struct rc_client_async_handle_t* async_handles[4];
|
||||
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;
|
||||
};
|
||||
|
||||
/*****************************************************************************\
|
||||
| Helpers |
|
||||
\*****************************************************************************/
|
||||
|
||||
#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, uint8_t* 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_release_leaderboard_tracker(rc_client_game_info_t* game, rc_client_leaderboard_info_t* leaderboard);
|
||||
|
||||
RC_END_C_DECLS
|
||||
|
||||
#endif /* RC_CLIENT_INTERNAL_H */
|
|
@ -0,0 +1,493 @@
|
|||
#include "rc_client_raintegration_internal.h"
|
||||
|
||||
#include "rc_client_internal.h"
|
||||
|
||||
#include "rapi/rc_api_common.h"
|
||||
|
||||
#ifdef RC_CLIENT_SUPPORTS_RAINTEGRATION
|
||||
|
||||
static void rc_client_raintegration_load_dll(rc_client_t* client,
|
||||
const wchar_t* search_directory, rc_client_callback_t callback, void* callback_userdata)
|
||||
{
|
||||
wchar_t sPath[_MAX_PATH];
|
||||
const int nPathSize = sizeof(sPath) / sizeof(sPath[0]);
|
||||
rc_client_raintegration_t* raintegration;
|
||||
int sPathIndex = 0;
|
||||
DWORD dwAttrib;
|
||||
HINSTANCE hDLL;
|
||||
|
||||
if (search_directory) {
|
||||
sPathIndex = swprintf_s(sPath, nPathSize, L"%s\\", search_directory);
|
||||
if (sPathIndex > nPathSize - 22) {
|
||||
callback(RC_INVALID_STATE, "search_directory too long", client, callback_userdata);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
#if defined(_M_X64) || defined(__amd64__)
|
||||
wcscpy_s(&sPath[sPathIndex], nPathSize - sPathIndex, L"RA_Integration-x64.dll");
|
||||
dwAttrib = GetFileAttributesW(sPath);
|
||||
if (dwAttrib == INVALID_FILE_ATTRIBUTES) {
|
||||
wcscpy_s(&sPath[sPathIndex], nPathSize - sPathIndex, L"RA_Integration.dll");
|
||||
dwAttrib = GetFileAttributesW(sPath);
|
||||
}
|
||||
#else
|
||||
wcscpy_s(&sPath[sPathIndex], nPathSize - sPathIndex, L"RA_Integration.dll");
|
||||
dwAttrib = GetFileAttributesW(sPath);
|
||||
#endif
|
||||
|
||||
if (dwAttrib == INVALID_FILE_ATTRIBUTES) {
|
||||
callback(RC_MISSING_VALUE, "RA_Integration.dll not found in search directory", client, callback_userdata);
|
||||
return;
|
||||
}
|
||||
|
||||
hDLL = LoadLibraryW(sPath);
|
||||
if (hDLL == NULL) {
|
||||
char error_message[512];
|
||||
const DWORD last_error = GetLastError();
|
||||
int offset = snprintf(error_message, sizeof(error_message), "Failed to load RA_Integration.dll (%u)", last_error);
|
||||
|
||||
if (last_error != 0) {
|
||||
LPSTR messageBuffer = NULL;
|
||||
const DWORD size = FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
|
||||
NULL, last_error, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)&messageBuffer, 0, NULL);
|
||||
|
||||
snprintf(&error_message[offset], sizeof(error_message) - offset, ": %.*s", size, messageBuffer);
|
||||
|
||||
LocalFree(messageBuffer);
|
||||
}
|
||||
|
||||
callback(RC_ABORTED, error_message, client, callback_userdata);
|
||||
return;
|
||||
}
|
||||
|
||||
raintegration = (rc_client_raintegration_t*)rc_buffer_alloc(&client->state.buffer, sizeof(rc_client_raintegration_t));
|
||||
memset(raintegration, 0, sizeof(*raintegration));
|
||||
raintegration->hDLL = hDLL;
|
||||
|
||||
raintegration->get_version = (rc_client_raintegration_get_string_func_t)GetProcAddress(hDLL, "_RA_IntegrationVersion");
|
||||
raintegration->get_host_url = (rc_client_raintegration_get_string_func_t)GetProcAddress(hDLL, "_RA_HostUrl");
|
||||
raintegration->init_client = (rc_client_raintegration_init_client_func_t)GetProcAddress(hDLL, "_RA_InitClient");
|
||||
raintegration->init_client_offline = (rc_client_raintegration_init_client_func_t)GetProcAddress(hDLL, "_RA_InitOffline");
|
||||
raintegration->shutdown = (rc_client_raintegration_action_func_t)GetProcAddress(hDLL, "_RA_Shutdown");
|
||||
|
||||
raintegration->update_main_window_handle = (rc_client_raintegration_hwnd_action_func_t)GetProcAddress(hDLL, "_RA_UpdateHWnd");
|
||||
|
||||
raintegration->get_external_client = (rc_client_raintegration_get_external_client_func_t)GetProcAddress(hDLL, "_Rcheevos_GetExternalClient");
|
||||
raintegration->get_menu = (rc_client_raintegration_get_menu_func_t)GetProcAddress(hDLL, "_Rcheevos_RAIntegrationGetMenu");
|
||||
raintegration->activate_menu_item = (rc_client_raintegration_activate_menuitem_func_t)GetProcAddress(hDLL, "_Rcheevos_ActivateRAIntegrationMenuItem");
|
||||
raintegration->set_write_memory_function = (rc_client_raintegration_set_write_memory_func_t)GetProcAddress(hDLL, "_Rcheevos_SetRAIntegrationWriteMemoryFunction");
|
||||
raintegration->set_event_handler = (rc_client_raintegration_set_event_handler_func_t)GetProcAddress(hDLL, "_Rcheevos_SetRAIntegrationEventHandler");
|
||||
|
||||
if (!raintegration->get_version ||
|
||||
!raintegration->init_client ||
|
||||
!raintegration->get_external_client) {
|
||||
FreeLibrary(hDLL);
|
||||
|
||||
callback(RC_ABORTED, "One or more required exports was not found in RA_Integration.dll", client, callback_userdata);
|
||||
}
|
||||
else {
|
||||
rc_mutex_lock(&client->state.mutex);
|
||||
client->state.raintegration = raintegration;
|
||||
rc_mutex_unlock(&client->state.mutex);
|
||||
|
||||
RC_CLIENT_LOG_INFO_FORMATTED(client, "RA_Integration.dll %s loaded", client->state.raintegration->get_version());
|
||||
}
|
||||
}
|
||||
|
||||
typedef struct rc_client_version_validation_callback_data_t {
|
||||
rc_client_t* client;
|
||||
rc_client_callback_t callback;
|
||||
void* callback_userdata;
|
||||
HWND main_window_handle;
|
||||
char* client_name;
|
||||
char* client_version;
|
||||
rc_client_async_handle_t async_handle;
|
||||
} rc_client_version_validation_callback_data_t;
|
||||
|
||||
int rc_client_version_less(const char* left, const char* right)
|
||||
{
|
||||
do {
|
||||
int left_len = 0;
|
||||
int right_len = 0;
|
||||
while (*left && *left == '0')
|
||||
++left;
|
||||
while (left[left_len] && left[left_len] != '.')
|
||||
++left_len;
|
||||
while (*right && *right == '0')
|
||||
++right;
|
||||
while (right[right_len] && right[right_len] != '.')
|
||||
++right_len;
|
||||
|
||||
if (left_len != right_len)
|
||||
return (left_len < right_len);
|
||||
|
||||
while (left_len--) {
|
||||
if (*left != *right)
|
||||
return (*left < *right);
|
||||
++left;
|
||||
++right;
|
||||
}
|
||||
|
||||
if (*left == '.')
|
||||
++left;
|
||||
if (*right == '.')
|
||||
++right;
|
||||
} while (*left || *right);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void rc_client_init_raintegration(rc_client_t* client,
|
||||
rc_client_version_validation_callback_data_t* version_validation_callback_data)
|
||||
{
|
||||
rc_client_raintegration_init_client_func_t init_func = client->state.raintegration->init_client;
|
||||
|
||||
if (client->state.raintegration->get_host_url) {
|
||||
const char* host_url = client->state.raintegration->get_host_url();
|
||||
if (host_url) {
|
||||
if (strcmp(host_url, "OFFLINE") != 0) {
|
||||
rc_client_set_host(client, host_url);
|
||||
}
|
||||
else if (client->state.raintegration->init_client_offline) {
|
||||
init_func = client->state.raintegration->init_client_offline;
|
||||
RC_CLIENT_LOG_INFO(client, "Initializing in offline mode");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!init_func || !init_func(version_validation_callback_data->main_window_handle,
|
||||
version_validation_callback_data->client_name,
|
||||
version_validation_callback_data->client_version)) {
|
||||
const char* error_message = "RA_Integration initialization failed";
|
||||
|
||||
rc_client_unload_raintegration(client);
|
||||
|
||||
RC_CLIENT_LOG_ERR(client, error_message);
|
||||
version_validation_callback_data->callback(RC_ABORTED, error_message, client, version_validation_callback_data->callback_userdata);
|
||||
}
|
||||
else {
|
||||
rc_client_external_t* external_client = (rc_client_external_t*)
|
||||
rc_buffer_alloc(&client->state.buffer, sizeof(*external_client));
|
||||
memset(external_client, 0, sizeof(*external_client));
|
||||
|
||||
if (!client->state.raintegration->get_external_client(external_client, RC_CLIENT_EXTERNAL_VERSION)) {
|
||||
const char* error_message = "RA_Integration external client export failed";
|
||||
|
||||
rc_client_unload_raintegration(client);
|
||||
|
||||
RC_CLIENT_LOG_ERR(client, error_message);
|
||||
version_validation_callback_data->callback(RC_ABORTED, error_message, client, version_validation_callback_data->callback_userdata);
|
||||
}
|
||||
else {
|
||||
/* copy state to the external client */
|
||||
if (external_client->enable_logging)
|
||||
external_client->enable_logging(client, client->state.log_level, client->callbacks.log_call);
|
||||
|
||||
if (external_client->set_event_handler)
|
||||
external_client->set_event_handler(client, client->callbacks.event_handler);
|
||||
if (external_client->set_read_memory)
|
||||
external_client->set_read_memory(client, client->callbacks.read_memory);
|
||||
|
||||
if (external_client->set_hardcore_enabled)
|
||||
external_client->set_hardcore_enabled(rc_client_get_hardcore_enabled(client));
|
||||
if (external_client->set_unofficial_enabled)
|
||||
external_client->set_unofficial_enabled(rc_client_get_unofficial_enabled(client));
|
||||
if (external_client->set_encore_mode_enabled)
|
||||
external_client->set_encore_mode_enabled(rc_client_get_encore_mode_enabled(client));
|
||||
if (external_client->set_spectator_mode_enabled)
|
||||
external_client->set_spectator_mode_enabled(rc_client_get_spectator_mode_enabled(client));
|
||||
|
||||
/* attach the external client and call the callback */
|
||||
client->state.external_client = external_client;
|
||||
|
||||
client->state.raintegration->bIsInited = 1;
|
||||
|
||||
version_validation_callback_data->callback(RC_OK, NULL,
|
||||
client, version_validation_callback_data->callback_userdata);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void rc_client_version_validation_callback(const rc_api_server_response_t* server_response, void* callback_data)
|
||||
{
|
||||
rc_client_version_validation_callback_data_t* version_validation_callback_data =
|
||||
(rc_client_version_validation_callback_data_t*)callback_data;
|
||||
rc_client_t* client = version_validation_callback_data->client;
|
||||
|
||||
if (rc_client_async_handle_aborted(client, &version_validation_callback_data->async_handle)) {
|
||||
RC_CLIENT_LOG_VERBOSE(client, "Version validation aborted");
|
||||
}
|
||||
else {
|
||||
rc_api_response_t response;
|
||||
int result;
|
||||
const char* current_version;
|
||||
const char* minimum_version = "";
|
||||
|
||||
rc_json_field_t fields[] = {
|
||||
RC_JSON_NEW_FIELD("Success"),
|
||||
RC_JSON_NEW_FIELD("Error"),
|
||||
RC_JSON_NEW_FIELD("MinimumVersion"),
|
||||
};
|
||||
|
||||
memset(&response, 0, sizeof(response));
|
||||
rc_buffer_init(&response.buffer);
|
||||
|
||||
result = rc_json_parse_server_response(&response, server_response, fields, sizeof(fields) / sizeof(fields[0]));
|
||||
if (result == RC_OK) {
|
||||
if (!rc_json_get_required_string(&minimum_version, &response, &fields[2], "MinimumVersion"))
|
||||
result = RC_MISSING_VALUE;
|
||||
}
|
||||
|
||||
if (result != RC_OK) {
|
||||
RC_CLIENT_LOG_ERR_FORMATTED(client, "Failed to fetch latest integration version: %.*s", server_response->body_length, server_response->body);
|
||||
|
||||
rc_client_unload_raintegration(client);
|
||||
|
||||
version_validation_callback_data->callback(result, rc_error_str(result),
|
||||
client, version_validation_callback_data->callback_userdata);
|
||||
}
|
||||
else {
|
||||
current_version = client->state.raintegration->get_version();
|
||||
|
||||
if (rc_client_version_less(current_version, minimum_version)) {
|
||||
char error_message[256];
|
||||
|
||||
rc_client_unload_raintegration(client);
|
||||
|
||||
snprintf(error_message, sizeof(error_message),
|
||||
"RA_Integration version %s is lower than minimum version %s", current_version, minimum_version);
|
||||
RC_CLIENT_LOG_WARN(client, error_message);
|
||||
version_validation_callback_data->callback(RC_ABORTED, error_message, client, version_validation_callback_data->callback_userdata);
|
||||
}
|
||||
else {
|
||||
RC_CLIENT_LOG_INFO_FORMATTED(client, "Validated RA_Integration version %s (minimum %s)", current_version, minimum_version);
|
||||
|
||||
rc_client_init_raintegration(client, version_validation_callback_data);
|
||||
}
|
||||
}
|
||||
|
||||
rc_buffer_destroy(&response.buffer);
|
||||
}
|
||||
|
||||
free(version_validation_callback_data->client_name);
|
||||
free(version_validation_callback_data->client_version);
|
||||
free(version_validation_callback_data);
|
||||
}
|
||||
|
||||
rc_client_async_handle_t* rc_client_begin_load_raintegration(rc_client_t* client,
|
||||
const wchar_t* search_directory, HWND main_window_handle,
|
||||
const char* client_name, const char* client_version,
|
||||
rc_client_callback_t callback, void* callback_userdata)
|
||||
{
|
||||
rc_client_version_validation_callback_data_t* callback_data;
|
||||
rc_api_url_builder_t builder;
|
||||
rc_api_request_t request;
|
||||
|
||||
if (!client) {
|
||||
callback(RC_INVALID_STATE, "client is required", client, callback_userdata);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (!client_name) {
|
||||
callback(RC_INVALID_STATE, "client_name is required", client, callback_userdata);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (!client_version) {
|
||||
callback(RC_INVALID_STATE, "client_version is required", client, callback_userdata);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (client->state.user != RC_CLIENT_USER_STATE_NONE) {
|
||||
callback(RC_INVALID_STATE, "Cannot initialize RAIntegration after login", client, callback_userdata);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (!client->state.raintegration) {
|
||||
if (!main_window_handle) {
|
||||
callback(RC_INVALID_STATE, "main_window_handle is required", client, callback_userdata);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
rc_client_raintegration_load_dll(client, search_directory, callback, callback_userdata);
|
||||
if (!client->state.raintegration)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (client->state.raintegration->get_host_url) {
|
||||
const char* host_url = client->state.raintegration->get_host_url();
|
||||
if (host_url && strcmp(host_url, "https://retroachievements.org") != 0 &&
|
||||
strcmp(host_url, "OFFLINE") != 0) {
|
||||
/* if the DLL specifies a custom host, use it */
|
||||
rc_client_set_host(client, host_url);
|
||||
}
|
||||
}
|
||||
|
||||
memset(&request, 0, sizeof(request));
|
||||
rc_api_url_build_dorequest_url(&request);
|
||||
rc_url_builder_init(&builder, &request.buffer, 48);
|
||||
rc_url_builder_append_str_param(&builder, "r", "latestintegration");
|
||||
request.post_data = rc_url_builder_finalize(&builder);
|
||||
|
||||
callback_data = calloc(1, sizeof(*callback_data));
|
||||
if (!callback_data) {
|
||||
callback(RC_OUT_OF_MEMORY, rc_error_str(RC_OUT_OF_MEMORY), client, callback_userdata);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
callback_data->client = client;
|
||||
callback_data->callback = callback;
|
||||
callback_data->callback_userdata = callback_userdata;
|
||||
callback_data->client_name = strdup(client_name);
|
||||
callback_data->client_version = strdup(client_version);
|
||||
callback_data->main_window_handle = main_window_handle;
|
||||
|
||||
client->callbacks.server_call(&request, rc_client_version_validation_callback, callback_data, client);
|
||||
return &callback_data->async_handle;
|
||||
}
|
||||
|
||||
void rc_client_raintegration_update_main_window_handle(rc_client_t* client, HWND main_window_handle)
|
||||
{
|
||||
if (client && client->state.raintegration &&
|
||||
client->state.raintegration->bIsInited &&
|
||||
client->state.raintegration->update_main_window_handle)
|
||||
{
|
||||
client->state.raintegration->update_main_window_handle(main_window_handle);
|
||||
}
|
||||
}
|
||||
|
||||
void rc_client_raintegration_set_write_memory_function(rc_client_t* client, rc_client_raintegration_write_memory_func_t handler)
|
||||
{
|
||||
if (client && client->state.raintegration && client->state.raintegration->set_write_memory_function)
|
||||
client->state.raintegration->set_write_memory_function(client, handler);
|
||||
}
|
||||
|
||||
void rc_client_raintegration_set_event_handler(rc_client_t* client,
|
||||
rc_client_raintegration_event_handler_t handler)
|
||||
{
|
||||
if (client && client->state.raintegration && client->state.raintegration->set_event_handler)
|
||||
client->state.raintegration->set_event_handler(client, handler);
|
||||
}
|
||||
|
||||
const rc_client_raintegration_menu_t* rc_client_raintegration_get_menu(const rc_client_t* client)
|
||||
{
|
||||
if (!client || !client->state.raintegration ||
|
||||
!client->state.raintegration->bIsInited ||
|
||||
!client->state.raintegration->get_menu)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return client->state.raintegration->get_menu();
|
||||
}
|
||||
|
||||
void rc_client_raintegration_rebuild_submenu(rc_client_t* client, HMENU hMenu)
|
||||
{
|
||||
HMENU hPopupMenu = NULL;
|
||||
const rc_client_raintegration_menu_t* menu;
|
||||
|
||||
if (!client || !client->state.raintegration)
|
||||
return;
|
||||
|
||||
/* destroy the existing menu */
|
||||
if (client->state.raintegration->hPopupMenu)
|
||||
DestroyMenu(client->state.raintegration->hPopupMenu);
|
||||
|
||||
/* create the popup menu */
|
||||
hPopupMenu = CreatePopupMenu();
|
||||
|
||||
menu = rc_client_raintegration_get_menu(client);
|
||||
if (menu && menu->num_items)
|
||||
{
|
||||
const rc_client_raintegration_menu_item_t* menuitem = menu->items;
|
||||
const rc_client_raintegration_menu_item_t* stop = menu->items + menu->num_items;
|
||||
|
||||
for (; menuitem < stop; ++menuitem)
|
||||
{
|
||||
if (menuitem->id == 0)
|
||||
AppendMenuA(hPopupMenu, MF_SEPARATOR, 0U, NULL);
|
||||
else
|
||||
{
|
||||
UINT flags = MF_STRING;
|
||||
if (menuitem->checked)
|
||||
flags |= MF_CHECKED;
|
||||
if (!menuitem->enabled)
|
||||
flags |= MF_DISABLED | MF_GRAYED;
|
||||
|
||||
AppendMenuA(hPopupMenu, flags, menuitem->id, menuitem->label);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* add/update the item containing the popup menu */
|
||||
{
|
||||
int nIndex = GetMenuItemCount(hMenu);
|
||||
const char* menuText = "&RetroAchievements";
|
||||
char buffer[64];
|
||||
|
||||
UINT flags = MF_POPUP | MF_STRING;
|
||||
if (!menu || !menu->num_items)
|
||||
flags |= MF_DISABLED | MF_GRAYED;
|
||||
|
||||
while (--nIndex >= 0)
|
||||
{
|
||||
if (GetMenuStringA(hMenu, nIndex, buffer, sizeof(buffer) - 1, MF_BYPOSITION))
|
||||
{
|
||||
if (strcmp(buffer, menuText) == 0)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (nIndex == -1)
|
||||
AppendMenuA(hMenu, flags, (UINT_PTR)hPopupMenu, menuText);
|
||||
else
|
||||
ModifyMenuA(hMenu, nIndex, flags | MF_BYPOSITION, (UINT_PTR)hPopupMenu, menuText);
|
||||
}
|
||||
|
||||
client->state.raintegration->hPopupMenu = hPopupMenu;
|
||||
}
|
||||
|
||||
void rc_client_raintegration_update_menu_item(const rc_client_t* client, const rc_client_raintegration_menu_item_t* menuitem)
|
||||
{
|
||||
if (client && client->state.raintegration && client->state.raintegration->hPopupMenu)
|
||||
{
|
||||
UINT flags = MF_STRING;
|
||||
if (menuitem->checked)
|
||||
flags |= MF_CHECKED;
|
||||
|
||||
CheckMenuItem(client->state.raintegration->hPopupMenu, menuitem->id, flags | MF_BYCOMMAND);
|
||||
}
|
||||
}
|
||||
|
||||
int rc_client_raintegration_activate_menu_item(const rc_client_t* client, uint32_t nMenuItemId)
|
||||
{
|
||||
if (!client || !client->state.raintegration || !client->state.raintegration->activate_menu_item)
|
||||
return 0;
|
||||
|
||||
return client->state.raintegration->activate_menu_item(nMenuItemId);
|
||||
}
|
||||
|
||||
void rc_client_unload_raintegration(rc_client_t* client)
|
||||
{
|
||||
HINSTANCE hDLL;
|
||||
|
||||
if (!client || !client->state.raintegration)
|
||||
return;
|
||||
|
||||
RC_CLIENT_LOG_INFO(client, "Unloading RA_Integration")
|
||||
|
||||
if (client->state.raintegration->shutdown)
|
||||
client->state.raintegration->shutdown();
|
||||
|
||||
rc_mutex_lock(&client->state.mutex);
|
||||
hDLL = client->state.raintegration->hDLL;
|
||||
client->state.raintegration = NULL;
|
||||
client->state.external_client = NULL;
|
||||
rc_mutex_unlock(&client->state.mutex);
|
||||
|
||||
if (hDLL)
|
||||
FreeLibrary(hDLL);
|
||||
}
|
||||
|
||||
#endif /* RC_CLIENT_SUPPORTS_RAINTEGRATION */
|
|
@ -0,0 +1,52 @@
|
|||
#ifndef RC_CLIENT_RAINTEGRATION_INTERNAL_H
|
||||
#define RC_CLIENT_RAINTEGRATION_INTERNAL_H
|
||||
|
||||
#include "rc_client_raintegration.h"
|
||||
|
||||
#ifdef RC_CLIENT_SUPPORTS_RAINTEGRATION
|
||||
|
||||
#include "rc_client_external.h"
|
||||
#include "rc_compat.h"
|
||||
|
||||
RC_BEGIN_C_DECLS
|
||||
|
||||
/* RAIntegration follows the same calling convention as rcheevos */
|
||||
|
||||
typedef void (RC_CCONV* rc_client_raintegration_action_func_t)(void);
|
||||
typedef const char* (RC_CCONV* rc_client_raintegration_get_string_func_t)(void);
|
||||
typedef int (RC_CCONV* rc_client_raintegration_init_client_func_t)(HWND hMainWnd, const char* sClientName, const char* sClientVersion);
|
||||
typedef int (RC_CCONV* rc_client_raintegration_get_external_client_func_t)(rc_client_external_t* pClient, int nVersion);
|
||||
typedef void (RC_CCONV* rc_client_raintegration_hwnd_action_func_t)(HWND hWnd);
|
||||
typedef const rc_client_raintegration_menu_t* (RC_CCONV* rc_client_raintegration_get_menu_func_t)(void);
|
||||
typedef int (RC_CCONV* rc_client_raintegration_activate_menuitem_func_t)(uint32_t nMenuItemId);
|
||||
typedef void (RC_CCONV* rc_client_raintegration_set_write_memory_func_t)(rc_client_t* pClient, rc_client_raintegration_write_memory_func_t handler);
|
||||
typedef void (RC_CCONV* rc_client_raintegration_set_event_handler_func_t)(rc_client_t* pClient, rc_client_raintegration_event_handler_t handler);
|
||||
|
||||
typedef struct rc_client_raintegration_t
|
||||
{
|
||||
HINSTANCE hDLL;
|
||||
HMENU hPopupMenu;
|
||||
uint8_t bIsInited;
|
||||
|
||||
rc_client_raintegration_get_string_func_t get_version;
|
||||
rc_client_raintegration_get_string_func_t get_host_url;
|
||||
rc_client_raintegration_init_client_func_t init_client;
|
||||
rc_client_raintegration_init_client_func_t init_client_offline;
|
||||
rc_client_raintegration_action_func_t shutdown;
|
||||
|
||||
rc_client_raintegration_hwnd_action_func_t update_main_window_handle;
|
||||
|
||||
rc_client_raintegration_set_write_memory_func_t set_write_memory_function;
|
||||
rc_client_raintegration_set_event_handler_func_t set_event_handler;
|
||||
rc_client_raintegration_get_menu_func_t get_menu;
|
||||
rc_client_raintegration_activate_menuitem_func_t activate_menu_item;
|
||||
|
||||
rc_client_raintegration_get_external_client_func_t get_external_client;
|
||||
|
||||
} rc_client_raintegration_t;
|
||||
|
||||
RC_END_C_DECLS
|
||||
|
||||
#endif /* RC_CLIENT_SUPPORTS_RAINTEGRATION */
|
||||
|
||||
#endif /* RC_CLIENT_RAINTEGRATION_INTERNAL_H */
|
|
@ -0,0 +1,164 @@
|
|||
#include "rc_compat.h"
|
||||
|
||||
#include <ctype.h>
|
||||
#include <stdarg.h>
|
||||
|
||||
#ifdef RC_C89_HELPERS
|
||||
|
||||
int rc_strncasecmp(const char* left, const char* right, size_t length)
|
||||
{
|
||||
while (length)
|
||||
{
|
||||
if (*left != *right)
|
||||
{
|
||||
const int diff = tolower(*left) - tolower(*right);
|
||||
if (diff != 0)
|
||||
return diff;
|
||||
}
|
||||
|
||||
++left;
|
||||
++right;
|
||||
--length;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int rc_strcasecmp(const char* left, const char* right)
|
||||
{
|
||||
while (*left || *right)
|
||||
{
|
||||
if (*left != *right)
|
||||
{
|
||||
const int diff = tolower(*left) - tolower(*right);
|
||||
if (diff != 0)
|
||||
return diff;
|
||||
}
|
||||
|
||||
++left;
|
||||
++right;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
char* rc_strdup(const char* str)
|
||||
{
|
||||
const size_t length = strlen(str);
|
||||
char* buffer = (char*)malloc(length + 1);
|
||||
if (buffer)
|
||||
memcpy(buffer, str, length + 1);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
int rc_snprintf(char* buffer, size_t size, const char* format, ...)
|
||||
{
|
||||
int result;
|
||||
va_list args;
|
||||
|
||||
va_start(args, format);
|
||||
|
||||
#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
|
||||
|
||||
#if defined(_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);
|
||||
}
|
||||
|
||||
#elif defined(GEKKO)
|
||||
|
||||
/* https://github.com/libretro/RetroArch/pull/16116 */
|
||||
|
||||
void rc_mutex_init(rc_mutex_t* mutex)
|
||||
{
|
||||
LWP_MutexInit(mutex, NULL);
|
||||
}
|
||||
|
||||
void rc_mutex_destroy(rc_mutex_t* mutex)
|
||||
{
|
||||
LWP_MutexDestroy(mutex);
|
||||
}
|
||||
|
||||
void rc_mutex_lock(rc_mutex_t* mutex)
|
||||
{
|
||||
LWP_MutexLock(mutex);
|
||||
}
|
||||
|
||||
void rc_mutex_unlock(rc_mutex_t* mutex)
|
||||
{
|
||||
LWP_MutexUnlock(mutex);
|
||||
}
|
||||
|
||||
#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 */
|
|
@ -0,0 +1,97 @@
|
|||
#ifndef RC_COMPAT_H
|
||||
#define RC_COMPAT_H
|
||||
|
||||
#include "rc_export.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
RC_BEGIN_C_DECLS
|
||||
|
||||
#if defined(MINGW) || defined(__MINGW32__) || defined(__MINGW64__)
|
||||
|
||||
/* MinGW redefinitions */
|
||||
|
||||
#define RC_NO_VARIADIC_MACROS 1
|
||||
|
||||
#elif defined(_MSC_VER)
|
||||
|
||||
/* Visual Studio redefinitions */
|
||||
|
||||
#ifndef strcasecmp
|
||||
#define strcasecmp _stricmp
|
||||
#endif
|
||||
#ifndef strncasecmp
|
||||
#define strncasecmp _strnicmp
|
||||
#endif
|
||||
#ifndef strdup
|
||||
#define strdup _strdup
|
||||
#endif
|
||||
|
||||
#elif __STDC_VERSION__ < 199901L
|
||||
|
||||
/* C89 redefinitions */
|
||||
#define RC_C89_HELPERS 1
|
||||
|
||||
#define RC_NO_VARIADIC_MACROS 1
|
||||
|
||||
#ifndef snprintf
|
||||
extern int rc_snprintf(char* buffer, size_t size, const char* format, ...);
|
||||
#define snprintf rc_snprintf
|
||||
#endif
|
||||
|
||||
#ifndef strncasecmp
|
||||
extern int rc_strncasecmp(const char* left, const char* right, size_t length);
|
||||
#define strncasecmp rc_strncasecmp
|
||||
#endif
|
||||
|
||||
#ifndef strcasecmp
|
||||
extern int rc_strcasecmp(const char* left, const char* right);
|
||||
#define strcasecmp rc_strcasecmp
|
||||
#endif
|
||||
|
||||
#ifndef strdup
|
||||
extern char* rc_strdup(const char* str);
|
||||
#define strdup rc_strdup
|
||||
#endif
|
||||
|
||||
#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
|
||||
|
||||
RC_END_C_DECLS
|
||||
|
||||
#endif /* RC_COMPAT_H */
|
|
@ -0,0 +1,831 @@
|
|||
/* 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_dosbox_pure_settings[] = {
|
||||
{ "dosbox_pure_strict_mode", "false" },
|
||||
{ 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_neocd_settings[] = {
|
||||
{ "neocd_bios", "uni-bios*" },
|
||||
{ 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 },
|
||||
{ "DOSBox-pure", _rc_disallowed_dosbox_pure_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 },
|
||||
{ "NeoCD", _rc_disallowed_neocd_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 uint32_t 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, uint32_t 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;
|
||||
}
|
||||
|
||||
uint8_t* rc_libretro_memory_find_avail(const rc_libretro_memory_regions_t* regions, uint32_t address, uint32_t* avail) {
|
||||
uint32_t 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 = (uint32_t)(size - address);
|
||||
|
||||
return ®ions->data[i][address];
|
||||
}
|
||||
|
||||
address -= (uint32_t)size;
|
||||
}
|
||||
|
||||
if (avail)
|
||||
*avail = 0;
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
uint8_t* rc_libretro_memory_find(const rc_libretro_memory_regions_t* regions, uint32_t address) {
|
||||
return rc_libretro_memory_find_avail(regions, address, NULL);
|
||||
}
|
||||
|
||||
uint32_t rc_libretro_memory_read(const rc_libretro_memory_regions_t* regions, uint32_t address,
|
||||
uint8_t* buffer, uint32_t num_bytes) {
|
||||
uint32_t bytes_read = 0;
|
||||
uint32_t avail;
|
||||
uint32_t i;
|
||||
|
||||
for (i = 0; i < regions->count; ++i) {
|
||||
const size_t size = regions->size[i];
|
||||
if (address >= size) {
|
||||
/* address is not in this block, adjust and look at next block */
|
||||
address -= (unsigned)size;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (regions->data[i] == NULL) /* no memory associated to this block. abort */
|
||||
break;
|
||||
|
||||
avail = (unsigned)(size - address);
|
||||
if (avail >= num_bytes) {
|
||||
/* requested memory is fully within this block, copy and return it */
|
||||
memcpy(buffer, ®ions->data[i][address], num_bytes);
|
||||
bytes_read += num_bytes;
|
||||
return bytes_read;
|
||||
}
|
||||
|
||||
/* copy whatever is available in this block, and adjust for the next block */
|
||||
memcpy(buffer, ®ions->data[i][address], avail);
|
||||
buffer += avail;
|
||||
bytes_read += avail;
|
||||
num_bytes -= avail;
|
||||
address = 0;
|
||||
}
|
||||
|
||||
return bytes_read;
|
||||
}
|
||||
|
||||
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,
|
||||
uint8_t* 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, uint32_t 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 */
|
||||
uint32_t 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. */
|
||||
uint32_t disconnect_mask = (unsigned)desc->disconnect;
|
||||
while (disconnect_mask) {
|
||||
const uint32_t 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];
|
||||
uint32_t i;
|
||||
uint8_t* region_start;
|
||||
uint8_t* 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;
|
||||
uint32_t real_address = console_region->real_address;
|
||||
uint32_t 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",
|
||||
(unsigned)(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",
|
||||
(unsigned)(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 uint32_t rc_libretro_memory_console_region_to_ram_type(uint8_t 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];
|
||||
uint32_t 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 uint32_t type = rc_libretro_memory_console_region_to_ram_type(console_region->type);
|
||||
uint32_t 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", (unsigned)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, uint32_t 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;
|
||||
uint32_t 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);
|
||||
if (m3u_contents) {
|
||||
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 uint32_t rc_libretro_djb2(const char* input)
|
||||
{
|
||||
uint32_t 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, uint32_t game_id, const char hash[33]) {
|
||||
const uint32_t 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));
|
||||
}
|
||||
|
||||
if (hash_set->entries == NULL) /* unexpected, but better than crashing */
|
||||
return;
|
||||
|
||||
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 uint32_t path_djb2 = rc_libretro_djb2(path);
|
||||
struct rc_libretro_hash_entry_t* scan = hash_set->entries;
|
||||
struct rc_libretro_hash_entry_t* stop = scan + hash_set->entries_count;
|
||||
for (; scan < stop; ++scan) {
|
||||
if (scan->path_djb2 == path_djb2)
|
||||
return scan->hash;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int rc_libretro_hash_set_get_game_id(const struct rc_libretro_hash_set_t* hash_set, const char* hash)
|
||||
{
|
||||
struct rc_libretro_hash_entry_t* scan = hash_set->entries;
|
||||
struct rc_libretro_hash_entry_t* stop = scan + hash_set->entries_count;
|
||||
for (; scan < stop; ++scan) {
|
||||
if (memcmp(scan->hash, hash, sizeof(scan->hash)) == 0)
|
||||
return scan->game_id;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,93 @@
|
|||
#ifndef RC_LIBRETRO_H
|
||||
#define RC_LIBRETRO_H
|
||||
|
||||
#include "rc_export.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>
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
RC_BEGIN_C_DECLS
|
||||
|
||||
/*****************************************************************************\
|
||||
| Disallowed Settings |
|
||||
\*****************************************************************************/
|
||||
|
||||
typedef struct rc_disallowed_setting_t
|
||||
{
|
||||
const char* setting;
|
||||
const char* value;
|
||||
} rc_disallowed_setting_t;
|
||||
|
||||
RC_EXPORT const rc_disallowed_setting_t* RC_CCONV rc_libretro_get_disallowed_settings(const char* library_name);
|
||||
RC_EXPORT int RC_CCONV rc_libretro_is_setting_allowed(const rc_disallowed_setting_t* disallowed_settings, const char* setting, const char* value);
|
||||
RC_EXPORT int RC_CCONV rc_libretro_is_system_allowed(const char* library_name, uint32_t console_id);
|
||||
|
||||
/*****************************************************************************\
|
||||
| Memory Mapping |
|
||||
\*****************************************************************************/
|
||||
|
||||
/* specifies a function to call for verbose logging */
|
||||
typedef void (RC_CCONV *rc_libretro_message_callback)(const char*);
|
||||
RC_EXPORT void RC_CCONV 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
|
||||
{
|
||||
uint8_t* data[RC_LIBRETRO_MAX_MEMORY_REGIONS];
|
||||
size_t size[RC_LIBRETRO_MAX_MEMORY_REGIONS];
|
||||
size_t total_size;
|
||||
uint32_t count;
|
||||
} rc_libretro_memory_regions_t;
|
||||
|
||||
typedef struct rc_libretro_core_memory_info_t
|
||||
{
|
||||
uint8_t* data;
|
||||
size_t size;
|
||||
} rc_libretro_core_memory_info_t;
|
||||
|
||||
typedef void (RC_CCONV *rc_libretro_get_core_memory_info_func)(uint32_t id, rc_libretro_core_memory_info_t* info);
|
||||
|
||||
RC_EXPORT int RC_CCONV 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, uint32_t console_id);
|
||||
RC_EXPORT void RC_CCONV rc_libretro_memory_destroy(rc_libretro_memory_regions_t* regions);
|
||||
|
||||
RC_EXPORT uint8_t* RC_CCONV rc_libretro_memory_find(const rc_libretro_memory_regions_t* regions, uint32_t address);
|
||||
RC_EXPORT uint8_t* RC_CCONV rc_libretro_memory_find_avail(const rc_libretro_memory_regions_t* regions, uint32_t address, uint32_t* avail);
|
||||
RC_EXPORT uint32_t RC_CCONV rc_libretro_memory_read(const rc_libretro_memory_regions_t* regions, uint32_t address, uint8_t* buffer, uint32_t num_bytes);
|
||||
|
||||
/*****************************************************************************\
|
||||
| Disk Identification |
|
||||
\*****************************************************************************/
|
||||
|
||||
typedef struct rc_libretro_hash_entry_t
|
||||
{
|
||||
uint32_t path_djb2;
|
||||
uint32_t 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_CCONV *rc_libretro_get_image_path_func)(uint32_t index, char* buffer, size_t buffer_size);
|
||||
|
||||
RC_EXPORT void RC_CCONV 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);
|
||||
RC_EXPORT void RC_CCONV rc_libretro_hash_set_destroy(struct rc_libretro_hash_set_t* hash_set);
|
||||
|
||||
RC_EXPORT void RC_CCONV rc_libretro_hash_set_add(struct rc_libretro_hash_set_t* hash_set,
|
||||
const char* path, uint32_t game_id, const char hash[33]);
|
||||
RC_EXPORT const char* RC_CCONV rc_libretro_hash_set_get_hash(const struct rc_libretro_hash_set_t* hash_set, const char* path);
|
||||
RC_EXPORT int RC_CCONV rc_libretro_hash_set_get_game_id(const struct rc_libretro_hash_set_t* hash_set, const char* hash);
|
||||
|
||||
RC_END_C_DECLS
|
||||
|
||||
#endif /* RC_LIBRETRO_H */
|
|
@ -0,0 +1,188 @@
|
|||
#include "rc_util.h"
|
||||
|
||||
#include "rc_compat.h"
|
||||
#include "rc_error.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#undef DEBUG_BUFFERS
|
||||
|
||||
/* --- rc_buffer --- */
|
||||
|
||||
void rc_buffer_init(rc_buffer_t* buffer)
|
||||
{
|
||||
buffer->chunk.write = buffer->chunk.start = &buffer->data[0];
|
||||
buffer->chunk.end = &buffer->data[sizeof(buffer->data)];
|
||||
buffer->chunk.next = NULL;
|
||||
/* leave buffer->data uninitialized */
|
||||
}
|
||||
|
||||
void rc_buffer_destroy(rc_buffer_t* buffer)
|
||||
{
|
||||
rc_buffer_chunk_t* chunk;
|
||||
#ifdef DEBUG_BUFFERS
|
||||
int count = 0;
|
||||
int wasted = 0;
|
||||
int total = 0;
|
||||
#endif
|
||||
|
||||
/* first chunk is not allocated. skip it. */
|
||||
chunk = buffer->chunk.next;
|
||||
|
||||
/* deallocate any additional buffers */
|
||||
while (chunk)
|
||||
{
|
||||
rc_buffer_chunk_t* next = chunk->next;
|
||||
#ifdef DEBUG_BUFFERS
|
||||
total += (int)(chunk->end - chunk->start);
|
||||
wasted += (int)(chunk->end - chunk->write);
|
||||
++count;
|
||||
#endif
|
||||
free(chunk);
|
||||
chunk = next;
|
||||
}
|
||||
|
||||
#ifdef DEBUG_BUFFERS
|
||||
printf("-- %d allocated buffers (%d/%d used, %d wasted, %0.2f%% efficiency)\n", count,
|
||||
total - wasted, total, wasted, (float)(100.0 - (wasted * 100.0) / total));
|
||||
#endif
|
||||
}
|
||||
|
||||
uint8_t* rc_buffer_reserve(rc_buffer_t* buffer, size_t amount)
|
||||
{
|
||||
rc_buffer_chunk_t* chunk = &buffer->chunk;
|
||||
size_t remaining;
|
||||
while (chunk)
|
||||
{
|
||||
remaining = chunk->end - chunk->write;
|
||||
if (remaining >= amount)
|
||||
return chunk->write;
|
||||
|
||||
if (!chunk->next)
|
||||
{
|
||||
/* allocate a chunk of memory that is a multiple of 256-bytes. the first 32 bytes will be associated
|
||||
* to the chunk header, and the remaining will be used for data.
|
||||
*/
|
||||
const size_t chunk_header_size = sizeof(rc_buffer_chunk_t);
|
||||
const size_t alloc_size = (chunk_header_size + amount + 0xFF) & ~0xFF;
|
||||
chunk->next = (rc_buffer_chunk_t*)malloc(alloc_size);
|
||||
if (!chunk->next)
|
||||
break;
|
||||
|
||||
chunk->next->start = (uint8_t*)chunk->next + chunk_header_size;
|
||||
chunk->next->write = chunk->next->start;
|
||||
chunk->next->end = (uint8_t*)chunk->next + alloc_size;
|
||||
chunk->next->next = NULL;
|
||||
}
|
||||
|
||||
chunk = chunk->next;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void rc_buffer_consume(rc_buffer_t* buffer, const uint8_t* start, uint8_t* end)
|
||||
{
|
||||
rc_buffer_chunk_t* chunk = &buffer->chunk;
|
||||
do
|
||||
{
|
||||
if (chunk->write == start)
|
||||
{
|
||||
size_t offset = (end - chunk->start);
|
||||
offset = (offset + 7) & ~7;
|
||||
chunk->write = &chunk->start[offset];
|
||||
|
||||
if (chunk->write > chunk->end)
|
||||
chunk->write = chunk->end;
|
||||
break;
|
||||
}
|
||||
|
||||
chunk = chunk->next;
|
||||
} while (chunk);
|
||||
}
|
||||
|
||||
void* rc_buffer_alloc(rc_buffer_t* buffer, size_t amount)
|
||||
{
|
||||
uint8_t* ptr = rc_buffer_reserve(buffer, amount);
|
||||
rc_buffer_consume(buffer, ptr, ptr + amount);
|
||||
return (void*)ptr;
|
||||
}
|
||||
|
||||
char* rc_buffer_strncpy(rc_buffer_t* buffer, const char* src, size_t len)
|
||||
{
|
||||
uint8_t* dst = rc_buffer_reserve(buffer, len + 1);
|
||||
memcpy(dst, src, len);
|
||||
dst[len] = '\0';
|
||||
rc_buffer_consume(buffer, dst, dst + len + 2);
|
||||
return (char*)dst;
|
||||
}
|
||||
|
||||
char* rc_buffer_strcpy(rc_buffer_t* buffer, const char* src)
|
||||
{
|
||||
return rc_buffer_strncpy(buffer, src, strlen(src));
|
||||
}
|
||||
|
||||
/* --- other --- */
|
||||
|
||||
void rc_format_md5(char checksum[33], const uint8_t digest[16])
|
||||
{
|
||||
snprintf(checksum, 33, "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
|
||||
digest[0], digest[1], digest[2], digest[3], digest[4], digest[5], digest[6], digest[7],
|
||||
digest[8], digest[9], digest[10], digest[11], digest[12], digest[13], digest[14], digest[15]
|
||||
);
|
||||
}
|
||||
|
||||
uint32_t rc_djb2(const char* input)
|
||||
{
|
||||
uint32_t result = 5381;
|
||||
char c;
|
||||
|
||||
while ((c = *input++) != '\0')
|
||||
result = ((result << 5) + result) + c; /* result = result * 33 + c */
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
const char* rc_error_str(int ret)
|
||||
{
|
||||
switch (ret) {
|
||||
case RC_OK: return "OK";
|
||||
case RC_INVALID_LUA_OPERAND: return "Invalid Lua operand";
|
||||
case RC_INVALID_MEMORY_OPERAND: return "Invalid memory operand";
|
||||
case RC_INVALID_CONST_OPERAND: return "Invalid constant operand";
|
||||
case RC_INVALID_FP_OPERAND: return "Invalid floating-point operand";
|
||||
case RC_INVALID_CONDITION_TYPE: return "Invalid condition type";
|
||||
case RC_INVALID_OPERATOR: return "Invalid operator";
|
||||
case RC_INVALID_REQUIRED_HITS: return "Invalid required hits";
|
||||
case RC_DUPLICATED_START: return "Duplicated start condition";
|
||||
case RC_DUPLICATED_CANCEL: return "Duplicated cancel condition";
|
||||
case RC_DUPLICATED_SUBMIT: return "Duplicated submit condition";
|
||||
case RC_DUPLICATED_VALUE: return "Duplicated value expression";
|
||||
case RC_DUPLICATED_PROGRESS: return "Duplicated progress expression";
|
||||
case RC_MISSING_START: return "Missing start condition";
|
||||
case RC_MISSING_CANCEL: return "Missing cancel condition";
|
||||
case RC_MISSING_SUBMIT: return "Missing submit condition";
|
||||
case RC_MISSING_VALUE: return "Missing value expression";
|
||||
case RC_INVALID_LBOARD_FIELD: return "Invalid field in leaderboard";
|
||||
case RC_MISSING_DISPLAY_STRING: return "Missing display string";
|
||||
case RC_OUT_OF_MEMORY: return "Out of memory";
|
||||
case RC_INVALID_VALUE_FLAG: return "Invalid flag in value expression";
|
||||
case RC_MISSING_VALUE_MEASURED: return "Missing measured flag in value expression";
|
||||
case RC_MULTIPLE_MEASURED: return "Multiple measured targets";
|
||||
case RC_INVALID_MEASURED_TARGET: return "Invalid measured target";
|
||||
case RC_INVALID_COMPARISON: return "Invalid comparison";
|
||||
case RC_INVALID_STATE: return "Invalid state";
|
||||
case RC_INVALID_JSON: return "Invalid JSON";
|
||||
case RC_API_FAILURE: return "API call failed";
|
||||
case RC_LOGIN_REQUIRED: return "Login required";
|
||||
case RC_NO_GAME_LOADED: return "No game loaded";
|
||||
case RC_HARDCORE_DISABLED: return "Hardcore disabled";
|
||||
case RC_ABORTED: return "Aborted";
|
||||
case RC_NO_RESPONSE: return "No response";
|
||||
case RC_ACCESS_DENIED: return "Access denied";
|
||||
case RC_INVALID_CREDENTIALS: return "Invalid credentials";
|
||||
case RC_EXPIRED_TOKEN: return "Expired token";
|
||||
default: return "Unknown error";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
#include "rc_version.h"
|
||||
|
||||
uint32_t rc_version(void)
|
||||
{
|
||||
return RCHEEVOS_VERSION;
|
||||
}
|
||||
|
||||
const char* rc_version_string(void)
|
||||
{
|
||||
return RCHEEVOS_VERSION_STRING;
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
#ifndef RC_VERSION_H
|
||||
#define RC_VERSION_H
|
||||
|
||||
#include "rc_export.h"
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
RC_BEGIN_C_DECLS
|
||||
|
||||
#define RCHEEVOS_VERSION_MAJOR 11
|
||||
#define RCHEEVOS_VERSION_MINOR 1
|
||||
#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
|
||||
|
||||
RC_EXPORT uint32_t rc_version(void);
|
||||
RC_EXPORT const char* rc_version_string(void);
|
||||
|
||||
RC_END_C_DECLS
|
||||
|
||||
#endif /* RC_VERSION_H */
|
|
@ -0,0 +1,118 @@
|
|||
#include "rc_internal.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
void* rc_alloc_scratch(void* pointer, int32_t* offset, uint32_t size, uint32_t alignment, rc_scratch_t* scratch, uint32_t scratch_object_pointer_offset)
|
||||
{
|
||||
void* data;
|
||||
|
||||
/* if we have a real buffer, then allocate the data there */
|
||||
if (pointer)
|
||||
return rc_alloc(pointer, offset, size, alignment, NULL, scratch_object_pointer_offset);
|
||||
|
||||
/* update how much space will be required in the real buffer */
|
||||
{
|
||||
const int32_t aligned_offset = (*offset + alignment - 1) & ~(alignment - 1);
|
||||
*offset += (aligned_offset - *offset);
|
||||
*offset += size;
|
||||
}
|
||||
|
||||
/* find a scratch buffer to hold the temporary data */
|
||||
data = rc_buffer_alloc(&scratch->buffer, size);
|
||||
if (!data) {
|
||||
*offset = RC_OUT_OF_MEMORY;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
void* rc_alloc(void* pointer, int32_t* offset, uint32_t size, uint32_t alignment, rc_scratch_t* scratch, uint32_t scratch_object_pointer_offset) {
|
||||
void* ptr;
|
||||
|
||||
*offset = (*offset + alignment - 1) & ~(alignment - 1);
|
||||
|
||||
if (pointer != 0) {
|
||||
/* valid buffer, grab the next chunk */
|
||||
ptr = (void*)((char*)pointer + *offset);
|
||||
}
|
||||
else if (scratch != 0 && scratch_object_pointer_offset < sizeof(scratch->objs)) {
|
||||
/* only allocate one instance of each object type (indentified by scratch_object_pointer_offset) */
|
||||
void** scratch_object_pointer = (void**)((char*)&scratch->objs + scratch_object_pointer_offset);
|
||||
ptr = *scratch_object_pointer;
|
||||
if (!ptr) {
|
||||
int32_t used;
|
||||
ptr = *scratch_object_pointer = rc_alloc_scratch(NULL, &used, size, alignment, scratch, -1);
|
||||
}
|
||||
}
|
||||
else {
|
||||
/* nowhere to get memory from, return NULL */
|
||||
ptr = NULL;
|
||||
}
|
||||
|
||||
*offset += size;
|
||||
return ptr;
|
||||
}
|
||||
|
||||
char* rc_alloc_str(rc_parse_state_t* parse, const char* text, size_t length) {
|
||||
int32_t used = 0;
|
||||
char* ptr;
|
||||
|
||||
rc_scratch_string_t** next = &parse->scratch.strings;
|
||||
while (*next) {
|
||||
int diff = strncmp(text, (*next)->value, length);
|
||||
if (diff == 0) {
|
||||
diff = (*next)->value[length];
|
||||
if (diff == 0)
|
||||
return (*next)->value;
|
||||
}
|
||||
|
||||
if (diff < 0)
|
||||
next = &(*next)->left;
|
||||
else
|
||||
next = &(*next)->right;
|
||||
}
|
||||
|
||||
*next = (rc_scratch_string_t*)rc_alloc_scratch(NULL, &used, sizeof(rc_scratch_string_t), RC_ALIGNOF(rc_scratch_string_t), &parse->scratch, RC_OFFSETOF(parse->scratch.objs, __rc_scratch_string_t));
|
||||
ptr = (char*)rc_alloc_scratch(parse->buffer, &parse->offset, (uint32_t)length + 1, RC_ALIGNOF(char), &parse->scratch, -1);
|
||||
|
||||
if (!ptr || !*next) {
|
||||
if (parse->offset >= 0)
|
||||
parse->offset = RC_OUT_OF_MEMORY;
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
memcpy(ptr, text, length);
|
||||
ptr[length] = '\0';
|
||||
|
||||
(*next)->left = NULL;
|
||||
(*next)->right = NULL;
|
||||
(*next)->value = ptr;
|
||||
|
||||
return ptr;
|
||||
}
|
||||
|
||||
void rc_init_parse_state(rc_parse_state_t* parse, void* buffer, lua_State* L, int funcs_ndx)
|
||||
{
|
||||
/* could use memset here, but rc_parse_state_t contains a 512 byte buffer that doesn't need to be initialized */
|
||||
parse->offset = 0;
|
||||
parse->L = L;
|
||||
parse->funcs_ndx = funcs_ndx;
|
||||
parse->buffer = buffer;
|
||||
parse->scratch.strings = NULL;
|
||||
rc_buffer_init(&parse->scratch.buffer);
|
||||
memset(&parse->scratch.objs, 0, sizeof(parse->scratch.objs));
|
||||
parse->first_memref = 0;
|
||||
parse->variables = 0;
|
||||
parse->measured_target = 0;
|
||||
parse->lines_read = 0;
|
||||
parse->has_required_hits = 0;
|
||||
parse->measured_as_percent = 0;
|
||||
}
|
||||
|
||||
void rc_destroy_parse_state(rc_parse_state_t* parse)
|
||||
{
|
||||
rc_buffer_destroy(&parse->scratch.buffer);
|
||||
}
|
|
@ -0,0 +1,555 @@
|
|||
#include "rc_internal.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <assert.h>
|
||||
|
||||
static int rc_test_condition_compare(uint32_t value1, uint32_t value2, uint8_t oper) {
|
||||
switch (oper) {
|
||||
case RC_OPERATOR_EQ: return value1 == value2;
|
||||
case RC_OPERATOR_NE: return value1 != value2;
|
||||
case RC_OPERATOR_LT: return value1 < value2;
|
||||
case RC_OPERATOR_LE: return value1 <= value2;
|
||||
case RC_OPERATOR_GT: return value1 > value2;
|
||||
case RC_OPERATOR_GE: return value1 >= value2;
|
||||
default: return 1;
|
||||
}
|
||||
}
|
||||
|
||||
static char rc_condition_determine_comparator(const rc_condition_t* self) {
|
||||
switch (self->oper) {
|
||||
case RC_OPERATOR_EQ:
|
||||
case RC_OPERATOR_NE:
|
||||
case RC_OPERATOR_LT:
|
||||
case RC_OPERATOR_LE:
|
||||
case RC_OPERATOR_GT:
|
||||
case RC_OPERATOR_GE:
|
||||
break;
|
||||
|
||||
default:
|
||||
/* not a comparison. should not be getting compared. but if it is, legacy behavior was to return 1 */
|
||||
return RC_PROCESSING_COMPARE_ALWAYS_TRUE;
|
||||
}
|
||||
|
||||
if ((self->operand1.type == RC_OPERAND_ADDRESS || self->operand1.type == RC_OPERAND_DELTA) &&
|
||||
!self->operand1.value.memref->value.is_indirect && !rc_operand_is_float(&self->operand1)) {
|
||||
/* left side is an integer memory reference */
|
||||
int needs_translate = (self->operand1.size != self->operand1.value.memref->value.size);
|
||||
|
||||
if (self->operand2.type == RC_OPERAND_CONST) {
|
||||
/* right side is a constant */
|
||||
if (self->operand1.type == RC_OPERAND_ADDRESS)
|
||||
return needs_translate ? RC_PROCESSING_COMPARE_MEMREF_TO_CONST_TRANSFORMED : RC_PROCESSING_COMPARE_MEMREF_TO_CONST;
|
||||
|
||||
return needs_translate ? RC_PROCESSING_COMPARE_DELTA_TO_CONST_TRANSFORMED : RC_PROCESSING_COMPARE_DELTA_TO_CONST;
|
||||
}
|
||||
else if ((self->operand2.type == RC_OPERAND_ADDRESS || self->operand2.type == RC_OPERAND_DELTA) &&
|
||||
!self->operand2.value.memref->value.is_indirect && !rc_operand_is_float(&self->operand2)) {
|
||||
/* right side is an integer memory reference */
|
||||
const int is_same_memref = (self->operand1.value.memref == self->operand2.value.memref);
|
||||
needs_translate |= (self->operand2.size != self->operand2.value.memref->value.size);
|
||||
|
||||
if (self->operand1.type == RC_OPERAND_ADDRESS) {
|
||||
if (self->operand2.type == RC_OPERAND_ADDRESS) {
|
||||
if (is_same_memref && !needs_translate) {
|
||||
/* comparing a memref to itself, will evaluate to a constant */
|
||||
return rc_test_condition_compare(0, 0, self->oper) ? RC_PROCESSING_COMPARE_ALWAYS_TRUE : RC_PROCESSING_COMPARE_ALWAYS_FALSE;
|
||||
}
|
||||
|
||||
return needs_translate ? RC_PROCESSING_COMPARE_MEMREF_TO_MEMREF_TRANSFORMED : RC_PROCESSING_COMPARE_MEMREF_TO_MEMREF;
|
||||
}
|
||||
|
||||
assert(self->operand2.type == RC_OPERAND_DELTA);
|
||||
|
||||
if (is_same_memref) {
|
||||
/* delta comparison is optimized to compare with itself (for detecting change) */
|
||||
return needs_translate ? RC_PROCESSING_COMPARE_MEMREF_TO_DELTA_TRANSFORMED : RC_PROCESSING_COMPARE_MEMREF_TO_DELTA;
|
||||
}
|
||||
}
|
||||
else {
|
||||
assert(self->operand1.type == RC_OPERAND_DELTA);
|
||||
|
||||
if (self->operand2.type == RC_OPERAND_ADDRESS) {
|
||||
if (is_same_memref) {
|
||||
/* delta comparison is optimized to compare with itself (for detecting change) */
|
||||
return needs_translate ? RC_PROCESSING_COMPARE_DELTA_TO_MEMREF_TRANSFORMED : RC_PROCESSING_COMPARE_DELTA_TO_MEMREF;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (self->operand1.type == RC_OPERAND_CONST && self->operand2.type == RC_OPERAND_CONST) {
|
||||
/* comparing constants will always generate a constant result */
|
||||
return rc_test_condition_compare(self->operand1.value.num, self->operand2.value.num, self->oper) ?
|
||||
RC_PROCESSING_COMPARE_ALWAYS_TRUE : RC_PROCESSING_COMPARE_ALWAYS_FALSE;
|
||||
}
|
||||
|
||||
return RC_PROCESSING_COMPARE_DEFAULT;
|
||||
}
|
||||
|
||||
static int rc_parse_operator(const char** memaddr) {
|
||||
const char* oper = *memaddr;
|
||||
|
||||
switch (*oper) {
|
||||
case '=':
|
||||
++(*memaddr);
|
||||
(*memaddr) += (**memaddr == '=');
|
||||
return RC_OPERATOR_EQ;
|
||||
|
||||
case '!':
|
||||
if (oper[1] == '=') {
|
||||
(*memaddr) += 2;
|
||||
return RC_OPERATOR_NE;
|
||||
}
|
||||
/* fall through */
|
||||
default:
|
||||
return RC_INVALID_OPERATOR;
|
||||
|
||||
case '<':
|
||||
if (oper[1] == '=') {
|
||||
(*memaddr) += 2;
|
||||
return RC_OPERATOR_LE;
|
||||
}
|
||||
|
||||
++(*memaddr);
|
||||
return RC_OPERATOR_LT;
|
||||
|
||||
case '>':
|
||||
if (oper[1] == '=') {
|
||||
(*memaddr) += 2;
|
||||
return RC_OPERATOR_GE;
|
||||
}
|
||||
|
||||
++(*memaddr);
|
||||
return RC_OPERATOR_GT;
|
||||
|
||||
case '*':
|
||||
++(*memaddr);
|
||||
return RC_OPERATOR_MULT;
|
||||
|
||||
case '/':
|
||||
++(*memaddr);
|
||||
return RC_OPERATOR_DIV;
|
||||
|
||||
case '&':
|
||||
++(*memaddr);
|
||||
return RC_OPERATOR_AND;
|
||||
|
||||
case '^':
|
||||
++(*memaddr);
|
||||
return RC_OPERATOR_XOR;
|
||||
|
||||
case '\0':/* end of string */
|
||||
case '_': /* next condition */
|
||||
case 'S': /* next condset */
|
||||
case ')': /* end of macro */
|
||||
case '$': /* maximum of values */
|
||||
/* valid condition separator, condition may not have an operator */
|
||||
return RC_OPERATOR_NONE;
|
||||
}
|
||||
}
|
||||
|
||||
rc_condition_t* rc_parse_condition(const char** memaddr, rc_parse_state_t* parse, uint8_t is_indirect) {
|
||||
rc_condition_t* self;
|
||||
const char* aux;
|
||||
int result;
|
||||
int can_modify = 0;
|
||||
|
||||
aux = *memaddr;
|
||||
self = RC_ALLOC(rc_condition_t, parse);
|
||||
self->current_hits = 0;
|
||||
self->is_true = 0;
|
||||
self->pause = 0;
|
||||
self->optimized_comparator = RC_PROCESSING_COMPARE_DEFAULT;
|
||||
|
||||
if (*aux != 0 && aux[1] == ':') {
|
||||
switch (*aux) {
|
||||
case 'p': case 'P': self->type = RC_CONDITION_PAUSE_IF; break;
|
||||
case 'r': case 'R': self->type = RC_CONDITION_RESET_IF; break;
|
||||
case 'a': case 'A': self->type = RC_CONDITION_ADD_SOURCE; can_modify = 1; break;
|
||||
case 'b': case 'B': self->type = RC_CONDITION_SUB_SOURCE; can_modify = 1; break;
|
||||
case 'c': case 'C': self->type = RC_CONDITION_ADD_HITS; break;
|
||||
case 'd': case 'D': self->type = RC_CONDITION_SUB_HITS; break;
|
||||
case 'n': case 'N': self->type = RC_CONDITION_AND_NEXT; break;
|
||||
case 'o': case 'O': self->type = RC_CONDITION_OR_NEXT; break;
|
||||
case 'm': case 'M': self->type = RC_CONDITION_MEASURED; break;
|
||||
case 'q': case 'Q': self->type = RC_CONDITION_MEASURED_IF; break;
|
||||
case 'i': case 'I': self->type = RC_CONDITION_ADD_ADDRESS; can_modify = 1; break;
|
||||
case 't': case 'T': self->type = RC_CONDITION_TRIGGER; break;
|
||||
case 'z': case 'Z': self->type = RC_CONDITION_RESET_NEXT_IF; break;
|
||||
case 'g': case 'G':
|
||||
parse->measured_as_percent = 1;
|
||||
self->type = RC_CONDITION_MEASURED;
|
||||
break;
|
||||
/* e f h j k l s u v w x y */
|
||||
default: parse->offset = RC_INVALID_CONDITION_TYPE; return 0;
|
||||
}
|
||||
|
||||
aux += 2;
|
||||
}
|
||||
else {
|
||||
self->type = RC_CONDITION_STANDARD;
|
||||
}
|
||||
|
||||
result = rc_parse_operand(&self->operand1, &aux, is_indirect, parse);
|
||||
if (result < 0) {
|
||||
parse->offset = result;
|
||||
return 0;
|
||||
}
|
||||
|
||||
result = rc_parse_operator(&aux);
|
||||
if (result < 0) {
|
||||
parse->offset = result;
|
||||
return 0;
|
||||
}
|
||||
|
||||
self->oper = (char)result;
|
||||
switch (self->oper) {
|
||||
case RC_OPERATOR_NONE:
|
||||
/* non-modifying statements must have a second operand */
|
||||
if (!can_modify) {
|
||||
/* measured does not require a second operand when used in a value */
|
||||
if (self->type != RC_CONDITION_MEASURED) {
|
||||
parse->offset = RC_INVALID_OPERATOR;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* provide dummy operand of '1' and no required hits */
|
||||
self->operand2.type = RC_OPERAND_CONST;
|
||||
self->operand2.value.num = 1;
|
||||
self->required_hits = 0;
|
||||
*memaddr = aux;
|
||||
return self;
|
||||
|
||||
case RC_OPERATOR_MULT:
|
||||
case RC_OPERATOR_DIV:
|
||||
case RC_OPERATOR_AND:
|
||||
case RC_OPERATOR_XOR:
|
||||
/* modifying operators are only valid on modifying statements */
|
||||
if (can_modify)
|
||||
break;
|
||||
/* fallthrough */
|
||||
|
||||
default:
|
||||
/* comparison operators are not valid on modifying statements */
|
||||
if (can_modify) {
|
||||
switch (self->type) {
|
||||
case RC_CONDITION_ADD_SOURCE:
|
||||
case RC_CONDITION_SUB_SOURCE:
|
||||
case RC_CONDITION_ADD_ADDRESS:
|
||||
/* prevent parse errors on legacy achievements where a condition was present before changing the type */
|
||||
self->oper = RC_OPERATOR_NONE;
|
||||
break;
|
||||
|
||||
default:
|
||||
parse->offset = RC_INVALID_OPERATOR;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
result = rc_parse_operand(&self->operand2, &aux, is_indirect, parse);
|
||||
if (result < 0) {
|
||||
parse->offset = result;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (self->oper == RC_OPERATOR_NONE) {
|
||||
/* if operator is none, explicitly clear out the right side */
|
||||
self->operand2.type = RC_OPERAND_CONST;
|
||||
self->operand2.value.num = 0;
|
||||
}
|
||||
|
||||
if (*aux == '(') {
|
||||
char* end;
|
||||
self->required_hits = (unsigned)strtoul(++aux, &end, 10);
|
||||
|
||||
if (end == aux || *end != ')') {
|
||||
parse->offset = RC_INVALID_REQUIRED_HITS;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* if operator is none, explicitly clear out the required hits */
|
||||
if (self->oper == RC_OPERATOR_NONE)
|
||||
self->required_hits = 0;
|
||||
else
|
||||
parse->has_required_hits = 1;
|
||||
|
||||
aux = end + 1;
|
||||
}
|
||||
else if (*aux == '.') {
|
||||
char* end;
|
||||
self->required_hits = (unsigned)strtoul(++aux, &end, 10);
|
||||
|
||||
if (end == aux || *end != '.') {
|
||||
parse->offset = RC_INVALID_REQUIRED_HITS;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* if operator is none, explicitly clear out the required hits */
|
||||
if (self->oper == RC_OPERATOR_NONE)
|
||||
self->required_hits = 0;
|
||||
else
|
||||
parse->has_required_hits = 1;
|
||||
|
||||
aux = end + 1;
|
||||
}
|
||||
else {
|
||||
self->required_hits = 0;
|
||||
}
|
||||
|
||||
if (parse->buffer != 0)
|
||||
self->optimized_comparator = rc_condition_determine_comparator(self);
|
||||
|
||||
*memaddr = aux;
|
||||
return self;
|
||||
}
|
||||
|
||||
int rc_condition_is_combining(const rc_condition_t* self) {
|
||||
switch (self->type) {
|
||||
case RC_CONDITION_STANDARD:
|
||||
case RC_CONDITION_PAUSE_IF:
|
||||
case RC_CONDITION_RESET_IF:
|
||||
case RC_CONDITION_MEASURED_IF:
|
||||
case RC_CONDITION_TRIGGER:
|
||||
case RC_CONDITION_MEASURED:
|
||||
return 0;
|
||||
|
||||
default:
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
static int rc_test_condition_compare_memref_to_const(rc_condition_t* self) {
|
||||
const uint32_t value1 = self->operand1.value.memref->value.value;
|
||||
const uint32_t 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 uint32_t value1 = (memref1->changed) ? memref1->prior : memref1->value;
|
||||
const uint32_t 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 uint32_t value1 = self->operand1.value.memref->value.value;
|
||||
const uint32_t 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 uint32_t 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 uint32_t value2 = self->operand2.value.num;
|
||||
|
||||
value1.type = RC_VALUE_TYPE_UNSIGNED;
|
||||
value1.value.u32 = (memref1->changed) ? memref1->prior : memref1->value;
|
||||
rc_transform_memref_value(&value1, self->operand1.size);
|
||||
|
||||
return rc_test_condition_compare(value1.value.u32, value2, self->oper);
|
||||
}
|
||||
|
||||
static int rc_test_condition_compare_memref_to_memref_transformed(rc_condition_t* self) {
|
||||
rc_typed_value_t value1, value2;
|
||||
|
||||
value1.type = RC_VALUE_TYPE_UNSIGNED;
|
||||
value1.value.u32 = self->operand1.value.memref->value.value;
|
||||
rc_transform_memref_value(&value1, self->operand1.size);
|
||||
|
||||
value2.type = RC_VALUE_TYPE_UNSIGNED;
|
||||
value2.value.u32 = self->operand2.value.memref->value.value;
|
||||
rc_transform_memref_value(&value2, self->operand2.size);
|
||||
|
||||
return rc_test_condition_compare(value1.value.u32, value2.value.u32, self->oper);
|
||||
}
|
||||
|
||||
static int rc_test_condition_compare_memref_to_delta_transformed(rc_condition_t* self) {
|
||||
const rc_memref_value_t* memref = &self->operand1.value.memref->value;
|
||||
assert(self->operand1.value.memref == self->operand2.value.memref);
|
||||
|
||||
if (memref->changed) {
|
||||
rc_typed_value_t value1, value2;
|
||||
|
||||
value1.type = RC_VALUE_TYPE_UNSIGNED;
|
||||
value1.value.u32 = memref->value;
|
||||
rc_transform_memref_value(&value1, self->operand1.size);
|
||||
|
||||
value2.type = RC_VALUE_TYPE_UNSIGNED;
|
||||
value2.value.u32 = memref->prior;
|
||||
rc_transform_memref_value(&value2, self->operand2.size);
|
||||
|
||||
return rc_test_condition_compare(value1.value.u32, value2.value.u32, self->oper);
|
||||
}
|
||||
|
||||
switch (self->oper) {
|
||||
case RC_OPERATOR_EQ:
|
||||
case RC_OPERATOR_GE:
|
||||
case RC_OPERATOR_LE:
|
||||
return 1;
|
||||
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static int rc_test_condition_compare_delta_to_memref_transformed(rc_condition_t* self) {
|
||||
const rc_memref_value_t* memref = &self->operand1.value.memref->value;
|
||||
assert(self->operand1.value.memref == self->operand2.value.memref);
|
||||
|
||||
if (memref->changed) {
|
||||
rc_typed_value_t value1, value2;
|
||||
|
||||
value1.type = RC_VALUE_TYPE_UNSIGNED;
|
||||
value1.value.u32 = memref->prior;
|
||||
rc_transform_memref_value(&value1, self->operand1.size);
|
||||
|
||||
value2.type = RC_VALUE_TYPE_UNSIGNED;
|
||||
value2.value.u32 = memref->value;
|
||||
rc_transform_memref_value(&value2, self->operand2.size);
|
||||
|
||||
return rc_test_condition_compare(value1.value.u32, value2.value.u32, self->oper);
|
||||
}
|
||||
|
||||
switch (self->oper) {
|
||||
case RC_OPERATOR_EQ:
|
||||
case RC_OPERATOR_GE:
|
||||
case RC_OPERATOR_LE:
|
||||
return 1;
|
||||
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
int rc_test_condition(rc_condition_t* self, rc_eval_state_t* eval_state) {
|
||||
rc_typed_value_t value1, value2;
|
||||
|
||||
if (eval_state->add_value.type != RC_VALUE_TYPE_NONE) {
|
||||
/* if there's an accumulator, we can't use the optimized comparators */
|
||||
rc_evaluate_operand(&value1, &self->operand1, eval_state);
|
||||
rc_typed_value_add(&value1, &eval_state->add_value);
|
||||
} else {
|
||||
/* use an optimized comparator whenever possible */
|
||||
switch (self->optimized_comparator) {
|
||||
case RC_PROCESSING_COMPARE_MEMREF_TO_CONST:
|
||||
return rc_test_condition_compare_memref_to_const(self);
|
||||
case RC_PROCESSING_COMPARE_MEMREF_TO_DELTA:
|
||||
return rc_test_condition_compare_memref_to_delta(self);
|
||||
case RC_PROCESSING_COMPARE_MEMREF_TO_MEMREF:
|
||||
return rc_test_condition_compare_memref_to_memref(self);
|
||||
case RC_PROCESSING_COMPARE_DELTA_TO_CONST:
|
||||
return rc_test_condition_compare_delta_to_const(self);
|
||||
case RC_PROCESSING_COMPARE_DELTA_TO_MEMREF:
|
||||
return rc_test_condition_compare_delta_to_memref(self);
|
||||
case RC_PROCESSING_COMPARE_MEMREF_TO_CONST_TRANSFORMED:
|
||||
return rc_test_condition_compare_memref_to_const_transformed(self);
|
||||
case RC_PROCESSING_COMPARE_MEMREF_TO_DELTA_TRANSFORMED:
|
||||
return rc_test_condition_compare_memref_to_delta_transformed(self);
|
||||
case RC_PROCESSING_COMPARE_MEMREF_TO_MEMREF_TRANSFORMED:
|
||||
return rc_test_condition_compare_memref_to_memref_transformed(self);
|
||||
case RC_PROCESSING_COMPARE_DELTA_TO_CONST_TRANSFORMED:
|
||||
return rc_test_condition_compare_delta_to_const_transformed(self);
|
||||
case RC_PROCESSING_COMPARE_DELTA_TO_MEMREF_TRANSFORMED:
|
||||
return rc_test_condition_compare_delta_to_memref_transformed(self);
|
||||
case RC_PROCESSING_COMPARE_ALWAYS_TRUE:
|
||||
return 1;
|
||||
case RC_PROCESSING_COMPARE_ALWAYS_FALSE:
|
||||
return 0;
|
||||
default:
|
||||
rc_evaluate_operand(&value1, &self->operand1, eval_state);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
rc_evaluate_operand(&value2, &self->operand2, eval_state);
|
||||
|
||||
return rc_typed_value_compare(&value1, &value2, self->oper);
|
||||
}
|
||||
|
||||
void rc_evaluate_condition_value(rc_typed_value_t* value, rc_condition_t* self, rc_eval_state_t* eval_state) {
|
||||
rc_typed_value_t amount;
|
||||
|
||||
rc_evaluate_operand(value, &self->operand1, eval_state);
|
||||
rc_evaluate_operand(&amount, &self->operand2, eval_state);
|
||||
|
||||
switch (self->oper) {
|
||||
case RC_OPERATOR_MULT:
|
||||
rc_typed_value_multiply(value, &amount);
|
||||
break;
|
||||
|
||||
case RC_OPERATOR_DIV:
|
||||
rc_typed_value_divide(value, &amount);
|
||||
break;
|
||||
|
||||
case RC_OPERATOR_AND:
|
||||
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;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,437 @@
|
|||
#include "rc_internal.h"
|
||||
|
||||
#include <string.h> /* memcpy */
|
||||
|
||||
static void rc_update_condition_pause(rc_condition_t* condition) {
|
||||
rc_condition_t* subclause = condition;
|
||||
|
||||
while (condition) {
|
||||
if (condition->type == RC_CONDITION_PAUSE_IF) {
|
||||
while (subclause != condition) {
|
||||
subclause->pause = 1;
|
||||
subclause = subclause->next;
|
||||
}
|
||||
condition->pause = 1;
|
||||
}
|
||||
else {
|
||||
condition->pause = 0;
|
||||
}
|
||||
|
||||
if (!rc_condition_is_combining(condition))
|
||||
subclause = condition->next;
|
||||
|
||||
condition = condition->next;
|
||||
}
|
||||
}
|
||||
|
||||
rc_condset_t* rc_parse_condset(const char** memaddr, rc_parse_state_t* parse, int is_value) {
|
||||
rc_condset_t* self;
|
||||
rc_condition_t** next;
|
||||
int in_add_address;
|
||||
uint32_t measured_target = 0;
|
||||
|
||||
self = RC_ALLOC(rc_condset_t, parse);
|
||||
self->has_pause = self->is_paused = self->has_indirect_memrefs = 0;
|
||||
next = &self->conditions;
|
||||
|
||||
if (**memaddr == 'S' || **memaddr == 's' || !**memaddr) {
|
||||
/* empty group - editor allows it, so we have to support it */
|
||||
*next = 0;
|
||||
return self;
|
||||
}
|
||||
|
||||
in_add_address = 0;
|
||||
for (;;) {
|
||||
*next = rc_parse_condition(memaddr, parse, in_add_address);
|
||||
|
||||
if (parse->offset < 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if ((*next)->oper == RC_OPERATOR_NONE) {
|
||||
switch ((*next)->type) {
|
||||
case RC_CONDITION_ADD_ADDRESS:
|
||||
case RC_CONDITION_ADD_SOURCE:
|
||||
case RC_CONDITION_SUB_SOURCE:
|
||||
/* these conditions don't require a right hand size (implied *1) */
|
||||
break;
|
||||
|
||||
case RC_CONDITION_MEASURED:
|
||||
/* right hand side is not required when Measured is used in a value */
|
||||
if (is_value)
|
||||
break;
|
||||
/* fallthrough */ /* to default */
|
||||
|
||||
default:
|
||||
parse->offset = RC_INVALID_OPERATOR;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
self->has_pause |= (*next)->type == RC_CONDITION_PAUSE_IF;
|
||||
in_add_address = (*next)->type == RC_CONDITION_ADD_ADDRESS;
|
||||
self->has_indirect_memrefs |= in_add_address;
|
||||
|
||||
switch ((*next)->type) {
|
||||
case RC_CONDITION_MEASURED:
|
||||
if (measured_target != 0) {
|
||||
/* multiple Measured flags cannot exist in the same group */
|
||||
parse->offset = RC_MULTIPLE_MEASURED;
|
||||
return 0;
|
||||
}
|
||||
else if (is_value) {
|
||||
measured_target = (unsigned)-1;
|
||||
switch ((*next)->oper)
|
||||
{
|
||||
case RC_OPERATOR_AND:
|
||||
case RC_OPERATOR_XOR:
|
||||
case RC_OPERATOR_DIV:
|
||||
case RC_OPERATOR_MULT:
|
||||
case RC_OPERATOR_NONE:
|
||||
/* measuring value. leave required_hits at 0 */
|
||||
break;
|
||||
|
||||
default:
|
||||
/* comparison operator, measuring hits. set required_hits to MAX_INT */
|
||||
(*next)->required_hits = measured_target;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if ((*next)->required_hits != 0) {
|
||||
measured_target = (*next)->required_hits;
|
||||
}
|
||||
else if ((*next)->operand2.type == RC_OPERAND_CONST) {
|
||||
measured_target = (*next)->operand2.value.num;
|
||||
}
|
||||
else if ((*next)->operand2.type == RC_OPERAND_FP) {
|
||||
measured_target = (unsigned)(*next)->operand2.value.dbl;
|
||||
}
|
||||
else {
|
||||
parse->offset = RC_INVALID_MEASURED_TARGET;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (parse->measured_target && measured_target != parse->measured_target) {
|
||||
/* multiple Measured flags in separate groups must have the same target */
|
||||
parse->offset = RC_MULTIPLE_MEASURED;
|
||||
return 0;
|
||||
}
|
||||
|
||||
parse->measured_target = measured_target;
|
||||
break;
|
||||
|
||||
case RC_CONDITION_STANDARD:
|
||||
case RC_CONDITION_TRIGGER:
|
||||
/* these flags are not allowed in value expressions */
|
||||
if (is_value) {
|
||||
parse->offset = RC_INVALID_VALUE_FLAG;
|
||||
return 0;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
next = &(*next)->next;
|
||||
|
||||
if (**memaddr != '_') {
|
||||
break;
|
||||
}
|
||||
|
||||
(*memaddr)++;
|
||||
}
|
||||
|
||||
*next = 0;
|
||||
|
||||
if (parse->buffer != 0)
|
||||
rc_update_condition_pause(self->conditions);
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
static void rc_condset_update_indirect_memrefs(rc_condition_t* condition, int processing_pause, rc_eval_state_t* eval_state) {
|
||||
for (; condition != 0; condition = condition->next) {
|
||||
if (condition->pause != processing_pause)
|
||||
continue;
|
||||
|
||||
if (condition->type == RC_CONDITION_ADD_ADDRESS) {
|
||||
rc_typed_value_t value;
|
||||
rc_evaluate_condition_value(&value, condition, eval_state);
|
||||
rc_typed_value_convert(&value, RC_VALUE_TYPE_UNSIGNED);
|
||||
eval_state->add_address = value.value.u32;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* call rc_get_memref_value to update the indirect memrefs. it won't do anything with non-indirect
|
||||
* memrefs and avoids a second check of is_indirect. also, we ignore the response, so it doesn't
|
||||
* matter what operand type we pass. assume RC_OPERAND_ADDRESS is the quickest. */
|
||||
if (rc_operand_is_memref(&condition->operand1))
|
||||
rc_get_memref_value(condition->operand1.value.memref, RC_OPERAND_ADDRESS, eval_state);
|
||||
|
||||
if (rc_operand_is_memref(&condition->operand2))
|
||||
rc_get_memref_value(condition->operand2.value.memref, RC_OPERAND_ADDRESS, eval_state);
|
||||
|
||||
eval_state->add_address = 0;
|
||||
}
|
||||
}
|
||||
|
||||
static int rc_test_condset_internal(rc_condset_t* self, int processing_pause, rc_eval_state_t* eval_state) {
|
||||
rc_condition_t* condition;
|
||||
rc_typed_value_t value;
|
||||
int set_valid, cond_valid, and_next, or_next, reset_next, measured_from_hits, can_measure;
|
||||
rc_typed_value_t measured_value;
|
||||
uint32_t total_hits;
|
||||
|
||||
measured_value.type = RC_VALUE_TYPE_NONE;
|
||||
measured_from_hits = 0;
|
||||
can_measure = 1;
|
||||
total_hits = 0;
|
||||
|
||||
eval_state->primed = 1;
|
||||
set_valid = 1;
|
||||
and_next = 1;
|
||||
or_next = 0;
|
||||
reset_next = 0;
|
||||
eval_state->add_value.type = RC_VALUE_TYPE_NONE;
|
||||
eval_state->add_hits = eval_state->add_address = 0;
|
||||
|
||||
for (condition = self->conditions; condition != 0; condition = condition->next) {
|
||||
if (condition->pause != processing_pause)
|
||||
continue;
|
||||
|
||||
/* STEP 1: process modifier conditions */
|
||||
switch (condition->type) {
|
||||
case RC_CONDITION_ADD_SOURCE:
|
||||
rc_evaluate_condition_value(&value, condition, eval_state);
|
||||
rc_typed_value_add(&eval_state->add_value, &value);
|
||||
eval_state->add_address = 0;
|
||||
continue;
|
||||
|
||||
case RC_CONDITION_SUB_SOURCE:
|
||||
rc_evaluate_condition_value(&value, condition, eval_state);
|
||||
rc_typed_value_negate(&value);
|
||||
rc_typed_value_add(&eval_state->add_value, &value);
|
||||
eval_state->add_address = 0;
|
||||
continue;
|
||||
|
||||
case RC_CONDITION_ADD_ADDRESS:
|
||||
rc_evaluate_condition_value(&value, condition, eval_state);
|
||||
rc_typed_value_convert(&value, RC_VALUE_TYPE_UNSIGNED);
|
||||
eval_state->add_address = value.value.u32;
|
||||
continue;
|
||||
|
||||
case RC_CONDITION_MEASURED:
|
||||
if (condition->required_hits == 0 && can_measure) {
|
||||
/* Measured condition without a hit target measures the value of the left operand */
|
||||
rc_evaluate_condition_value(&measured_value, condition, eval_state);
|
||||
rc_typed_value_add(&measured_value, &eval_state->add_value);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
/* STEP 2: evaluate the current condition */
|
||||
condition->is_true = (char)rc_test_condition(condition, eval_state);
|
||||
eval_state->add_value.type = RC_VALUE_TYPE_NONE;
|
||||
eval_state->add_address = 0;
|
||||
|
||||
/* apply logic flags and reset them for the next condition */
|
||||
cond_valid = condition->is_true;
|
||||
cond_valid &= and_next;
|
||||
cond_valid |= or_next;
|
||||
and_next = 1;
|
||||
or_next = 0;
|
||||
|
||||
if (reset_next) {
|
||||
/* previous ResetNextIf resets the hit count on this condition and prevents it from being true */
|
||||
if (condition->current_hits)
|
||||
eval_state->was_cond_reset = 1;
|
||||
|
||||
condition->current_hits = 0;
|
||||
cond_valid = 0;
|
||||
}
|
||||
else if (cond_valid) {
|
||||
/* true conditions should update hit count */
|
||||
eval_state->has_hits = 1;
|
||||
|
||||
if (condition->required_hits == 0) {
|
||||
/* no target hit count, just keep tallying */
|
||||
++condition->current_hits;
|
||||
}
|
||||
else if (condition->current_hits < condition->required_hits) {
|
||||
/* target hit count hasn't been met, tally and revalidate - only true if hit count becomes met */
|
||||
++condition->current_hits;
|
||||
cond_valid = (condition->current_hits == condition->required_hits);
|
||||
}
|
||||
else {
|
||||
/* target hit count has been met, do nothing */
|
||||
}
|
||||
}
|
||||
else if (condition->current_hits > 0) {
|
||||
/* target has been true in the past, if the hit target is met, consider it true now */
|
||||
eval_state->has_hits = 1;
|
||||
cond_valid = (condition->current_hits == condition->required_hits);
|
||||
}
|
||||
|
||||
/* STEP 3: handle logic flags */
|
||||
switch (condition->type) {
|
||||
case RC_CONDITION_ADD_HITS:
|
||||
eval_state->add_hits += condition->current_hits;
|
||||
reset_next = 0; /* ResetNextIf was applied to this AddHits condition; don't apply it to future conditions */
|
||||
continue;
|
||||
|
||||
case RC_CONDITION_SUB_HITS:
|
||||
eval_state->add_hits -= condition->current_hits;
|
||||
reset_next = 0; /* ResetNextIf was applied to this AddHits condition; don't apply it to future conditions */
|
||||
continue;
|
||||
|
||||
case RC_CONDITION_RESET_NEXT_IF:
|
||||
reset_next = cond_valid;
|
||||
continue;
|
||||
|
||||
case RC_CONDITION_AND_NEXT:
|
||||
and_next = cond_valid;
|
||||
continue;
|
||||
|
||||
case RC_CONDITION_OR_NEXT:
|
||||
or_next = cond_valid;
|
||||
continue;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
/* reset logic flags for next condition */
|
||||
reset_next = 0;
|
||||
|
||||
/* STEP 4: calculate total hits */
|
||||
total_hits = condition->current_hits;
|
||||
|
||||
if (eval_state->add_hits) {
|
||||
if (condition->required_hits != 0) {
|
||||
/* if the condition has a target hit count, we have to recalculate cond_valid including the AddHits counter */
|
||||
const int signed_hits = (int)condition->current_hits + eval_state->add_hits;
|
||||
total_hits = (signed_hits >= 0) ? (unsigned)signed_hits : 0;
|
||||
cond_valid = (total_hits >= condition->required_hits);
|
||||
}
|
||||
else {
|
||||
/* no target hit count. we can't tell if the add_hits value is from this frame or not, so ignore it.
|
||||
complex condition will only be true if the current condition is true */
|
||||
}
|
||||
|
||||
eval_state->add_hits = 0;
|
||||
}
|
||||
|
||||
/* STEP 5: handle special flags */
|
||||
switch (condition->type) {
|
||||
case RC_CONDITION_PAUSE_IF:
|
||||
/* as soon as we find a PauseIf that evaluates to true, stop processing the rest of the group */
|
||||
if (cond_valid) {
|
||||
/* indirect memrefs are not updated as part of the rc_update_memref_values call.
|
||||
* an active pause aborts processing of the remaining part of the pause subset and the entire non-pause subset.
|
||||
* if the set has any indirect memrefs, manually update them now so the deltas are correct */
|
||||
if (self->has_indirect_memrefs) {
|
||||
/* first, update any indirect memrefs in the remaining part of the pause subset */
|
||||
rc_condset_update_indirect_memrefs(condition->next, 1, eval_state);
|
||||
|
||||
/* then, update all indirect memrefs in the non-pause subset */
|
||||
rc_condset_update_indirect_memrefs(self->conditions, 0, eval_state);
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* if we make it to the end of the function, make sure we indicate that nothing matched. if we do find
|
||||
a later PauseIf match, it'll automatically return true via the previous condition. */
|
||||
set_valid = 0;
|
||||
|
||||
if (condition->required_hits == 0) {
|
||||
/* PauseIf didn't evaluate true, and doesn't have a HitCount, reset the HitCount to indicate the condition didn't match */
|
||||
condition->current_hits = 0;
|
||||
}
|
||||
else {
|
||||
/* PauseIf has a HitCount that hasn't been met, ignore it for now. */
|
||||
}
|
||||
|
||||
continue;
|
||||
|
||||
case RC_CONDITION_RESET_IF:
|
||||
if (cond_valid) {
|
||||
eval_state->was_reset = 1; /* let caller know to reset all hit counts */
|
||||
set_valid = 0; /* cannot be valid if we've hit a reset condition */
|
||||
}
|
||||
continue;
|
||||
|
||||
case RC_CONDITION_MEASURED:
|
||||
if (condition->required_hits != 0) {
|
||||
/* if there's a hit target, capture the current hits for recording Measured value later */
|
||||
measured_from_hits = 1;
|
||||
if (can_measure) {
|
||||
measured_value.value.u32 = total_hits;
|
||||
measured_value.type = RC_VALUE_TYPE_UNSIGNED;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case RC_CONDITION_MEASURED_IF:
|
||||
if (!cond_valid) {
|
||||
measured_value.value.u32 = 0;
|
||||
measured_value.type = RC_VALUE_TYPE_UNSIGNED;
|
||||
can_measure = 0;
|
||||
}
|
||||
break;
|
||||
|
||||
case RC_CONDITION_TRIGGER:
|
||||
/* update truthiness of set, but do not update truthiness of primed state */
|
||||
set_valid &= cond_valid;
|
||||
continue;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
/* STEP 5: update overall truthiness of set and primed state */
|
||||
eval_state->primed &= cond_valid;
|
||||
set_valid &= cond_valid;
|
||||
}
|
||||
|
||||
if (measured_value.type != RC_VALUE_TYPE_NONE) {
|
||||
/* if no previous Measured value was captured, or the new one is greater, keep the new one */
|
||||
if (eval_state->measured_value.type == RC_VALUE_TYPE_NONE ||
|
||||
rc_typed_value_compare(&measured_value, &eval_state->measured_value, RC_OPERATOR_GT)) {
|
||||
memcpy(&eval_state->measured_value, &measured_value, sizeof(measured_value));
|
||||
eval_state->measured_from_hits = (char)measured_from_hits;
|
||||
}
|
||||
}
|
||||
|
||||
return set_valid;
|
||||
}
|
||||
|
||||
int rc_test_condset(rc_condset_t* self, rc_eval_state_t* eval_state) {
|
||||
if (self->conditions == 0) {
|
||||
/* important: empty group must evaluate true */
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (self->has_pause) {
|
||||
/* one or more Pause conditions exists, if any of them are true, stop processing this group */
|
||||
self->is_paused = (char)rc_test_condset_internal(self, 1, eval_state);
|
||||
if (self->is_paused) {
|
||||
eval_state->primed = 0;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
return rc_test_condset_internal(self, 0, eval_state);
|
||||
}
|
||||
|
||||
void rc_reset_condset(rc_condset_t* self) {
|
||||
rc_condition_t* condition;
|
||||
|
||||
for (condition = self->conditions; condition != 0; condition = condition->next) {
|
||||
condition->current_hits = 0;
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,280 @@
|
|||
#include "rc_internal.h"
|
||||
|
||||
#include "../rc_compat.h"
|
||||
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
|
||||
int rc_parse_format(const char* format_str) {
|
||||
switch (*format_str++) {
|
||||
case 'F':
|
||||
if (!strcmp(format_str, "RAMES")) {
|
||||
return RC_FORMAT_FRAMES;
|
||||
}
|
||||
if (!strncmp(format_str, "LOAT", 4) && format_str[4] >= '1' && format_str[4] <= '6' && format_str[5] == '\0') {
|
||||
return RC_FORMAT_FLOAT1 + (format_str[4] - '1');
|
||||
}
|
||||
if (!strncmp(format_str, "IXED", 4) && format_str[4] >= '1' && format_str[4] <= '3' && format_str[5] == '\0') {
|
||||
return RC_FORMAT_FIXED1 + (format_str[4] - '1');
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case 'T':
|
||||
if (!strcmp(format_str, "IME")) {
|
||||
return RC_FORMAT_FRAMES;
|
||||
}
|
||||
if (!strcmp(format_str, "IMESECS")) {
|
||||
return RC_FORMAT_SECONDS;
|
||||
}
|
||||
if (!strcmp(format_str, "HOUSANDS")) {
|
||||
return RC_FORMAT_THOUSANDS;
|
||||
}
|
||||
if (!strcmp(format_str, "ENS")) {
|
||||
return RC_FORMAT_TENS;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case 'S':
|
||||
if (!strcmp(format_str, "ECS")) {
|
||||
return RC_FORMAT_SECONDS;
|
||||
}
|
||||
if (!strcmp(format_str, "CORE")) {
|
||||
return RC_FORMAT_SCORE;
|
||||
}
|
||||
if (!strcmp(format_str, "ECS_AS_MINS")) {
|
||||
return RC_FORMAT_SECONDS_AS_MINUTES;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case 'M':
|
||||
if (!strcmp(format_str, "ILLISECS")) {
|
||||
return RC_FORMAT_CENTISECS;
|
||||
}
|
||||
if (!strcmp(format_str, "INUTES")) {
|
||||
return RC_FORMAT_MINUTES;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case 'P':
|
||||
if (!strcmp(format_str, "OINTS")) {
|
||||
return RC_FORMAT_SCORE;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case 'V':
|
||||
if (!strcmp(format_str, "ALUE")) {
|
||||
return RC_FORMAT_VALUE;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case 'U':
|
||||
if (!strcmp(format_str, "NSIGNED")) {
|
||||
return RC_FORMAT_UNSIGNED_VALUE;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case 'O':
|
||||
if (!strcmp(format_str, "THER")) {
|
||||
return RC_FORMAT_SCORE;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case 'H':
|
||||
if (!strcmp(format_str, "UNDREDS")) {
|
||||
return RC_FORMAT_HUNDREDS;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
return RC_FORMAT_VALUE;
|
||||
}
|
||||
|
||||
static int rc_format_value_minutes(char* buffer, size_t size, uint32_t minutes) {
|
||||
uint32_t hours;
|
||||
|
||||
hours = minutes / 60;
|
||||
minutes -= hours * 60;
|
||||
return snprintf(buffer, size, "%uh%02u", hours, minutes);
|
||||
}
|
||||
|
||||
static int rc_format_value_seconds(char* buffer, size_t size, uint32_t seconds) {
|
||||
uint32_t hours, minutes;
|
||||
|
||||
/* apply modulus math to split the seconds into hours/minutes/seconds */
|
||||
minutes = seconds / 60;
|
||||
seconds -= minutes * 60;
|
||||
if (minutes < 60) {
|
||||
return snprintf(buffer, size, "%u:%02u", minutes, seconds);
|
||||
}
|
||||
|
||||
hours = minutes / 60;
|
||||
minutes -= hours * 60;
|
||||
return snprintf(buffer, size, "%uh%02u:%02u", hours, minutes, seconds);
|
||||
}
|
||||
|
||||
static int rc_format_value_centiseconds(char* buffer, size_t size, uint32_t centiseconds) {
|
||||
uint32_t seconds;
|
||||
int chars, chars2;
|
||||
|
||||
/* modulus off the centiseconds */
|
||||
seconds = centiseconds / 100;
|
||||
centiseconds -= seconds * 100;
|
||||
|
||||
chars = rc_format_value_seconds(buffer, size, seconds);
|
||||
if (chars > 0) {
|
||||
chars2 = snprintf(buffer + chars, size - chars, ".%02u", centiseconds);
|
||||
if (chars2 > 0) {
|
||||
chars += chars2;
|
||||
} else {
|
||||
chars = chars2;
|
||||
}
|
||||
}
|
||||
|
||||
return chars;
|
||||
}
|
||||
|
||||
static int rc_format_value_fixed(char* buffer, size_t size, const char* format, int32_t value, int32_t factor)
|
||||
{
|
||||
if (value >= 0)
|
||||
return snprintf(buffer, size, format, value / factor, value % factor);
|
||||
|
||||
return snprintf(buffer, size, format, value / factor, (-value) % factor);
|
||||
}
|
||||
|
||||
static int rc_format_value_padded(char* buffer, size_t size, const char* format, int32_t value)
|
||||
{
|
||||
if (value == 0)
|
||||
return snprintf(buffer, size, "0");
|
||||
|
||||
return snprintf(buffer, size, format, value);
|
||||
}
|
||||
|
||||
int rc_format_typed_value(char* buffer, size_t size, const rc_typed_value_t* value, int format) {
|
||||
int chars;
|
||||
rc_typed_value_t converted_value;
|
||||
|
||||
memcpy(&converted_value, value, sizeof(converted_value));
|
||||
|
||||
switch (format) {
|
||||
default:
|
||||
case RC_FORMAT_VALUE:
|
||||
rc_typed_value_convert(&converted_value, RC_VALUE_TYPE_SIGNED);
|
||||
chars = snprintf(buffer, size, "%d", converted_value.value.i32);
|
||||
break;
|
||||
|
||||
case RC_FORMAT_FRAMES:
|
||||
/* 60 frames per second = 100 centiseconds / 60 frames; multiply frames by 100 / 60 */
|
||||
rc_typed_value_convert(&converted_value, RC_VALUE_TYPE_UNSIGNED);
|
||||
chars = rc_format_value_centiseconds(buffer, size, converted_value.value.u32 * 10 / 6);
|
||||
break;
|
||||
|
||||
case RC_FORMAT_CENTISECS:
|
||||
rc_typed_value_convert(&converted_value, RC_VALUE_TYPE_UNSIGNED);
|
||||
chars = rc_format_value_centiseconds(buffer, size, converted_value.value.u32);
|
||||
break;
|
||||
|
||||
case RC_FORMAT_SECONDS:
|
||||
rc_typed_value_convert(&converted_value, RC_VALUE_TYPE_UNSIGNED);
|
||||
chars = rc_format_value_seconds(buffer, size, converted_value.value.u32);
|
||||
break;
|
||||
|
||||
case RC_FORMAT_SECONDS_AS_MINUTES:
|
||||
rc_typed_value_convert(&converted_value, RC_VALUE_TYPE_UNSIGNED);
|
||||
chars = rc_format_value_minutes(buffer, size, converted_value.value.u32 / 60);
|
||||
break;
|
||||
|
||||
case RC_FORMAT_MINUTES:
|
||||
rc_typed_value_convert(&converted_value, RC_VALUE_TYPE_UNSIGNED);
|
||||
chars = rc_format_value_minutes(buffer, size, converted_value.value.u32);
|
||||
break;
|
||||
|
||||
case RC_FORMAT_SCORE:
|
||||
rc_typed_value_convert(&converted_value, RC_VALUE_TYPE_SIGNED);
|
||||
chars = snprintf(buffer, size, "%06d", converted_value.value.i32);
|
||||
break;
|
||||
|
||||
case RC_FORMAT_FLOAT1:
|
||||
rc_typed_value_convert(&converted_value, RC_VALUE_TYPE_FLOAT);
|
||||
chars = snprintf(buffer, size, "%.1f", converted_value.value.f32);
|
||||
break;
|
||||
|
||||
case RC_FORMAT_FLOAT2:
|
||||
rc_typed_value_convert(&converted_value, RC_VALUE_TYPE_FLOAT);
|
||||
chars = snprintf(buffer, size, "%.2f", converted_value.value.f32);
|
||||
break;
|
||||
|
||||
case RC_FORMAT_FLOAT3:
|
||||
rc_typed_value_convert(&converted_value, RC_VALUE_TYPE_FLOAT);
|
||||
chars = snprintf(buffer, size, "%.3f", converted_value.value.f32);
|
||||
break;
|
||||
|
||||
case RC_FORMAT_FLOAT4:
|
||||
rc_typed_value_convert(&converted_value, RC_VALUE_TYPE_FLOAT);
|
||||
chars = snprintf(buffer, size, "%.4f", converted_value.value.f32);
|
||||
break;
|
||||
|
||||
case RC_FORMAT_FLOAT5:
|
||||
rc_typed_value_convert(&converted_value, RC_VALUE_TYPE_FLOAT);
|
||||
chars = snprintf(buffer, size, "%.5f", converted_value.value.f32);
|
||||
break;
|
||||
|
||||
case RC_FORMAT_FLOAT6:
|
||||
rc_typed_value_convert(&converted_value, RC_VALUE_TYPE_FLOAT);
|
||||
chars = snprintf(buffer, size, "%.6f", converted_value.value.f32);
|
||||
break;
|
||||
|
||||
case RC_FORMAT_FIXED1:
|
||||
rc_typed_value_convert(&converted_value, RC_VALUE_TYPE_SIGNED);
|
||||
chars = rc_format_value_fixed(buffer, size, "%d.%u", converted_value.value.i32, 10);
|
||||
break;
|
||||
|
||||
case RC_FORMAT_FIXED2:
|
||||
rc_typed_value_convert(&converted_value, RC_VALUE_TYPE_SIGNED);
|
||||
chars = rc_format_value_fixed(buffer, size, "%d.%02u", converted_value.value.i32, 100);
|
||||
break;
|
||||
|
||||
case RC_FORMAT_FIXED3:
|
||||
rc_typed_value_convert(&converted_value, RC_VALUE_TYPE_SIGNED);
|
||||
chars = rc_format_value_fixed(buffer, size, "%d.%03u", converted_value.value.i32, 1000);
|
||||
break;
|
||||
|
||||
case RC_FORMAT_TENS:
|
||||
rc_typed_value_convert(&converted_value, RC_VALUE_TYPE_SIGNED);
|
||||
chars = rc_format_value_padded(buffer, size, "%d0", converted_value.value.i32);
|
||||
break;
|
||||
|
||||
case RC_FORMAT_HUNDREDS:
|
||||
rc_typed_value_convert(&converted_value, RC_VALUE_TYPE_SIGNED);
|
||||
chars = rc_format_value_padded(buffer, size, "%d00", converted_value.value.i32);
|
||||
break;
|
||||
|
||||
case RC_FORMAT_THOUSANDS:
|
||||
rc_typed_value_convert(&converted_value, RC_VALUE_TYPE_SIGNED);
|
||||
chars = rc_format_value_padded(buffer, size, "%d000", converted_value.value.i32);
|
||||
break;
|
||||
|
||||
case RC_FORMAT_UNSIGNED_VALUE:
|
||||
rc_typed_value_convert(&converted_value, RC_VALUE_TYPE_UNSIGNED);
|
||||
chars = snprintf(buffer, size, "%u", converted_value.value.u32);
|
||||
break;
|
||||
}
|
||||
|
||||
return chars;
|
||||
}
|
||||
|
||||
int rc_format_value(char* buffer, int size, int32_t value, int format) {
|
||||
rc_typed_value_t typed_value;
|
||||
|
||||
typed_value.value.i32 = value;
|
||||
typed_value.type = RC_VALUE_TYPE_SIGNED;
|
||||
return rc_format_typed_value(buffer, size, &typed_value, format);
|
||||
}
|
|
@ -0,0 +1,278 @@
|
|||
#include "rc_internal.h"
|
||||
|
||||
enum {
|
||||
RC_LBOARD_START = 1 << 0,
|
||||
RC_LBOARD_CANCEL = 1 << 1,
|
||||
RC_LBOARD_SUBMIT = 1 << 2,
|
||||
RC_LBOARD_VALUE = 1 << 3,
|
||||
RC_LBOARD_PROGRESS = 1 << 4,
|
||||
RC_LBOARD_COMPLETE = RC_LBOARD_START | RC_LBOARD_CANCEL | RC_LBOARD_SUBMIT | RC_LBOARD_VALUE
|
||||
};
|
||||
|
||||
void rc_parse_lboard_internal(rc_lboard_t* self, const char* memaddr, rc_parse_state_t* parse) {
|
||||
int found;
|
||||
|
||||
self->progress = 0;
|
||||
found = 0;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
if ((memaddr[0] == 's' || memaddr[0] == 'S') &&
|
||||
(memaddr[1] == 't' || memaddr[1] == 'T') &&
|
||||
(memaddr[2] == 'a' || memaddr[2] == 'A') && memaddr[3] == ':') {
|
||||
if ((found & RC_LBOARD_START) != 0) {
|
||||
parse->offset = RC_DUPLICATED_START;
|
||||
return;
|
||||
}
|
||||
|
||||
memaddr += 4;
|
||||
if (*memaddr && *memaddr != ':') {
|
||||
found |= RC_LBOARD_START;
|
||||
rc_parse_trigger_internal(&self->start, &memaddr, parse);
|
||||
self->start.memrefs = 0;
|
||||
}
|
||||
}
|
||||
else if ((memaddr[0] == 'c' || memaddr[0] == 'C') &&
|
||||
(memaddr[1] == 'a' || memaddr[1] == 'A') &&
|
||||
(memaddr[2] == 'n' || memaddr[2] == 'N') && memaddr[3] == ':') {
|
||||
if ((found & RC_LBOARD_CANCEL) != 0) {
|
||||
parse->offset = RC_DUPLICATED_CANCEL;
|
||||
return;
|
||||
}
|
||||
|
||||
memaddr += 4;
|
||||
if (*memaddr && *memaddr != ':') {
|
||||
found |= RC_LBOARD_CANCEL;
|
||||
rc_parse_trigger_internal(&self->cancel, &memaddr, parse);
|
||||
self->cancel.memrefs = 0;
|
||||
}
|
||||
}
|
||||
else if ((memaddr[0] == 's' || memaddr[0] == 'S') &&
|
||||
(memaddr[1] == 'u' || memaddr[1] == 'U') &&
|
||||
(memaddr[2] == 'b' || memaddr[2] == 'B') && memaddr[3] == ':') {
|
||||
if ((found & RC_LBOARD_SUBMIT) != 0) {
|
||||
parse->offset = RC_DUPLICATED_SUBMIT;
|
||||
return;
|
||||
}
|
||||
|
||||
memaddr += 4;
|
||||
if (*memaddr && *memaddr != ':') {
|
||||
found |= RC_LBOARD_SUBMIT;
|
||||
rc_parse_trigger_internal(&self->submit, &memaddr, parse);
|
||||
self->submit.memrefs = 0;
|
||||
}
|
||||
}
|
||||
else if ((memaddr[0] == 'v' || memaddr[0] == 'V') &&
|
||||
(memaddr[1] == 'a' || memaddr[1] == 'A') &&
|
||||
(memaddr[2] == 'l' || memaddr[2] == 'L') && memaddr[3] == ':') {
|
||||
if ((found & RC_LBOARD_VALUE) != 0) {
|
||||
parse->offset = RC_DUPLICATED_VALUE;
|
||||
return;
|
||||
}
|
||||
|
||||
memaddr += 4;
|
||||
if (*memaddr && *memaddr != ':') {
|
||||
found |= RC_LBOARD_VALUE;
|
||||
rc_parse_value_internal(&self->value, &memaddr, parse);
|
||||
self->value.memrefs = 0;
|
||||
}
|
||||
}
|
||||
else if ((memaddr[0] == 'p' || memaddr[0] == 'P') &&
|
||||
(memaddr[1] == 'r' || memaddr[1] == 'R') &&
|
||||
(memaddr[2] == 'o' || memaddr[2] == 'O') && memaddr[3] == ':') {
|
||||
if ((found & RC_LBOARD_PROGRESS) != 0) {
|
||||
parse->offset = RC_DUPLICATED_PROGRESS;
|
||||
return;
|
||||
}
|
||||
|
||||
memaddr += 4;
|
||||
if (*memaddr && *memaddr != ':') {
|
||||
found |= RC_LBOARD_PROGRESS;
|
||||
|
||||
self->progress = RC_ALLOC(rc_value_t, parse);
|
||||
rc_parse_value_internal(self->progress, &memaddr, parse);
|
||||
self->progress->memrefs = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* encountered an error parsing one of the parts */
|
||||
if (parse->offset < 0)
|
||||
return;
|
||||
|
||||
/* end of string, or end of quoted string - stop processing */
|
||||
if (memaddr[0] == '\0' || memaddr[0] == '\"')
|
||||
break;
|
||||
|
||||
/* expect two colons between fields */
|
||||
if (memaddr[0] != ':' || memaddr[1] != ':') {
|
||||
parse->offset = RC_INVALID_LBOARD_FIELD;
|
||||
return;
|
||||
}
|
||||
|
||||
memaddr += 2;
|
||||
}
|
||||
|
||||
if ((found & RC_LBOARD_COMPLETE) != RC_LBOARD_COMPLETE) {
|
||||
if ((found & RC_LBOARD_START) == 0) {
|
||||
parse->offset = RC_MISSING_START;
|
||||
}
|
||||
else if ((found & RC_LBOARD_CANCEL) == 0) {
|
||||
parse->offset = RC_MISSING_CANCEL;
|
||||
}
|
||||
else if ((found & RC_LBOARD_SUBMIT) == 0) {
|
||||
parse->offset = RC_MISSING_SUBMIT;
|
||||
}
|
||||
else if ((found & RC_LBOARD_VALUE) == 0) {
|
||||
parse->offset = RC_MISSING_VALUE;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
self->state = RC_LBOARD_STATE_WAITING;
|
||||
}
|
||||
|
||||
int rc_lboard_size(const char* memaddr) {
|
||||
rc_lboard_t* self;
|
||||
rc_parse_state_t parse;
|
||||
rc_memref_t* first_memref;
|
||||
rc_init_parse_state(&parse, 0, 0, 0);
|
||||
rc_init_parse_state_memrefs(&parse, &first_memref);
|
||||
|
||||
self = RC_ALLOC(rc_lboard_t, &parse);
|
||||
rc_parse_lboard_internal(self, memaddr, &parse);
|
||||
|
||||
rc_destroy_parse_state(&parse);
|
||||
return parse.offset;
|
||||
}
|
||||
|
||||
rc_lboard_t* rc_parse_lboard(void* buffer, const char* memaddr, lua_State* L, int funcs_ndx) {
|
||||
rc_lboard_t* self;
|
||||
rc_parse_state_t parse;
|
||||
|
||||
if (!buffer || !memaddr)
|
||||
return 0;
|
||||
|
||||
rc_init_parse_state(&parse, buffer, L, funcs_ndx);
|
||||
|
||||
self = RC_ALLOC(rc_lboard_t, &parse);
|
||||
rc_init_parse_state_memrefs(&parse, &self->memrefs);
|
||||
|
||||
rc_parse_lboard_internal(self, memaddr, &parse);
|
||||
|
||||
rc_destroy_parse_state(&parse);
|
||||
return (parse.offset >= 0) ? self : 0;
|
||||
}
|
||||
|
||||
int rc_evaluate_lboard(rc_lboard_t* self, int32_t* value, rc_peek_t peek, void* peek_ud, lua_State* L) {
|
||||
int start_ok, cancel_ok, submit_ok;
|
||||
|
||||
rc_update_memref_values(self->memrefs, peek, peek_ud);
|
||||
|
||||
if (self->state == RC_LBOARD_STATE_INACTIVE || self->state == RC_LBOARD_STATE_DISABLED)
|
||||
return RC_LBOARD_STATE_INACTIVE;
|
||||
|
||||
/* these are always tested once every frame, to ensure hit counts work properly */
|
||||
start_ok = rc_test_trigger(&self->start, peek, peek_ud, L);
|
||||
cancel_ok = rc_test_trigger(&self->cancel, peek, peek_ud, L);
|
||||
submit_ok = rc_test_trigger(&self->submit, peek, peek_ud, L);
|
||||
|
||||
switch (self->state)
|
||||
{
|
||||
case RC_LBOARD_STATE_WAITING:
|
||||
case RC_LBOARD_STATE_TRIGGERED:
|
||||
case RC_LBOARD_STATE_CANCELED:
|
||||
/* don't activate/reactivate until the start condition becomes false */
|
||||
if (start_ok) {
|
||||
*value = 0;
|
||||
return RC_LBOARD_STATE_INACTIVE; /* just return inactive for all of these */
|
||||
}
|
||||
|
||||
/* start condition is false, allow the leaderboard to start on future frames */
|
||||
self->state = RC_LBOARD_STATE_ACTIVE;
|
||||
break;
|
||||
|
||||
case RC_LBOARD_STATE_ACTIVE:
|
||||
/* leaderboard attempt is not in progress. if the start condition is true and the cancel condition is not, start the attempt */
|
||||
if (start_ok && !cancel_ok) {
|
||||
if (submit_ok) {
|
||||
/* start and submit are both true in the same frame, just submit without announcing the leaderboard is available */
|
||||
self->state = RC_LBOARD_STATE_TRIGGERED;
|
||||
}
|
||||
else if (self->start.requirement == 0 && self->start.alternative == 0) {
|
||||
/* start condition is empty - this leaderboard is submit-only with no measured progress */
|
||||
}
|
||||
else {
|
||||
/* start the leaderboard attempt */
|
||||
self->state = RC_LBOARD_STATE_STARTED;
|
||||
|
||||
/* reset any hit counts in the value */
|
||||
if (self->progress)
|
||||
rc_reset_value(self->progress);
|
||||
|
||||
rc_reset_value(&self->value);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case RC_LBOARD_STATE_STARTED:
|
||||
/* leaderboard attempt in progress */
|
||||
if (cancel_ok) {
|
||||
/* cancel condition is true, abort the attempt */
|
||||
self->state = RC_LBOARD_STATE_CANCELED;
|
||||
}
|
||||
else if (submit_ok) {
|
||||
/* submit condition is true, submit the current value */
|
||||
self->state = RC_LBOARD_STATE_TRIGGERED;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
/* Calculate the value */
|
||||
switch (self->state) {
|
||||
case RC_LBOARD_STATE_STARTED:
|
||||
if (self->progress) {
|
||||
*value = rc_evaluate_value(self->progress, peek, peek_ud, L);
|
||||
break;
|
||||
}
|
||||
/* fallthrough */ /* to RC_LBOARD_STATE_TRIGGERED */
|
||||
|
||||
case RC_LBOARD_STATE_TRIGGERED:
|
||||
*value = rc_evaluate_value(&self->value, peek, peek_ud, L);
|
||||
break;
|
||||
|
||||
default:
|
||||
*value = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
return self->state;
|
||||
}
|
||||
|
||||
int rc_lboard_state_active(int state) {
|
||||
switch (state)
|
||||
{
|
||||
case RC_LBOARD_STATE_DISABLED:
|
||||
case RC_LBOARD_STATE_INACTIVE:
|
||||
return 0;
|
||||
|
||||
default:
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
void rc_reset_lboard(rc_lboard_t* self) {
|
||||
if (!self)
|
||||
return;
|
||||
|
||||
self->state = RC_LBOARD_STATE_WAITING;
|
||||
|
||||
rc_reset_trigger(&self->start);
|
||||
rc_reset_trigger(&self->submit);
|
||||
rc_reset_trigger(&self->cancel);
|
||||
|
||||
if (self->progress)
|
||||
rc_reset_value(self->progress);
|
||||
|
||||
rc_reset_value(&self->value);
|
||||
}
|
|
@ -0,0 +1,494 @@
|
|||
#include "rc_internal.h"
|
||||
|
||||
#include <stdlib.h> /* malloc/realloc */
|
||||
#include <string.h> /* memcpy */
|
||||
#include <math.h> /* INFINITY/NAN */
|
||||
|
||||
#define MEMREF_PLACEHOLDER_ADDRESS 0xFFFFFFFF
|
||||
|
||||
rc_memref_t* rc_alloc_memref(rc_parse_state_t* parse, uint32_t address, uint8_t size, uint8_t is_indirect) {
|
||||
rc_memref_t** next_memref;
|
||||
rc_memref_t* memref;
|
||||
|
||||
if (!is_indirect) {
|
||||
/* attempt to find an existing memref that can be shared */
|
||||
next_memref = parse->first_memref;
|
||||
while (*next_memref) {
|
||||
memref = *next_memref;
|
||||
if (!memref->value.is_indirect && memref->address == address && memref->value.size == size)
|
||||
return memref;
|
||||
|
||||
next_memref = &memref->next;
|
||||
}
|
||||
|
||||
/* no match found, create a new entry */
|
||||
memref = RC_ALLOC_SCRATCH(rc_memref_t, parse);
|
||||
*next_memref = memref;
|
||||
}
|
||||
else {
|
||||
/* indirect references always create a new entry because we can't guarantee that the
|
||||
* indirection amount will be the same between references. because they aren't shared,
|
||||
* don't bother putting them in the chain.
|
||||
*/
|
||||
memref = RC_ALLOC(rc_memref_t, parse);
|
||||
}
|
||||
|
||||
memset(memref, 0, sizeof(*memref));
|
||||
memref->address = address;
|
||||
memref->value.size = size;
|
||||
memref->value.is_indirect = is_indirect;
|
||||
|
||||
return memref;
|
||||
}
|
||||
|
||||
int rc_parse_memref(const char** memaddr, uint8_t* size, uint32_t* address) {
|
||||
const char* aux = *memaddr;
|
||||
char* end;
|
||||
unsigned long value;
|
||||
|
||||
if (aux[0] == '0') {
|
||||
if (aux[1] != 'x' && aux[1] != 'X')
|
||||
return RC_INVALID_MEMORY_OPERAND;
|
||||
|
||||
aux += 2;
|
||||
switch (*aux++) {
|
||||
/* ordered by estimated frequency in case compiler doesn't build a jump table */
|
||||
case 'h': case 'H': *size = RC_MEMSIZE_8_BITS; break;
|
||||
case ' ': *size = RC_MEMSIZE_16_BITS; break;
|
||||
case 'x': case 'X': *size = RC_MEMSIZE_32_BITS; break;
|
||||
|
||||
case 'm': case 'M': *size = RC_MEMSIZE_BIT_0; break;
|
||||
case 'n': case 'N': *size = RC_MEMSIZE_BIT_1; break;
|
||||
case 'o': case 'O': *size = RC_MEMSIZE_BIT_2; break;
|
||||
case 'p': case 'P': *size = RC_MEMSIZE_BIT_3; break;
|
||||
case 'q': case 'Q': *size = RC_MEMSIZE_BIT_4; break;
|
||||
case 'r': case 'R': *size = RC_MEMSIZE_BIT_5; break;
|
||||
case 's': case 'S': *size = RC_MEMSIZE_BIT_6; break;
|
||||
case 't': case 'T': *size = RC_MEMSIZE_BIT_7; break;
|
||||
case 'l': case 'L': *size = RC_MEMSIZE_LOW; break;
|
||||
case 'u': case 'U': *size = RC_MEMSIZE_HIGH; break;
|
||||
case 'k': case 'K': *size = RC_MEMSIZE_BITCOUNT; break;
|
||||
case 'w': case 'W': *size = RC_MEMSIZE_24_BITS; break;
|
||||
case 'g': case 'G': *size = RC_MEMSIZE_32_BITS_BE; break;
|
||||
case 'i': case 'I': *size = RC_MEMSIZE_16_BITS_BE; break;
|
||||
case 'j': case 'J': *size = RC_MEMSIZE_24_BITS_BE; break;
|
||||
|
||||
/* case 'v': case 'V': */
|
||||
/* case 'y': case 'Y': 64 bit? */
|
||||
/* case 'z': case 'Z': 128 bit? */
|
||||
|
||||
case '0': case '1': case '2': case '3': case '4':
|
||||
case '5': case '6': case '7': case '8': case '9':
|
||||
case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
|
||||
case 'A': case 'B': case 'C': case 'D': case 'E': case 'F':
|
||||
/* legacy support - addresses without a size prefix are assumed to be 16-bit */
|
||||
aux--;
|
||||
*size = RC_MEMSIZE_16_BITS;
|
||||
break;
|
||||
|
||||
default:
|
||||
return RC_INVALID_MEMORY_OPERAND;
|
||||
}
|
||||
}
|
||||
else if (aux[0] == 'f' || aux[0] == 'F') {
|
||||
++aux;
|
||||
switch (*aux++) {
|
||||
case 'f': case 'F': *size = RC_MEMSIZE_FLOAT; break;
|
||||
case 'b': case 'B': *size = RC_MEMSIZE_FLOAT_BE; break;
|
||||
case 'm': case 'M': *size = RC_MEMSIZE_MBF32; break;
|
||||
case 'l': case 'L': *size = RC_MEMSIZE_MBF32_LE; break;
|
||||
|
||||
default:
|
||||
return RC_INVALID_FP_OPERAND;
|
||||
}
|
||||
}
|
||||
else {
|
||||
return RC_INVALID_MEMORY_OPERAND;
|
||||
}
|
||||
|
||||
value = strtoul(aux, &end, 16);
|
||||
|
||||
if (end == aux)
|
||||
return RC_INVALID_MEMORY_OPERAND;
|
||||
|
||||
if (value > 0xffffffffU)
|
||||
value = 0xffffffffU;
|
||||
|
||||
*address = (uint32_t)value;
|
||||
*memaddr = end;
|
||||
return RC_OK;
|
||||
}
|
||||
|
||||
static float rc_build_float(uint32_t mantissa_bits, int32_t exponent, int sign) {
|
||||
/* 32-bit float has a 23-bit mantissa and 8-bit exponent */
|
||||
const uint32_t implied_bit = 1 << 23;
|
||||
const uint32_t mantissa = mantissa_bits | implied_bit;
|
||||
double dbl = ((double)mantissa) / ((double)implied_bit);
|
||||
|
||||
if (exponent > 127) {
|
||||
/* exponent above 127 is a special number */
|
||||
if (mantissa_bits == 0) {
|
||||
/* infinity */
|
||||
#ifdef INFINITY /* INFINITY and NAN #defines require C99 */
|
||||
dbl = INFINITY;
|
||||
#else
|
||||
dbl = -log(0.0);
|
||||
#endif
|
||||
}
|
||||
else {
|
||||
/* NaN */
|
||||
#ifdef NAN
|
||||
dbl = NAN;
|
||||
#else
|
||||
dbl = -sqrt(-1);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
else if (exponent > 0) {
|
||||
/* exponent from 1 to 127 is a number greater than 1 */
|
||||
while (exponent > 30) {
|
||||
dbl *= (double)(1 << 30);
|
||||
exponent -= 30;
|
||||
}
|
||||
dbl *= (double)((long long)1 << exponent);
|
||||
}
|
||||
else if (exponent < 0) {
|
||||
/* exponent from -1 to -127 is a number less than 1 */
|
||||
|
||||
if (exponent == -127) {
|
||||
/* exponent -127 (all exponent bits were zero) is a denormalized value
|
||||
* (no implied leading bit) with exponent -126 */
|
||||
dbl = ((double)mantissa_bits) / ((double)implied_bit);
|
||||
exponent = 126;
|
||||
} else {
|
||||
exponent = -exponent;
|
||||
}
|
||||
|
||||
while (exponent > 30) {
|
||||
dbl /= (double)(1 << 30);
|
||||
exponent -= 30;
|
||||
}
|
||||
dbl /= (double)((long long)1 << exponent);
|
||||
}
|
||||
else {
|
||||
/* exponent of 0 requires no adjustment */
|
||||
}
|
||||
|
||||
return (sign) ? (float)-dbl : (float)dbl;
|
||||
}
|
||||
|
||||
static void rc_transform_memref_float(rc_typed_value_t* value) {
|
||||
/* decodes an IEEE 754 float */
|
||||
const uint32_t mantissa = (value->value.u32 & 0x7FFFFF);
|
||||
const int32_t exponent = (int32_t)((value->value.u32 >> 23) & 0xFF) - 127;
|
||||
const int sign = (value->value.u32 & 0x80000000);
|
||||
value->value.f32 = rc_build_float(mantissa, exponent, sign);
|
||||
value->type = RC_VALUE_TYPE_FLOAT;
|
||||
}
|
||||
|
||||
static void rc_transform_memref_float_be(rc_typed_value_t* value) {
|
||||
/* decodes an IEEE 754 float in big endian format */
|
||||
const uint32_t mantissa = ((value->value.u32 & 0xFF000000) >> 24) |
|
||||
((value->value.u32 & 0x00FF0000) >> 8) |
|
||||
((value->value.u32 & 0x00007F00) << 8);
|
||||
const int32_t exponent = (int32_t)(((value->value.u32 & 0x0000007F) << 1) |
|
||||
((value->value.u32 & 0x00008000) >> 15)) - 127;
|
||||
const int sign = (value->value.u32 & 0x00000080);
|
||||
value->value.f32 = rc_build_float(mantissa, exponent, sign);
|
||||
value->type = RC_VALUE_TYPE_FLOAT;
|
||||
}
|
||||
|
||||
static void rc_transform_memref_mbf32(rc_typed_value_t* value) {
|
||||
/* decodes a Microsoft Binary Format float */
|
||||
/* NOTE: 32-bit MBF is stored in memory as big endian (at least for Apple II) */
|
||||
const uint32_t mantissa = ((value->value.u32 & 0xFF000000) >> 24) |
|
||||
((value->value.u32 & 0x00FF0000) >> 8) |
|
||||
((value->value.u32 & 0x00007F00) << 8);
|
||||
const int32_t exponent = (int32_t)(value->value.u32 & 0xFF) - 129;
|
||||
const int sign = (value->value.u32 & 0x00008000);
|
||||
|
||||
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 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 uint32_t mantissa = value->value.u32 & 0x007FFFFF;
|
||||
const int32_t exponent = (int32_t)(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 uint8_t 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, uint8_t size) {
|
||||
/* ASSERT: value->type == RC_VALUE_TYPE_UNSIGNED */
|
||||
switch (size)
|
||||
{
|
||||
case RC_MEMSIZE_8_BITS:
|
||||
value->value.u32 = (value->value.u32 & 0x000000ff);
|
||||
break;
|
||||
|
||||
case RC_MEMSIZE_16_BITS:
|
||||
value->value.u32 = (value->value.u32 & 0x0000ffff);
|
||||
break;
|
||||
|
||||
case RC_MEMSIZE_24_BITS:
|
||||
value->value.u32 = (value->value.u32 & 0x00ffffff);
|
||||
break;
|
||||
|
||||
case RC_MEMSIZE_32_BITS:
|
||||
break;
|
||||
|
||||
case RC_MEMSIZE_BIT_0:
|
||||
value->value.u32 = (value->value.u32 >> 0) & 1;
|
||||
break;
|
||||
|
||||
case RC_MEMSIZE_BIT_1:
|
||||
value->value.u32 = (value->value.u32 >> 1) & 1;
|
||||
break;
|
||||
|
||||
case RC_MEMSIZE_BIT_2:
|
||||
value->value.u32 = (value->value.u32 >> 2) & 1;
|
||||
break;
|
||||
|
||||
case RC_MEMSIZE_BIT_3:
|
||||
value->value.u32 = (value->value.u32 >> 3) & 1;
|
||||
break;
|
||||
|
||||
case RC_MEMSIZE_BIT_4:
|
||||
value->value.u32 = (value->value.u32 >> 4) & 1;
|
||||
break;
|
||||
|
||||
case RC_MEMSIZE_BIT_5:
|
||||
value->value.u32 = (value->value.u32 >> 5) & 1;
|
||||
break;
|
||||
|
||||
case RC_MEMSIZE_BIT_6:
|
||||
value->value.u32 = (value->value.u32 >> 6) & 1;
|
||||
break;
|
||||
|
||||
case RC_MEMSIZE_BIT_7:
|
||||
value->value.u32 = (value->value.u32 >> 7) & 1;
|
||||
break;
|
||||
|
||||
case RC_MEMSIZE_LOW:
|
||||
value->value.u32 = value->value.u32 & 0x0f;
|
||||
break;
|
||||
|
||||
case RC_MEMSIZE_HIGH:
|
||||
value->value.u32 = (value->value.u32 >> 4) & 0x0f;
|
||||
break;
|
||||
|
||||
case RC_MEMSIZE_BITCOUNT:
|
||||
value->value.u32 = rc_bits_set[(value->value.u32 & 0x0F)]
|
||||
+ rc_bits_set[((value->value.u32 >> 4) & 0x0F)];
|
||||
break;
|
||||
|
||||
case RC_MEMSIZE_16_BITS_BE:
|
||||
value->value.u32 = ((value->value.u32 & 0xFF00) >> 8) |
|
||||
((value->value.u32 & 0x00FF) << 8);
|
||||
break;
|
||||
|
||||
case RC_MEMSIZE_24_BITS_BE:
|
||||
value->value.u32 = ((value->value.u32 & 0xFF0000) >> 16) |
|
||||
(value->value.u32 & 0x00FF00) |
|
||||
((value->value.u32 & 0x0000FF) << 16);
|
||||
break;
|
||||
|
||||
case RC_MEMSIZE_32_BITS_BE:
|
||||
value->value.u32 = ((value->value.u32 & 0xFF000000) >> 24) |
|
||||
((value->value.u32 & 0x00FF0000) >> 8) |
|
||||
((value->value.u32 & 0x0000FF00) << 8) |
|
||||
((value->value.u32 & 0x000000FF) << 24);
|
||||
break;
|
||||
|
||||
case RC_MEMSIZE_FLOAT:
|
||||
rc_transform_memref_float(value);
|
||||
break;
|
||||
|
||||
case RC_MEMSIZE_FLOAT_BE:
|
||||
rc_transform_memref_float_be(value);
|
||||
break;
|
||||
|
||||
case RC_MEMSIZE_MBF32:
|
||||
rc_transform_memref_mbf32(value);
|
||||
break;
|
||||
|
||||
case RC_MEMSIZE_MBF32_LE:
|
||||
rc_transform_memref_mbf32_le(value);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static const uint32_t rc_memref_masks[] = {
|
||||
0x000000ff, /* RC_MEMSIZE_8_BITS */
|
||||
0x0000ffff, /* RC_MEMSIZE_16_BITS */
|
||||
0x00ffffff, /* RC_MEMSIZE_24_BITS */
|
||||
0xffffffff, /* RC_MEMSIZE_32_BITS */
|
||||
0x0000000f, /* RC_MEMSIZE_LOW */
|
||||
0x000000f0, /* RC_MEMSIZE_HIGH */
|
||||
0x00000001, /* RC_MEMSIZE_BIT_0 */
|
||||
0x00000002, /* RC_MEMSIZE_BIT_1 */
|
||||
0x00000004, /* RC_MEMSIZE_BIT_2 */
|
||||
0x00000008, /* RC_MEMSIZE_BIT_3 */
|
||||
0x00000010, /* RC_MEMSIZE_BIT_4 */
|
||||
0x00000020, /* RC_MEMSIZE_BIT_5 */
|
||||
0x00000040, /* RC_MEMSIZE_BIT_6 */
|
||||
0x00000080, /* RC_MEMSIZE_BIT_7 */
|
||||
0x000000ff, /* RC_MEMSIZE_BITCOUNT */
|
||||
0x0000ffff, /* RC_MEMSIZE_16_BITS_BE */
|
||||
0x00ffffff, /* RC_MEMSIZE_24_BITS_BE */
|
||||
0xffffffff, /* RC_MEMSIZE_32_BITS_BE */
|
||||
0xffffffff, /* RC_MEMSIZE_FLOAT */
|
||||
0xffffffff, /* RC_MEMSIZE_MBF32 */
|
||||
0xffffffff, /* RC_MEMSIZE_MBF32_LE */
|
||||
0xffffffff, /* RC_MEMSIZE_FLOAT_BE */
|
||||
0xffffffff /* RC_MEMSIZE_VARIABLE */
|
||||
};
|
||||
|
||||
uint32_t rc_memref_mask(uint8_t size) {
|
||||
const size_t index = (size_t)size;
|
||||
if (index >= sizeof(rc_memref_masks) / sizeof(rc_memref_masks[0]))
|
||||
return 0xffffffff;
|
||||
|
||||
return rc_memref_masks[index];
|
||||
}
|
||||
|
||||
/* all sizes less than 8-bits (1 byte) are mapped to 8-bits. 24-bit is mapped to 32-bit
|
||||
* as we don't expect the client to understand a request for 3 bytes. all other reads are
|
||||
* mapped to the little-endian read of the same size. */
|
||||
static const uint8_t rc_memref_shared_sizes[] = {
|
||||
RC_MEMSIZE_8_BITS, /* RC_MEMSIZE_8_BITS */
|
||||
RC_MEMSIZE_16_BITS, /* RC_MEMSIZE_16_BITS */
|
||||
RC_MEMSIZE_32_BITS, /* RC_MEMSIZE_24_BITS */
|
||||
RC_MEMSIZE_32_BITS, /* RC_MEMSIZE_32_BITS */
|
||||
RC_MEMSIZE_8_BITS, /* RC_MEMSIZE_LOW */
|
||||
RC_MEMSIZE_8_BITS, /* RC_MEMSIZE_HIGH */
|
||||
RC_MEMSIZE_8_BITS, /* RC_MEMSIZE_BIT_0 */
|
||||
RC_MEMSIZE_8_BITS, /* RC_MEMSIZE_BIT_1 */
|
||||
RC_MEMSIZE_8_BITS, /* RC_MEMSIZE_BIT_2 */
|
||||
RC_MEMSIZE_8_BITS, /* RC_MEMSIZE_BIT_3 */
|
||||
RC_MEMSIZE_8_BITS, /* RC_MEMSIZE_BIT_4 */
|
||||
RC_MEMSIZE_8_BITS, /* RC_MEMSIZE_BIT_5 */
|
||||
RC_MEMSIZE_8_BITS, /* RC_MEMSIZE_BIT_6 */
|
||||
RC_MEMSIZE_8_BITS, /* RC_MEMSIZE_BIT_7 */
|
||||
RC_MEMSIZE_8_BITS, /* RC_MEMSIZE_BITCOUNT */
|
||||
RC_MEMSIZE_16_BITS, /* RC_MEMSIZE_16_BITS_BE */
|
||||
RC_MEMSIZE_32_BITS, /* RC_MEMSIZE_24_BITS_BE */
|
||||
RC_MEMSIZE_32_BITS, /* RC_MEMSIZE_32_BITS_BE */
|
||||
RC_MEMSIZE_32_BITS, /* RC_MEMSIZE_FLOAT */
|
||||
RC_MEMSIZE_32_BITS, /* RC_MEMSIZE_MBF32 */
|
||||
RC_MEMSIZE_32_BITS, /* RC_MEMSIZE_MBF32_LE */
|
||||
RC_MEMSIZE_32_BITS, /* RC_MEMSIZE_FLOAT_BE */
|
||||
RC_MEMSIZE_32_BITS /* RC_MEMSIZE_VARIABLE */
|
||||
};
|
||||
|
||||
uint8_t rc_memref_shared_size(uint8_t size) {
|
||||
const size_t index = (size_t)size;
|
||||
if (index >= sizeof(rc_memref_shared_sizes) / sizeof(rc_memref_shared_sizes[0]))
|
||||
return size;
|
||||
|
||||
return rc_memref_shared_sizes[index];
|
||||
}
|
||||
|
||||
uint32_t rc_peek_value(uint32_t address, uint8_t size, rc_peek_t peek, void* ud) {
|
||||
if (!peek)
|
||||
return 0;
|
||||
|
||||
switch (size)
|
||||
{
|
||||
case RC_MEMSIZE_8_BITS:
|
||||
return peek(address, 1, ud);
|
||||
|
||||
case RC_MEMSIZE_16_BITS:
|
||||
return peek(address, 2, ud);
|
||||
|
||||
case RC_MEMSIZE_32_BITS:
|
||||
return peek(address, 4, ud);
|
||||
|
||||
default:
|
||||
{
|
||||
uint32_t value;
|
||||
const size_t index = (size_t)size;
|
||||
if (index >= sizeof(rc_memref_shared_sizes) / sizeof(rc_memref_shared_sizes[0]))
|
||||
return 0;
|
||||
|
||||
/* fetch the larger value and mask off the bits associated to the specified size
|
||||
* for correct deduction of prior value. non-prior memrefs should already be using
|
||||
* shared size memrefs to minimize the total number of memory reads required. */
|
||||
value = rc_peek_value(address, rc_memref_shared_sizes[index], peek, ud);
|
||||
return value & rc_memref_masks[index];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void rc_update_memref_value(rc_memref_value_t* memref, uint32_t new_value) {
|
||||
if (memref->value == new_value) {
|
||||
memref->changed = 0;
|
||||
}
|
||||
else {
|
||||
memref->prior = memref->value;
|
||||
memref->value = new_value;
|
||||
memref->changed = 1;
|
||||
}
|
||||
}
|
||||
|
||||
void rc_update_memref_values(rc_memref_t* memref, rc_peek_t peek, void* ud) {
|
||||
while (memref) {
|
||||
/* indirect memory references are not shared and will be updated in rc_get_memref_value */
|
||||
if (!memref->value.is_indirect)
|
||||
rc_update_memref_value(&memref->value, rc_peek_value(memref->address, memref->value.size, peek, ud));
|
||||
|
||||
memref = memref->next;
|
||||
}
|
||||
}
|
||||
|
||||
void rc_init_parse_state_memrefs(rc_parse_state_t* parse, rc_memref_t** memrefs) {
|
||||
parse->first_memref = memrefs;
|
||||
*memrefs = 0;
|
||||
}
|
||||
|
||||
static uint32_t rc_get_memref_value_value(const rc_memref_value_t* memref, int operand_type) {
|
||||
switch (operand_type)
|
||||
{
|
||||
/* most common case explicitly first, even though it could be handled by default case.
|
||||
* this helps the compiler to optimize if it turns the switch into a series of if/elses */
|
||||
case RC_OPERAND_ADDRESS:
|
||||
return memref->value;
|
||||
|
||||
case RC_OPERAND_DELTA:
|
||||
if (!memref->changed) {
|
||||
/* fallthrough */
|
||||
default:
|
||||
return memref->value;
|
||||
}
|
||||
/* fallthrough */
|
||||
case RC_OPERAND_PRIOR:
|
||||
return memref->prior;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t rc_get_memref_value(rc_memref_t* memref, int operand_type, rc_eval_state_t* eval_state) {
|
||||
/* if this is an indirect reference, handle the indirection. */
|
||||
if (memref->value.is_indirect) {
|
||||
const uint32_t new_address = memref->address + eval_state->add_address;
|
||||
rc_update_memref_value(&memref->value, rc_peek_value(new_address, memref->value.size, eval_state->peek, eval_state->peek_userdata));
|
||||
}
|
||||
|
||||
return rc_get_memref_value_value(&memref->value, operand_type);
|
||||
}
|
|
@ -0,0 +1,475 @@
|
|||
#include "rc_internal.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <ctype.h>
|
||||
#include <math.h>
|
||||
|
||||
#ifndef RC_DISABLE_LUA
|
||||
|
||||
RC_BEGIN_C_DECLS
|
||||
|
||||
#include <lua.h>
|
||||
#include <lauxlib.h>
|
||||
|
||||
RC_END_C_DECLS
|
||||
|
||||
#endif /* RC_DISABLE_LUA */
|
||||
|
||||
static int rc_parse_operand_lua(rc_operand_t* self, const char** memaddr, rc_parse_state_t* parse) {
|
||||
const char* aux = *memaddr;
|
||||
#ifndef RC_DISABLE_LUA
|
||||
const char* id;
|
||||
#endif
|
||||
|
||||
if (*aux++ != '@') {
|
||||
return RC_INVALID_LUA_OPERAND;
|
||||
}
|
||||
|
||||
if (!isalpha((unsigned char)*aux)) {
|
||||
return RC_INVALID_LUA_OPERAND;
|
||||
}
|
||||
|
||||
#ifndef RC_DISABLE_LUA
|
||||
id = aux;
|
||||
#endif
|
||||
|
||||
while (isalnum((unsigned char)*aux) || *aux == '_') {
|
||||
aux++;
|
||||
}
|
||||
|
||||
#ifndef RC_DISABLE_LUA
|
||||
|
||||
if (parse->L != 0) {
|
||||
if (!lua_istable(parse->L, parse->funcs_ndx)) {
|
||||
return RC_INVALID_LUA_OPERAND;
|
||||
}
|
||||
|
||||
lua_pushlstring(parse->L, id, aux - id);
|
||||
lua_gettable(parse->L, parse->funcs_ndx);
|
||||
|
||||
if (!lua_isfunction(parse->L, -1)) {
|
||||
lua_pop(parse->L, 1);
|
||||
return RC_INVALID_LUA_OPERAND;
|
||||
}
|
||||
|
||||
self->value.luafunc = luaL_ref(parse->L, LUA_REGISTRYINDEX);
|
||||
}
|
||||
|
||||
#else
|
||||
(void)parse;
|
||||
#endif /* RC_DISABLE_LUA */
|
||||
|
||||
self->type = RC_OPERAND_LUA;
|
||||
*memaddr = aux;
|
||||
return RC_OK;
|
||||
}
|
||||
|
||||
static int rc_parse_operand_memory(rc_operand_t* self, const char** memaddr, rc_parse_state_t* parse, uint8_t is_indirect) {
|
||||
const char* aux = *memaddr;
|
||||
uint32_t address;
|
||||
uint8_t size;
|
||||
int ret;
|
||||
|
||||
switch (*aux) {
|
||||
case 'd': case 'D':
|
||||
self->type = RC_OPERAND_DELTA;
|
||||
++aux;
|
||||
break;
|
||||
|
||||
case 'p': case 'P':
|
||||
self->type = RC_OPERAND_PRIOR;
|
||||
++aux;
|
||||
break;
|
||||
|
||||
case 'b': case 'B':
|
||||
self->type = RC_OPERAND_BCD;
|
||||
++aux;
|
||||
break;
|
||||
|
||||
case '~':
|
||||
self->type = RC_OPERAND_INVERTED;
|
||||
++aux;
|
||||
break;
|
||||
|
||||
default:
|
||||
self->type = RC_OPERAND_ADDRESS;
|
||||
break;
|
||||
}
|
||||
|
||||
ret = rc_parse_memref(&aux, &self->size, &address);
|
||||
if (ret != RC_OK)
|
||||
return ret;
|
||||
|
||||
size = rc_memref_shared_size(self->size);
|
||||
if (size != self->size && self->type == RC_OPERAND_PRIOR) {
|
||||
/* if the shared size differs from the requested size and it's a prior operation, we
|
||||
* have to check to make sure both sizes use the same mask, or the prior value may be
|
||||
* updated when bits outside the mask are modified, which would make it look like the
|
||||
* current value once the mask is applied. if the mask differs, create a new
|
||||
* non-shared record for tracking the prior data. */
|
||||
if (rc_memref_mask(size) != rc_memref_mask(self->size))
|
||||
size = self->size;
|
||||
}
|
||||
|
||||
self->value.memref = rc_alloc_memref(parse, address, size, is_indirect);
|
||||
if (parse->offset < 0)
|
||||
return parse->offset;
|
||||
|
||||
*memaddr = aux;
|
||||
return RC_OK;
|
||||
}
|
||||
|
||||
int rc_parse_operand(rc_operand_t* self, const char** memaddr, uint8_t is_indirect, rc_parse_state_t* parse) {
|
||||
const char* aux = *memaddr;
|
||||
char* end;
|
||||
int ret;
|
||||
unsigned long value;
|
||||
int negative;
|
||||
int allow_decimal = 0;
|
||||
|
||||
self->size = RC_MEMSIZE_32_BITS;
|
||||
|
||||
switch (*aux) {
|
||||
case 'h': case 'H': /* hex constant */
|
||||
if (aux[2] == 'x' || aux[2] == 'X') {
|
||||
/* H0x1234 is a typo - either H1234 or 0xH1234 was probably meant */
|
||||
return RC_INVALID_CONST_OPERAND;
|
||||
}
|
||||
|
||||
value = strtoul(++aux, &end, 16);
|
||||
if (end == aux)
|
||||
return RC_INVALID_CONST_OPERAND;
|
||||
|
||||
if (value > 0xffffffffU)
|
||||
value = 0xffffffffU;
|
||||
|
||||
self->type = RC_OPERAND_CONST;
|
||||
self->value.num = (unsigned)value;
|
||||
|
||||
aux = end;
|
||||
break;
|
||||
|
||||
case 'f': case 'F': /* floating point constant */
|
||||
if (isalpha((unsigned char)aux[1])) {
|
||||
ret = rc_parse_operand_memory(self, &aux, parse, is_indirect);
|
||||
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
break;
|
||||
}
|
||||
allow_decimal = 1;
|
||||
/* fall through */
|
||||
case 'v': case 'V': /* signed integer constant */
|
||||
++aux;
|
||||
/* fall through */
|
||||
case '+': case '-': /* signed integer constant */
|
||||
negative = 0;
|
||||
if (*aux == '-') {
|
||||
negative = 1;
|
||||
++aux;
|
||||
}
|
||||
else if (*aux == '+') {
|
||||
++aux;
|
||||
}
|
||||
|
||||
value = strtoul(aux, &end, 10);
|
||||
|
||||
if (*end == '.' && allow_decimal) {
|
||||
/* custom parser for decimal values to ignore locale */
|
||||
unsigned long shift = 1;
|
||||
unsigned long fraction = 0;
|
||||
|
||||
aux = end + 1;
|
||||
if (*aux < '0' || *aux > '9')
|
||||
return RC_INVALID_FP_OPERAND;
|
||||
|
||||
do {
|
||||
/* only keep as many digits as will fit in a 32-bit value to prevent overflow.
|
||||
* float only has around 7 digits of precision anyway. */
|
||||
if (shift < 1000000000) {
|
||||
fraction *= 10;
|
||||
fraction += (*aux - '0');
|
||||
shift *= 10;
|
||||
}
|
||||
++aux;
|
||||
} while (*aux >= '0' && *aux <= '9');
|
||||
|
||||
if (fraction != 0) {
|
||||
/* non-zero fractional part, convert to double and merge in integer portion */
|
||||
const double dbl_fraction = ((double)fraction) / ((double)shift);
|
||||
if (negative)
|
||||
self->value.dbl = ((double)(-((long)value))) - dbl_fraction;
|
||||
else
|
||||
self->value.dbl = (double)value + dbl_fraction;
|
||||
}
|
||||
else {
|
||||
/* fractional part is 0, just convert the integer portion */
|
||||
if (negative)
|
||||
self->value.dbl = (double)(-((long)value));
|
||||
else
|
||||
self->value.dbl = (double)value;
|
||||
}
|
||||
|
||||
self->type = RC_OPERAND_FP;
|
||||
}
|
||||
else {
|
||||
/* not a floating point value, make sure something was read and advance the read pointer */
|
||||
if (end == aux)
|
||||
return allow_decimal ? RC_INVALID_FP_OPERAND : RC_INVALID_CONST_OPERAND;
|
||||
|
||||
aux = end;
|
||||
|
||||
if (value > 0x7fffffffU)
|
||||
value = 0x7fffffffU;
|
||||
|
||||
self->type = RC_OPERAND_CONST;
|
||||
|
||||
if (negative)
|
||||
self->value.num = (unsigned)(-((long)value));
|
||||
else
|
||||
self->value.num = (unsigned)value;
|
||||
}
|
||||
break;
|
||||
|
||||
case '0':
|
||||
if (aux[1] == 'x' || aux[1] == 'X') { /* hex integer constant */
|
||||
/* fallthrough */ /* to default */
|
||||
default:
|
||||
ret = rc_parse_operand_memory(self, &aux, parse, is_indirect);
|
||||
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
break;
|
||||
}
|
||||
/* fallthrough */ /* to case '1' for case '0' where not '0x' */
|
||||
case '1': case '2': case '3': case '4': case '5': /* unsigned integer constant */
|
||||
case '6': case '7': case '8': case '9':
|
||||
value = strtoul(aux, &end, 10);
|
||||
if (end == aux)
|
||||
return RC_INVALID_CONST_OPERAND;
|
||||
|
||||
if (value > 0xffffffffU)
|
||||
value = 0xffffffffU;
|
||||
|
||||
self->type = RC_OPERAND_CONST;
|
||||
self->value.num = (unsigned)value;
|
||||
|
||||
aux = end;
|
||||
break;
|
||||
|
||||
case '@':
|
||||
ret = rc_parse_operand_lua(self, &aux, parse);
|
||||
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
*memaddr = aux;
|
||||
return RC_OK;
|
||||
}
|
||||
|
||||
#ifndef RC_DISABLE_LUA
|
||||
|
||||
typedef struct {
|
||||
rc_peek_t peek;
|
||||
void* ud;
|
||||
}
|
||||
rc_luapeek_t;
|
||||
|
||||
static int rc_luapeek(lua_State* L) {
|
||||
uint32_t address = (uint32_t)luaL_checkinteger(L, 1);
|
||||
uint32_t num_bytes = (uint32_t)luaL_checkinteger(L, 2);
|
||||
rc_luapeek_t* luapeek = (rc_luapeek_t*)lua_touserdata(L, 3);
|
||||
|
||||
uint32_t value = luapeek->peek(address, num_bytes, luapeek->ud);
|
||||
|
||||
lua_pushinteger(L, value);
|
||||
return 1;
|
||||
}
|
||||
|
||||
#endif /* RC_DISABLE_LUA */
|
||||
|
||||
int rc_operand_is_float_memref(const rc_operand_t* self) {
|
||||
switch (self->size) {
|
||||
case RC_MEMSIZE_FLOAT:
|
||||
case RC_MEMSIZE_FLOAT_BE:
|
||||
case RC_MEMSIZE_MBF32:
|
||||
case RC_MEMSIZE_MBF32_LE:
|
||||
return 1;
|
||||
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
int rc_operand_is_memref(const rc_operand_t* self) {
|
||||
switch (self->type) {
|
||||
case RC_OPERAND_CONST:
|
||||
case RC_OPERAND_FP:
|
||||
case RC_OPERAND_LUA:
|
||||
return 0;
|
||||
|
||||
default:
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
uint32_t rc_transform_operand_value(uint32_t value, const rc_operand_t* self) {
|
||||
switch (self->type)
|
||||
{
|
||||
case RC_OPERAND_BCD:
|
||||
switch (self->size)
|
||||
{
|
||||
case RC_MEMSIZE_8_BITS:
|
||||
value = ((value >> 4) & 0x0f) * 10
|
||||
+ ((value ) & 0x0f);
|
||||
break;
|
||||
|
||||
case RC_MEMSIZE_16_BITS:
|
||||
case RC_MEMSIZE_16_BITS_BE:
|
||||
value = ((value >> 12) & 0x0f) * 1000
|
||||
+ ((value >> 8) & 0x0f) * 100
|
||||
+ ((value >> 4) & 0x0f) * 10
|
||||
+ ((value ) & 0x0f);
|
||||
break;
|
||||
|
||||
case RC_MEMSIZE_24_BITS:
|
||||
case RC_MEMSIZE_24_BITS_BE:
|
||||
value = ((value >> 20) & 0x0f) * 100000
|
||||
+ ((value >> 16) & 0x0f) * 10000
|
||||
+ ((value >> 12) & 0x0f) * 1000
|
||||
+ ((value >> 8) & 0x0f) * 100
|
||||
+ ((value >> 4) & 0x0f) * 10
|
||||
+ ((value ) & 0x0f);
|
||||
break;
|
||||
|
||||
case RC_MEMSIZE_32_BITS:
|
||||
case RC_MEMSIZE_32_BITS_BE:
|
||||
case RC_MEMSIZE_VARIABLE:
|
||||
value = ((value >> 28) & 0x0f) * 10000000
|
||||
+ ((value >> 24) & 0x0f) * 1000000
|
||||
+ ((value >> 20) & 0x0f) * 100000
|
||||
+ ((value >> 16) & 0x0f) * 10000
|
||||
+ ((value >> 12) & 0x0f) * 1000
|
||||
+ ((value >> 8) & 0x0f) * 100
|
||||
+ ((value >> 4) & 0x0f) * 10
|
||||
+ ((value ) & 0x0f);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case RC_OPERAND_INVERTED:
|
||||
switch (self->size)
|
||||
{
|
||||
case RC_MEMSIZE_LOW:
|
||||
case RC_MEMSIZE_HIGH:
|
||||
value ^= 0x0f;
|
||||
break;
|
||||
|
||||
case RC_MEMSIZE_8_BITS:
|
||||
value ^= 0xff;
|
||||
break;
|
||||
|
||||
case RC_MEMSIZE_16_BITS:
|
||||
case RC_MEMSIZE_16_BITS_BE:
|
||||
value ^= 0xffff;
|
||||
break;
|
||||
|
||||
case RC_MEMSIZE_24_BITS:
|
||||
case RC_MEMSIZE_24_BITS_BE:
|
||||
value ^= 0xffffff;
|
||||
break;
|
||||
|
||||
case RC_MEMSIZE_32_BITS:
|
||||
case RC_MEMSIZE_32_BITS_BE:
|
||||
case RC_MEMSIZE_VARIABLE:
|
||||
value ^= 0xffffffff;
|
||||
break;
|
||||
|
||||
default:
|
||||
value ^= 0x01;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
void rc_evaluate_operand(rc_typed_value_t* result, rc_operand_t* self, rc_eval_state_t* eval_state) {
|
||||
#ifndef RC_DISABLE_LUA
|
||||
rc_luapeek_t luapeek;
|
||||
#endif /* RC_DISABLE_LUA */
|
||||
|
||||
/* step 1: read memory */
|
||||
switch (self->type) {
|
||||
case RC_OPERAND_CONST:
|
||||
result->type = RC_VALUE_TYPE_UNSIGNED;
|
||||
result->value.u32 = self->value.num;
|
||||
return;
|
||||
|
||||
case RC_OPERAND_FP:
|
||||
result->type = RC_VALUE_TYPE_FLOAT;
|
||||
result->value.f32 = (float)self->value.dbl;
|
||||
return;
|
||||
|
||||
case RC_OPERAND_LUA:
|
||||
result->type = RC_VALUE_TYPE_UNSIGNED;
|
||||
result->value.u32 = 0;
|
||||
|
||||
#ifndef RC_DISABLE_LUA
|
||||
if (eval_state->L != 0) {
|
||||
lua_rawgeti(eval_state->L, LUA_REGISTRYINDEX, self->value.luafunc);
|
||||
lua_pushcfunction(eval_state->L, rc_luapeek);
|
||||
|
||||
luapeek.peek = eval_state->peek;
|
||||
luapeek.ud = eval_state->peek_userdata;
|
||||
|
||||
lua_pushlightuserdata(eval_state->L, &luapeek);
|
||||
|
||||
if (lua_pcall(eval_state->L, 2, 1, 0) == LUA_OK) {
|
||||
if (lua_isboolean(eval_state->L, -1)) {
|
||||
result->value.u32 = (uint32_t)lua_toboolean(eval_state->L, -1);
|
||||
}
|
||||
else {
|
||||
result->value.u32 = (uint32_t)lua_tonumber(eval_state->L, -1);
|
||||
}
|
||||
}
|
||||
|
||||
lua_pop(eval_state->L, 1);
|
||||
}
|
||||
|
||||
#endif /* RC_DISABLE_LUA */
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
result->type = RC_VALUE_TYPE_UNSIGNED;
|
||||
result->value.u32 = rc_get_memref_value(self->value.memref, self->type, eval_state);
|
||||
break;
|
||||
}
|
||||
|
||||
/* step 2: convert read memory to desired format */
|
||||
rc_transform_memref_value(result, self->size);
|
||||
|
||||
/* step 3: apply logic (BCD/invert) */
|
||||
if (result->type == RC_VALUE_TYPE_UNSIGNED)
|
||||
result->value.u32 = rc_transform_operand_value(result->value.u32, self);
|
||||
}
|
|
@ -0,0 +1,197 @@
|
|||
#ifndef RC_INTERNAL_H
|
||||
#define RC_INTERNAL_H
|
||||
|
||||
#include "rc_runtime_types.h"
|
||||
#include "rc_util.h"
|
||||
|
||||
RC_BEGIN_C_DECLS
|
||||
|
||||
typedef struct rc_scratch_string {
|
||||
char* value;
|
||||
struct rc_scratch_string* left;
|
||||
struct rc_scratch_string* right;
|
||||
}
|
||||
rc_scratch_string_t;
|
||||
|
||||
#define RC_ALLOW_ALIGN(T) struct __align_ ## T { char ch; T t; };
|
||||
RC_ALLOW_ALIGN(rc_condition_t)
|
||||
RC_ALLOW_ALIGN(rc_condset_t)
|
||||
RC_ALLOW_ALIGN(rc_lboard_t)
|
||||
RC_ALLOW_ALIGN(rc_memref_t)
|
||||
RC_ALLOW_ALIGN(rc_operand_t)
|
||||
RC_ALLOW_ALIGN(rc_richpresence_t)
|
||||
RC_ALLOW_ALIGN(rc_richpresence_display_t)
|
||||
RC_ALLOW_ALIGN(rc_richpresence_display_part_t)
|
||||
RC_ALLOW_ALIGN(rc_richpresence_lookup_t)
|
||||
RC_ALLOW_ALIGN(rc_richpresence_lookup_item_t)
|
||||
RC_ALLOW_ALIGN(rc_scratch_string_t)
|
||||
RC_ALLOW_ALIGN(rc_trigger_t)
|
||||
RC_ALLOW_ALIGN(rc_value_t)
|
||||
RC_ALLOW_ALIGN(char)
|
||||
|
||||
#define RC_ALIGNOF(T) (sizeof(struct __align_ ## T) - sizeof(T))
|
||||
#define RC_OFFSETOF(o, t) (int)((char*)&(o.t) - (char*)&(o))
|
||||
|
||||
#define RC_ALLOC(t, p) ((t*)rc_alloc((p)->buffer, &(p)->offset, sizeof(t), RC_ALIGNOF(t), &(p)->scratch, RC_OFFSETOF((p)->scratch.objs, __ ## t)))
|
||||
#define RC_ALLOC_SCRATCH(t, p) ((t*)rc_alloc_scratch((p)->buffer, &(p)->offset, sizeof(t), RC_ALIGNOF(t), &(p)->scratch, RC_OFFSETOF((p)->scratch.objs, __ ## t)))
|
||||
|
||||
/* force alignment to 4 bytes on 32-bit systems, or 8 bytes on 64-bit systems */
|
||||
#define RC_ALIGN(n) (((n) + (sizeof(void*)-1)) & ~(sizeof(void*)-1))
|
||||
|
||||
typedef struct {
|
||||
rc_buffer_t buffer;
|
||||
rc_scratch_string_t* strings;
|
||||
|
||||
struct objs {
|
||||
rc_condition_t* __rc_condition_t;
|
||||
rc_condset_t* __rc_condset_t;
|
||||
rc_lboard_t* __rc_lboard_t;
|
||||
rc_memref_t* __rc_memref_t;
|
||||
rc_operand_t* __rc_operand_t;
|
||||
rc_richpresence_t* __rc_richpresence_t;
|
||||
rc_richpresence_display_t* __rc_richpresence_display_t;
|
||||
rc_richpresence_display_part_t* __rc_richpresence_display_part_t;
|
||||
rc_richpresence_lookup_t* __rc_richpresence_lookup_t;
|
||||
rc_richpresence_lookup_item_t* __rc_richpresence_lookup_item_t;
|
||||
rc_scratch_string_t __rc_scratch_string_t;
|
||||
rc_trigger_t* __rc_trigger_t;
|
||||
rc_value_t* __rc_value_t;
|
||||
} objs;
|
||||
}
|
||||
rc_scratch_t;
|
||||
|
||||
enum {
|
||||
RC_VALUE_TYPE_NONE,
|
||||
RC_VALUE_TYPE_UNSIGNED,
|
||||
RC_VALUE_TYPE_SIGNED,
|
||||
RC_VALUE_TYPE_FLOAT
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
union {
|
||||
uint32_t u32;
|
||||
int32_t i32;
|
||||
float f32;
|
||||
} value;
|
||||
|
||||
char type;
|
||||
}
|
||||
rc_typed_value_t;
|
||||
|
||||
#define RC_MEASURED_UNKNOWN 0xFFFFFFFF
|
||||
|
||||
typedef struct {
|
||||
rc_typed_value_t add_value;/* AddSource/SubSource */
|
||||
int32_t add_hits; /* AddHits */
|
||||
uint32_t add_address; /* AddAddress */
|
||||
|
||||
rc_peek_t peek;
|
||||
void* peek_userdata;
|
||||
lua_State* L;
|
||||
|
||||
rc_typed_value_t measured_value; /* Measured */
|
||||
uint8_t was_reset; /* ResetIf triggered */
|
||||
uint8_t has_hits; /* one of more hit counts is non-zero */
|
||||
uint8_t primed; /* true if all non-Trigger conditions are true */
|
||||
uint8_t measured_from_hits; /* true if the measured_value came from a condition's hit count */
|
||||
uint8_t was_cond_reset; /* ResetNextIf triggered */
|
||||
}
|
||||
rc_eval_state_t;
|
||||
|
||||
typedef struct {
|
||||
int32_t offset;
|
||||
|
||||
lua_State* L;
|
||||
int funcs_ndx;
|
||||
|
||||
void* buffer;
|
||||
rc_scratch_t scratch;
|
||||
|
||||
rc_memref_t** first_memref;
|
||||
rc_value_t** variables;
|
||||
|
||||
uint32_t measured_target;
|
||||
int lines_read;
|
||||
|
||||
uint8_t has_required_hits;
|
||||
uint8_t measured_as_percent;
|
||||
}
|
||||
rc_parse_state_t;
|
||||
|
||||
void rc_init_parse_state(rc_parse_state_t* parse, void* buffer, lua_State* L, int funcs_ndx);
|
||||
void rc_init_parse_state_memrefs(rc_parse_state_t* parse, rc_memref_t** memrefs);
|
||||
void rc_init_parse_state_variables(rc_parse_state_t* parse, rc_value_t** variables);
|
||||
void rc_destroy_parse_state(rc_parse_state_t* parse);
|
||||
|
||||
void* rc_alloc(void* pointer, int32_t* offset, uint32_t size, uint32_t alignment, rc_scratch_t* scratch, uint32_t scratch_object_pointer_offset);
|
||||
void* rc_alloc_scratch(void* pointer, int32_t* offset, uint32_t size, uint32_t alignment, rc_scratch_t* scratch, uint32_t scratch_object_pointer_offset);
|
||||
char* rc_alloc_str(rc_parse_state_t* parse, const char* text, size_t length);
|
||||
|
||||
rc_memref_t* rc_alloc_memref(rc_parse_state_t* parse, uint32_t address, uint8_t size, uint8_t is_indirect);
|
||||
int rc_parse_memref(const char** memaddr, uint8_t* size, uint32_t* address);
|
||||
void rc_update_memref_values(rc_memref_t* memref, rc_peek_t peek, void* ud);
|
||||
void rc_update_memref_value(rc_memref_value_t* memref, uint32_t value);
|
||||
uint32_t rc_get_memref_value(rc_memref_t* memref, int operand_type, rc_eval_state_t* eval_state);
|
||||
uint8_t rc_memref_shared_size(uint8_t size);
|
||||
uint32_t rc_memref_mask(uint8_t size);
|
||||
void rc_transform_memref_value(rc_typed_value_t* value, uint8_t size);
|
||||
uint32_t rc_peek_value(uint32_t address, uint8_t size, rc_peek_t peek, void* ud);
|
||||
|
||||
void rc_parse_trigger_internal(rc_trigger_t* self, const char** memaddr, rc_parse_state_t* parse);
|
||||
int rc_trigger_state_active(int state);
|
||||
|
||||
rc_condset_t* rc_parse_condset(const char** memaddr, rc_parse_state_t* parse, int is_value);
|
||||
int rc_test_condset(rc_condset_t* self, rc_eval_state_t* eval_state);
|
||||
void rc_reset_condset(rc_condset_t* self);
|
||||
|
||||
enum {
|
||||
RC_PROCESSING_COMPARE_DEFAULT = 0,
|
||||
RC_PROCESSING_COMPARE_MEMREF_TO_CONST,
|
||||
RC_PROCESSING_COMPARE_MEMREF_TO_DELTA,
|
||||
RC_PROCESSING_COMPARE_MEMREF_TO_MEMREF,
|
||||
RC_PROCESSING_COMPARE_DELTA_TO_MEMREF,
|
||||
RC_PROCESSING_COMPARE_DELTA_TO_CONST,
|
||||
RC_PROCESSING_COMPARE_MEMREF_TO_CONST_TRANSFORMED,
|
||||
RC_PROCESSING_COMPARE_MEMREF_TO_DELTA_TRANSFORMED,
|
||||
RC_PROCESSING_COMPARE_MEMREF_TO_MEMREF_TRANSFORMED,
|
||||
RC_PROCESSING_COMPARE_DELTA_TO_MEMREF_TRANSFORMED,
|
||||
RC_PROCESSING_COMPARE_DELTA_TO_CONST_TRANSFORMED,
|
||||
RC_PROCESSING_COMPARE_ALWAYS_TRUE,
|
||||
RC_PROCESSING_COMPARE_ALWAYS_FALSE
|
||||
};
|
||||
|
||||
rc_condition_t* rc_parse_condition(const char** memaddr, rc_parse_state_t* parse, uint8_t is_indirect);
|
||||
int rc_test_condition(rc_condition_t* self, rc_eval_state_t* eval_state);
|
||||
void rc_evaluate_condition_value(rc_typed_value_t* value, rc_condition_t* self, rc_eval_state_t* eval_state);
|
||||
int rc_condition_is_combining(const rc_condition_t* self);
|
||||
|
||||
int rc_parse_operand(rc_operand_t* self, const char** memaddr, uint8_t is_indirect, rc_parse_state_t* parse);
|
||||
void rc_evaluate_operand(rc_typed_value_t* value, rc_operand_t* self, rc_eval_state_t* eval_state);
|
||||
int rc_operand_is_float_memref(const rc_operand_t* self);
|
||||
int rc_operand_is_float(const rc_operand_t* self);
|
||||
|
||||
void rc_parse_value_internal(rc_value_t* self, const char** memaddr, rc_parse_state_t* parse);
|
||||
int rc_evaluate_value_typed(rc_value_t* self, rc_typed_value_t* value, rc_peek_t peek, void* ud, lua_State* L);
|
||||
void rc_reset_value(rc_value_t* self);
|
||||
int rc_value_from_hits(rc_value_t* self);
|
||||
rc_value_t* rc_alloc_helper_variable(const char* memaddr, size_t 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_typed_value_convert(rc_typed_value_t* value, char new_type);
|
||||
void rc_typed_value_add(rc_typed_value_t* value, const rc_typed_value_t* amount);
|
||||
void rc_typed_value_multiply(rc_typed_value_t* value, const rc_typed_value_t* amount);
|
||||
void rc_typed_value_divide(rc_typed_value_t* value, const rc_typed_value_t* amount);
|
||||
void rc_typed_value_negate(rc_typed_value_t* value);
|
||||
int rc_typed_value_compare(const rc_typed_value_t* value1, const rc_typed_value_t* value2, char oper);
|
||||
void rc_typed_value_from_memref_value(rc_typed_value_t* value, const rc_memref_value_t* memref);
|
||||
|
||||
int rc_format_typed_value(char* buffer, size_t size, const rc_typed_value_t* value, int format);
|
||||
|
||||
void rc_parse_lboard_internal(rc_lboard_t* self, const char* memaddr, rc_parse_state_t* parse);
|
||||
int rc_lboard_state_active(int state);
|
||||
|
||||
void rc_parse_richpresence_internal(rc_richpresence_t* self, const char* script, rc_parse_state_t* parse);
|
||||
|
||||
RC_END_C_DECLS
|
||||
|
||||
#endif /* RC_INTERNAL_H */
|
|
@ -0,0 +1,854 @@
|
|||
#include "rc_validate.h"
|
||||
|
||||
#include "rc_consoles.h"
|
||||
#include "rc_internal.h"
|
||||
|
||||
#include "../rc_compat.h"
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
static int rc_validate_memref(const rc_memref_t* memref, char result[], const size_t result_size, uint32_t console_id, uint32_t 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, uint32_t max_address)
|
||||
{
|
||||
while (memref) {
|
||||
if (!rc_validate_memref(memref, result, result_size, 0, max_address))
|
||||
return 0;
|
||||
|
||||
memref = memref->next;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static uint32_t rc_console_max_address(uint32_t 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, uint32_t console_id)
|
||||
{
|
||||
const uint32_t 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 uint32_t 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 uint32_t rc_scale_value(uint32_t value, uint8_t 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 (uint32_t)scaled;
|
||||
}
|
||||
|
||||
case RC_OPERATOR_DIV:
|
||||
{
|
||||
const uint32_t 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(uint32_t min_val, uint32_t max_val, char oper, uint32_t 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, uint32_t console_id, uint32_t max_address)
|
||||
{
|
||||
const rc_condition_t* cond;
|
||||
char buffer[128];
|
||||
uint32_t 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) {
|
||||
uint32_t 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;
|
||||
|
||||
case RC_CONDITION_RESET_IF:
|
||||
if (cond->required_hits == 1) {
|
||||
snprintf(result, result_size, "Condition %d: Hit target of 1 is redundant on ResetIf", index);
|
||||
return 0;
|
||||
}
|
||||
/* fallthrough */ /* to default */
|
||||
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);
|
||||
|
||||
uint32_t 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, uint32_t 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, uint32_t console_id)
|
||||
{
|
||||
const uint32_t 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, uint32_t value1, int comparison2, uint32_t 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;
|
||||
uint32_t 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, uint32_t console_id, uint32_t 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, uint32_t 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, uint32_t console_id)
|
||||
{
|
||||
const uint32_t max_address = rc_console_max_address(console_id);
|
||||
return rc_validate_trigger_internal(trigger, result, result_size, console_id, max_address);
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
#ifndef RC_VALIDATE_H
|
||||
#define RC_VALIDATE_H
|
||||
|
||||
#include "rc_runtime_types.h"
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
RC_BEGIN_C_DECLS
|
||||
|
||||
int rc_validate_memrefs(const rc_memref_t* memref, char result[], const size_t result_size, uint32_t max_address);
|
||||
|
||||
int rc_validate_condset(const rc_condset_t* condset, char result[], const size_t result_size, uint32_t max_address);
|
||||
int rc_validate_trigger(const rc_trigger_t* trigger, char result[], const size_t result_size, uint32_t max_address);
|
||||
|
||||
int rc_validate_memrefs_for_console(const rc_memref_t* memref, char result[], const size_t result_size, uint32_t console_id);
|
||||
|
||||
int rc_validate_condset_for_console(const rc_condset_t* condset, char result[], const size_t result_size, uint32_t console_id);
|
||||
int rc_validate_trigger_for_console(const rc_trigger_t* trigger, char result[], const size_t result_size, uint32_t console_id);
|
||||
|
||||
RC_END_C_DECLS
|
||||
|
||||
#endif /* RC_VALIDATE_H */
|
|
@ -0,0 +1,829 @@
|
|||
#include "rc_internal.h"
|
||||
|
||||
#include "../rc_compat.h"
|
||||
|
||||
#include <ctype.h>
|
||||
|
||||
/* special formats only used by rc_richpresence_display_part_t.display_type. must not overlap other RC_FORMAT values */
|
||||
enum {
|
||||
RC_FORMAT_STRING = 101,
|
||||
RC_FORMAT_LOOKUP = 102,
|
||||
RC_FORMAT_UNKNOWN_MACRO = 103,
|
||||
RC_FORMAT_ASCIICHAR = 104,
|
||||
RC_FORMAT_UNICODECHAR = 105
|
||||
};
|
||||
|
||||
static rc_memref_value_t* rc_alloc_helper_variable_memref_value(const char* memaddr, int memaddr_len, rc_parse_state_t* parse) {
|
||||
const char* end;
|
||||
rc_value_t* variable;
|
||||
uint32_t address;
|
||||
uint8_t size;
|
||||
|
||||
/* single memory reference lookups without a modifier flag can be handled without a variable */
|
||||
end = memaddr;
|
||||
if (rc_parse_memref(&end, &size, &address) == RC_OK) {
|
||||
/* make sure the entire memaddr was consumed. if not, there's an operator and it's a comparison, not a memory reference */
|
||||
if (end == &memaddr[memaddr_len]) {
|
||||
/* if it's not a derived size, we can reference the memref directly */
|
||||
if (rc_memref_shared_size(size) == size)
|
||||
return &rc_alloc_memref(parse, address, size, 0)->value;
|
||||
}
|
||||
}
|
||||
|
||||
/* not a simple memory reference, need to create a variable */
|
||||
variable = rc_alloc_helper_variable(memaddr, memaddr_len, parse);
|
||||
if (!variable)
|
||||
return NULL;
|
||||
|
||||
return &variable->value;
|
||||
}
|
||||
|
||||
static const char* rc_parse_line(const char* line, const char** end, rc_parse_state_t* parse) {
|
||||
const char* nextline;
|
||||
const char* endline;
|
||||
|
||||
/* get a single line */
|
||||
nextline = line;
|
||||
while (*nextline && *nextline != '\n')
|
||||
++nextline;
|
||||
|
||||
/* if a trailing comment marker (//) exists, the line stops there */
|
||||
endline = line;
|
||||
while (endline < nextline && (endline[0] != '/' || endline[1] != '/' || (endline > line && endline[-1] == '\\')))
|
||||
++endline;
|
||||
|
||||
if (endline == nextline) {
|
||||
/* trailing whitespace on a line without a comment marker may be significant, just remove the line ending */
|
||||
if (endline > line && endline[-1] == '\r')
|
||||
--endline;
|
||||
} else {
|
||||
/* remove trailing whitespace before the comment marker */
|
||||
while (endline > line && isspace((int)((unsigned char*)endline)[-1]))
|
||||
--endline;
|
||||
}
|
||||
|
||||
/* point end at the first character to ignore, it makes subtraction for length easier */
|
||||
*end = endline;
|
||||
|
||||
/* tally the line */
|
||||
++parse->lines_read;
|
||||
|
||||
/* skip the newline character so we're pointing at the next line */
|
||||
if (*nextline == '\n')
|
||||
++nextline;
|
||||
|
||||
return nextline;
|
||||
}
|
||||
|
||||
typedef struct rc_richpresence_builtin_macro_t {
|
||||
const char* name;
|
||||
size_t name_len;
|
||||
uint8_t display_type;
|
||||
} rc_richpresence_builtin_macro_t;
|
||||
|
||||
static rc_richpresence_display_t* rc_parse_richpresence_display_internal(const char* line, const char* endline, rc_parse_state_t* parse, rc_richpresence_lookup_t* first_lookup) {
|
||||
rc_richpresence_display_t* self;
|
||||
rc_richpresence_display_part_t* part;
|
||||
rc_richpresence_display_part_t** next;
|
||||
rc_richpresence_lookup_t* lookup;
|
||||
const char* ptr;
|
||||
const char* in;
|
||||
char* out;
|
||||
|
||||
if (endline - line < 1) {
|
||||
parse->offset = RC_MISSING_DISPLAY_STRING;
|
||||
return 0;
|
||||
}
|
||||
|
||||
{
|
||||
self = RC_ALLOC(rc_richpresence_display_t, parse);
|
||||
memset(self, 0, sizeof(rc_richpresence_display_t));
|
||||
next = &self->display;
|
||||
}
|
||||
|
||||
/* break the string up on macros: text @macro() moretext */
|
||||
do {
|
||||
ptr = line;
|
||||
while (ptr < endline) {
|
||||
if (*ptr == '@' && (ptr == line || ptr[-1] != '\\')) /* ignore escaped @s */
|
||||
break;
|
||||
|
||||
++ptr;
|
||||
}
|
||||
|
||||
if (ptr > line) {
|
||||
part = RC_ALLOC(rc_richpresence_display_part_t, parse);
|
||||
memset(part, 0, sizeof(rc_richpresence_display_part_t));
|
||||
*next = part;
|
||||
next = &part->next;
|
||||
|
||||
/* handle string part */
|
||||
part->display_type = RC_FORMAT_STRING;
|
||||
part->text = rc_alloc_str(parse, line, (int)(ptr - line));
|
||||
if (part->text) {
|
||||
/* remove backslashes used for escaping */
|
||||
in = part->text;
|
||||
while (*in && *in != '\\')
|
||||
++in;
|
||||
|
||||
if (*in == '\\') {
|
||||
out = (char*)in++;
|
||||
while (*in) {
|
||||
*out++ = *in++;
|
||||
if (*in == '\\')
|
||||
++in;
|
||||
}
|
||||
*out = '\0';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (*ptr == '@') {
|
||||
/* handle macro part */
|
||||
size_t macro_len;
|
||||
|
||||
line = ++ptr;
|
||||
while (ptr < endline && *ptr != '(')
|
||||
++ptr;
|
||||
|
||||
if (ptr == endline) {
|
||||
parse->offset = RC_MISSING_VALUE;
|
||||
return 0;
|
||||
}
|
||||
|
||||
macro_len = ptr - line;
|
||||
|
||||
part = RC_ALLOC(rc_richpresence_display_part_t, parse);
|
||||
memset(part, 0, sizeof(rc_richpresence_display_part_t));
|
||||
*next = part;
|
||||
next = &part->next;
|
||||
|
||||
part->display_type = RC_FORMAT_UNKNOWN_MACRO;
|
||||
|
||||
/* find the lookup and hook it up */
|
||||
lookup = first_lookup;
|
||||
while (lookup) {
|
||||
if (strncmp(lookup->name, line, macro_len) == 0 && lookup->name[macro_len] == '\0') {
|
||||
part->text = lookup->name;
|
||||
part->lookup = lookup;
|
||||
part->display_type = lookup->format;
|
||||
break;
|
||||
}
|
||||
|
||||
lookup = lookup->next;
|
||||
}
|
||||
|
||||
if (!lookup) {
|
||||
static const rc_richpresence_builtin_macro_t builtin_macros[] = {
|
||||
{"Number", 6, RC_FORMAT_VALUE},
|
||||
{"Score", 5, RC_FORMAT_SCORE},
|
||||
{"Centiseconds", 12, RC_FORMAT_CENTISECS},
|
||||
{"Seconds", 7, RC_FORMAT_SECONDS},
|
||||
{"Minutes", 7, RC_FORMAT_MINUTES},
|
||||
{"SecondsAsMinutes", 16, RC_FORMAT_SECONDS_AS_MINUTES},
|
||||
{"ASCIIChar", 9, RC_FORMAT_ASCIICHAR},
|
||||
{"UnicodeChar", 11, RC_FORMAT_UNICODECHAR},
|
||||
{"Float1", 6, RC_FORMAT_FLOAT1},
|
||||
{"Float2", 6, RC_FORMAT_FLOAT2},
|
||||
{"Float3", 6, RC_FORMAT_FLOAT3},
|
||||
{"Float4", 6, RC_FORMAT_FLOAT4},
|
||||
{"Float5", 6, RC_FORMAT_FLOAT5},
|
||||
{"Float6", 6, RC_FORMAT_FLOAT6},
|
||||
{"Fixed1", 6, RC_FORMAT_FIXED1},
|
||||
{"Fixed2", 6, RC_FORMAT_FIXED2},
|
||||
{"Fixed3", 6, RC_FORMAT_FIXED3},
|
||||
{"Unsigned", 8, RC_FORMAT_UNSIGNED_VALUE}
|
||||
};
|
||||
size_t i;
|
||||
|
||||
for (i = 0; i < sizeof(builtin_macros) / sizeof(builtin_macros[0]); ++i) {
|
||||
if (macro_len == builtin_macros[i].name_len &&
|
||||
memcmp(builtin_macros[i].name, line, builtin_macros[i].name_len) == 0) {
|
||||
part->text = builtin_macros[i].name;
|
||||
part->lookup = NULL;
|
||||
part->display_type = builtin_macros[i].display_type;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* find the closing parenthesis */
|
||||
in = line;
|
||||
line = ++ptr;
|
||||
while (ptr < endline && *ptr != ')')
|
||||
++ptr;
|
||||
|
||||
if (*ptr != ')') {
|
||||
/* non-terminated macro, dump the macro and the remaining portion of the line */
|
||||
--in; /* already skipped over @ */
|
||||
part->display_type = RC_FORMAT_STRING;
|
||||
part->text = rc_alloc_str(parse, in, (int)(ptr - in));
|
||||
}
|
||||
else if (part->display_type != RC_FORMAT_UNKNOWN_MACRO) {
|
||||
part->value = rc_alloc_helper_variable_memref_value(line, (int)(ptr - line), parse);
|
||||
if (parse->offset < 0)
|
||||
return 0;
|
||||
|
||||
++ptr;
|
||||
}
|
||||
else {
|
||||
/* assert: the allocated string is going to be smaller than the memory used for the parameter of the macro */
|
||||
++ptr;
|
||||
part->text = rc_alloc_str(parse, in, (int)(ptr - in));
|
||||
}
|
||||
}
|
||||
|
||||
line = ptr;
|
||||
} while (line < endline);
|
||||
|
||||
*next = 0;
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
static int rc_richpresence_lookup_item_count(rc_richpresence_lookup_item_t* item)
|
||||
{
|
||||
if (item == NULL)
|
||||
return 0;
|
||||
|
||||
return (rc_richpresence_lookup_item_count(item->left) + rc_richpresence_lookup_item_count(item->right) + 1);
|
||||
}
|
||||
|
||||
static void rc_rebalance_richpresence_lookup_get_items(rc_richpresence_lookup_item_t* root,
|
||||
rc_richpresence_lookup_item_t** items, int* index)
|
||||
{
|
||||
if (root->left != NULL)
|
||||
rc_rebalance_richpresence_lookup_get_items(root->left, items, index);
|
||||
|
||||
items[*index] = root;
|
||||
++(*index);
|
||||
|
||||
if (root->right != NULL)
|
||||
rc_rebalance_richpresence_lookup_get_items(root->right, items, index);
|
||||
}
|
||||
|
||||
static void rc_rebalance_richpresence_lookup_rebuild(rc_richpresence_lookup_item_t** root,
|
||||
rc_richpresence_lookup_item_t** items, int first, int last)
|
||||
{
|
||||
int mid = (first + last) / 2;
|
||||
rc_richpresence_lookup_item_t* item = items[mid];
|
||||
*root = item;
|
||||
|
||||
if (mid == first)
|
||||
item->left = NULL;
|
||||
else
|
||||
rc_rebalance_richpresence_lookup_rebuild(&item->left, items, first, mid - 1);
|
||||
|
||||
if (mid == last)
|
||||
item->right = NULL;
|
||||
else
|
||||
rc_rebalance_richpresence_lookup_rebuild(&item->right, items, mid + 1, last);
|
||||
}
|
||||
|
||||
static void rc_rebalance_richpresence_lookup(rc_richpresence_lookup_item_t** root, rc_parse_state_t* parse)
|
||||
{
|
||||
rc_richpresence_lookup_item_t** items;
|
||||
int index;
|
||||
int size;
|
||||
|
||||
/* don't bother rebalancing one or two items */
|
||||
int count = rc_richpresence_lookup_item_count(*root);
|
||||
if (count < 3)
|
||||
return;
|
||||
|
||||
/* allocate space for the flattened list in scratch memory */
|
||||
size = count * sizeof(rc_richpresence_lookup_item_t*);
|
||||
items = (rc_richpresence_lookup_item_t**)rc_buffer_alloc(&parse->scratch.buffer, size);
|
||||
|
||||
/* if allocation fails, we can still use the unbalanced tree, so just bail out */
|
||||
if (items == NULL)
|
||||
return;
|
||||
|
||||
/* flatten the list */
|
||||
index = 0;
|
||||
rc_rebalance_richpresence_lookup_get_items(*root, items, &index);
|
||||
|
||||
/* and rebuild it as a balanced tree */
|
||||
rc_rebalance_richpresence_lookup_rebuild(root, items, 0, count - 1);
|
||||
}
|
||||
|
||||
static void rc_insert_richpresence_lookup_item(rc_richpresence_lookup_t* lookup,
|
||||
uint32_t first, uint32_t last, const char* label, size_t label_len, rc_parse_state_t* parse)
|
||||
{
|
||||
rc_richpresence_lookup_item_t** next;
|
||||
rc_richpresence_lookup_item_t* item;
|
||||
|
||||
next = &lookup->root;
|
||||
while ((item = *next) != NULL) {
|
||||
if (first > item->last) {
|
||||
if (first == item->last + 1 &&
|
||||
strncmp(label, item->label, label_len) == 0 && item->label[label_len] == '\0') {
|
||||
item->last = last;
|
||||
return;
|
||||
}
|
||||
|
||||
next = &item->right;
|
||||
}
|
||||
else if (last < item->first) {
|
||||
if (last == item->first - 1 &&
|
||||
strncmp(label, item->label, label_len) == 0 && item->label[label_len] == '\0') {
|
||||
item->first = first;
|
||||
return;
|
||||
}
|
||||
|
||||
next = &item->left;
|
||||
}
|
||||
else {
|
||||
parse->offset = RC_DUPLICATED_VALUE;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
item = RC_ALLOC_SCRATCH(rc_richpresence_lookup_item_t, parse);
|
||||
item->first = first;
|
||||
item->last = last;
|
||||
item->label = rc_alloc_str(parse, label, label_len);
|
||||
item->left = item->right = NULL;
|
||||
|
||||
*next = item;
|
||||
}
|
||||
|
||||
static const char* rc_parse_richpresence_lookup(rc_richpresence_lookup_t* lookup, const char* nextline, rc_parse_state_t* parse)
|
||||
{
|
||||
const char* line;
|
||||
const char* endline;
|
||||
const char* label;
|
||||
char* endptr = 0;
|
||||
uint32_t first, last;
|
||||
int base;
|
||||
|
||||
do
|
||||
{
|
||||
line = nextline;
|
||||
nextline = rc_parse_line(line, &endline, parse);
|
||||
|
||||
if (endline - line < 2) {
|
||||
/* ignore full line comments inside a lookup */
|
||||
if (line[0] == '/' && line[1] == '/')
|
||||
continue;
|
||||
|
||||
/* empty line indicates end of lookup */
|
||||
if (lookup->root)
|
||||
rc_rebalance_richpresence_lookup(&lookup->root, parse);
|
||||
break;
|
||||
}
|
||||
|
||||
/* "*=XXX" specifies default label if lookup does not provide a mapping for the value */
|
||||
if (line[0] == '*' && line[1] == '=') {
|
||||
line += 2;
|
||||
lookup->default_label = rc_alloc_str(parse, line, (int)(endline - line));
|
||||
continue;
|
||||
}
|
||||
|
||||
label = line;
|
||||
while (label < endline && *label != '=')
|
||||
++label;
|
||||
|
||||
if (label == endline) {
|
||||
parse->offset = RC_MISSING_VALUE;
|
||||
break;
|
||||
}
|
||||
++label;
|
||||
|
||||
do {
|
||||
/* get the value for the mapping */
|
||||
if (line[0] == '0' && line[1] == 'x') {
|
||||
line += 2;
|
||||
base = 16;
|
||||
} else {
|
||||
base = 10;
|
||||
}
|
||||
|
||||
first = (unsigned)strtoul(line, &endptr, base);
|
||||
|
||||
/* check for a range */
|
||||
if (*endptr != '-') {
|
||||
/* no range, just set last to first */
|
||||
last = first;
|
||||
}
|
||||
else {
|
||||
/* range, get last value */
|
||||
line = endptr + 1;
|
||||
|
||||
if (line[0] == '0' && line[1] == 'x') {
|
||||
line += 2;
|
||||
base = 16;
|
||||
} else {
|
||||
base = 10;
|
||||
}
|
||||
|
||||
last = (unsigned)strtoul(line, &endptr, base);
|
||||
}
|
||||
|
||||
/* ignore spaces after the number - was previously ignored as string was split on equals */
|
||||
while (*endptr == ' ')
|
||||
++endptr;
|
||||
|
||||
/* if we've found the equal sign, this is the last item */
|
||||
if (*endptr == '=') {
|
||||
rc_insert_richpresence_lookup_item(lookup, first, last, label, (int)(endline - label), parse);
|
||||
break;
|
||||
}
|
||||
|
||||
/* otherwise, if it's not a comma, it's an error */
|
||||
if (*endptr != ',') {
|
||||
parse->offset = RC_INVALID_CONST_OPERAND;
|
||||
break;
|
||||
}
|
||||
|
||||
/* insert the current item and continue scanning the next one */
|
||||
rc_insert_richpresence_lookup_item(lookup, first, last, label, (int)(endline - label), parse);
|
||||
line = endptr + 1;
|
||||
} while (line < endline);
|
||||
|
||||
} while (parse->offset > 0);
|
||||
|
||||
return nextline;
|
||||
}
|
||||
|
||||
void rc_parse_richpresence_internal(rc_richpresence_t* self, const char* script, rc_parse_state_t* parse) {
|
||||
rc_richpresence_display_t** nextdisplay;
|
||||
rc_richpresence_lookup_t* firstlookup = NULL;
|
||||
rc_richpresence_lookup_t** nextlookup = &firstlookup;
|
||||
rc_richpresence_lookup_t* lookup;
|
||||
rc_trigger_t* trigger;
|
||||
char format[64];
|
||||
const char* display = 0;
|
||||
const char* line;
|
||||
const char* nextline;
|
||||
const char* endline;
|
||||
const char* ptr;
|
||||
int hasdisplay = 0;
|
||||
int display_line = 0;
|
||||
int chars;
|
||||
|
||||
/* special case for empty script to return 1 line read */
|
||||
if (!*script) {
|
||||
parse->lines_read = 1;
|
||||
parse->offset = RC_MISSING_DISPLAY_STRING;
|
||||
return;
|
||||
}
|
||||
|
||||
/* first pass: process macro initializers */
|
||||
line = script;
|
||||
while (*line) {
|
||||
nextline = rc_parse_line(line, &endline, parse);
|
||||
if (strncmp(line, "Lookup:", 7) == 0) {
|
||||
line += 7;
|
||||
|
||||
lookup = RC_ALLOC_SCRATCH(rc_richpresence_lookup_t, parse);
|
||||
lookup->name = rc_alloc_str(parse, line, (int)(endline - line));
|
||||
lookup->format = RC_FORMAT_LOOKUP;
|
||||
lookup->root = NULL;
|
||||
lookup->default_label = "";
|
||||
*nextlookup = lookup;
|
||||
nextlookup = &lookup->next;
|
||||
|
||||
nextline = rc_parse_richpresence_lookup(lookup, nextline, parse);
|
||||
if (parse->offset < 0)
|
||||
return;
|
||||
|
||||
} else if (strncmp(line, "Format:", 7) == 0) {
|
||||
line += 7;
|
||||
|
||||
lookup = RC_ALLOC_SCRATCH(rc_richpresence_lookup_t, parse);
|
||||
lookup->name = rc_alloc_str(parse, line, (int)(endline - line));
|
||||
lookup->root = NULL;
|
||||
lookup->default_label = "";
|
||||
*nextlookup = lookup;
|
||||
nextlookup = &lookup->next;
|
||||
|
||||
line = nextline;
|
||||
nextline = rc_parse_line(line, &endline, parse);
|
||||
if (parse->buffer && strncmp(line, "FormatType=", 11) == 0) {
|
||||
line += 11;
|
||||
|
||||
chars = (int)(endline - line);
|
||||
if (chars > 63)
|
||||
chars = 63;
|
||||
memcpy(format, line, chars);
|
||||
format[chars] = '\0';
|
||||
|
||||
lookup->format = (uint8_t)rc_parse_format(format);
|
||||
} else {
|
||||
lookup->format = RC_FORMAT_VALUE;
|
||||
}
|
||||
} else if (strncmp(line, "Display:", 8) == 0) {
|
||||
display = nextline;
|
||||
display_line = parse->lines_read;
|
||||
|
||||
/* scan as long as we find conditional lines or full line comments */
|
||||
do {
|
||||
line = nextline;
|
||||
nextline = rc_parse_line(line, &endline, parse);
|
||||
} while (*line == '?' || (line[0] == '/' && line[1] == '/'));
|
||||
}
|
||||
|
||||
line = nextline;
|
||||
}
|
||||
|
||||
*nextlookup = 0;
|
||||
self->first_lookup = firstlookup;
|
||||
|
||||
nextdisplay = &self->first_display;
|
||||
|
||||
/* second pass, process display string*/
|
||||
if (display) {
|
||||
/* point the parser back at the display strings */
|
||||
int lines_read = parse->lines_read;
|
||||
parse->lines_read = display_line;
|
||||
line = display;
|
||||
|
||||
nextline = rc_parse_line(line, &endline, parse);
|
||||
|
||||
do {
|
||||
if (line[0] == '?') {
|
||||
/* conditional display: ?trigger?string */
|
||||
ptr = ++line;
|
||||
while (ptr < endline && *ptr != '?')
|
||||
++ptr;
|
||||
|
||||
if (ptr < endline) {
|
||||
*nextdisplay = rc_parse_richpresence_display_internal(ptr + 1, endline, parse, firstlookup);
|
||||
if (parse->offset < 0)
|
||||
return;
|
||||
trigger = &((*nextdisplay)->trigger);
|
||||
rc_parse_trigger_internal(trigger, &line, parse);
|
||||
trigger->memrefs = 0;
|
||||
if (parse->offset < 0)
|
||||
return;
|
||||
if (parse->buffer)
|
||||
nextdisplay = &((*nextdisplay)->next);
|
||||
}
|
||||
}
|
||||
else if (line[0] != '/' || line[1] != '/') {
|
||||
break;
|
||||
}
|
||||
|
||||
line = nextline;
|
||||
nextline = rc_parse_line(line, &endline, parse);
|
||||
} while (1);
|
||||
|
||||
/* non-conditional display: string */
|
||||
*nextdisplay = rc_parse_richpresence_display_internal(line, endline, parse, firstlookup);
|
||||
if (*nextdisplay) {
|
||||
hasdisplay = 1;
|
||||
nextdisplay = &((*nextdisplay)->next);
|
||||
|
||||
/* restore the parser state */
|
||||
parse->lines_read = lines_read;
|
||||
}
|
||||
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 */
|
||||
*nextdisplay = 0;
|
||||
|
||||
if (!hasdisplay && parse->offset > 0) {
|
||||
parse->offset = RC_MISSING_DISPLAY_STRING;
|
||||
}
|
||||
}
|
||||
|
||||
int rc_richpresence_size_lines(const char* script, int* lines_read) {
|
||||
rc_richpresence_t* self;
|
||||
rc_parse_state_t parse;
|
||||
rc_memref_t* first_memref;
|
||||
rc_value_t* variables;
|
||||
rc_init_parse_state(&parse, 0, 0, 0);
|
||||
rc_init_parse_state_memrefs(&parse, &first_memref);
|
||||
rc_init_parse_state_variables(&parse, &variables);
|
||||
|
||||
self = RC_ALLOC(rc_richpresence_t, &parse);
|
||||
rc_parse_richpresence_internal(self, script, &parse);
|
||||
|
||||
if (lines_read)
|
||||
*lines_read = parse.lines_read;
|
||||
|
||||
rc_destroy_parse_state(&parse);
|
||||
return parse.offset;
|
||||
}
|
||||
|
||||
int rc_richpresence_size(const char* script) {
|
||||
return rc_richpresence_size_lines(script, NULL);
|
||||
}
|
||||
|
||||
rc_richpresence_t* rc_parse_richpresence(void* buffer, const char* script, lua_State* L, int funcs_ndx) {
|
||||
rc_richpresence_t* self;
|
||||
rc_parse_state_t parse;
|
||||
|
||||
if (!buffer || !script)
|
||||
return NULL;
|
||||
|
||||
rc_init_parse_state(&parse, buffer, L, funcs_ndx);
|
||||
|
||||
self = RC_ALLOC(rc_richpresence_t, &parse);
|
||||
rc_init_parse_state_memrefs(&parse, &self->memrefs);
|
||||
rc_init_parse_state_variables(&parse, &self->variables);
|
||||
|
||||
rc_parse_richpresence_internal(self, script, &parse);
|
||||
|
||||
rc_destroy_parse_state(&parse);
|
||||
return (parse.offset >= 0) ? self : NULL;
|
||||
}
|
||||
|
||||
void rc_update_richpresence(rc_richpresence_t* richpresence, rc_peek_t peek, void* peek_ud, lua_State* L) {
|
||||
rc_richpresence_display_t* display;
|
||||
|
||||
rc_update_memref_values(richpresence->memrefs, peek, peek_ud);
|
||||
rc_update_variables(richpresence->variables, peek, peek_ud, L);
|
||||
|
||||
for (display = richpresence->first_display; display; display = display->next) {
|
||||
if (display->trigger.has_required_hits)
|
||||
rc_test_trigger(&display->trigger, peek, peek_ud, L);
|
||||
}
|
||||
}
|
||||
|
||||
static int rc_evaluate_richpresence_display(rc_richpresence_display_part_t* part, char* buffer, size_t buffersize)
|
||||
{
|
||||
rc_richpresence_lookup_item_t* item;
|
||||
rc_typed_value_t value;
|
||||
char tmp[256];
|
||||
char* ptr = buffer;
|
||||
const char* text;
|
||||
size_t chars;
|
||||
|
||||
*ptr = '\0';
|
||||
while (part) {
|
||||
switch (part->display_type) {
|
||||
case RC_FORMAT_STRING:
|
||||
text = part->text;
|
||||
chars = strlen(text);
|
||||
break;
|
||||
|
||||
case RC_FORMAT_LOOKUP:
|
||||
rc_typed_value_from_memref_value(&value, part->value);
|
||||
rc_typed_value_convert(&value, RC_VALUE_TYPE_UNSIGNED);
|
||||
|
||||
text = part->lookup->default_label;
|
||||
item = part->lookup->root;
|
||||
while (item) {
|
||||
if (value.value.u32 > item->last) {
|
||||
item = item->right;
|
||||
}
|
||||
else if (value.value.u32 < item->first) {
|
||||
item = item->left;
|
||||
}
|
||||
else {
|
||||
text = item->label;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
chars = strlen(text);
|
||||
break;
|
||||
|
||||
case RC_FORMAT_ASCIICHAR:
|
||||
chars = 0;
|
||||
text = tmp;
|
||||
value.type = RC_VALUE_TYPE_UNSIGNED;
|
||||
|
||||
do {
|
||||
value.value.u32 = part->value->value;
|
||||
if (value.value.u32 == 0) {
|
||||
/* null terminator - skip over remaining character macros */
|
||||
while (part->next && part->next->display_type == RC_FORMAT_ASCIICHAR)
|
||||
part = part->next;
|
||||
break;
|
||||
}
|
||||
|
||||
if (value.value.u32 < 32 || value.value.u32 >= 127)
|
||||
value.value.u32 = '?';
|
||||
|
||||
tmp[chars++] = (char)value.value.u32;
|
||||
if (chars == sizeof(tmp) || !part->next || part->next->display_type != RC_FORMAT_ASCIICHAR)
|
||||
break;
|
||||
|
||||
part = part->next;
|
||||
} while (1);
|
||||
|
||||
tmp[chars] = '\0';
|
||||
break;
|
||||
|
||||
case RC_FORMAT_UNICODECHAR:
|
||||
chars = 0;
|
||||
text = tmp;
|
||||
value.type = RC_VALUE_TYPE_UNSIGNED;
|
||||
|
||||
do {
|
||||
value.value.u32 = part->value->value;
|
||||
if (value.value.u32 == 0) {
|
||||
/* null terminator - skip over remaining character macros */
|
||||
while (part->next && part->next->display_type == RC_FORMAT_UNICODECHAR)
|
||||
part = part->next;
|
||||
break;
|
||||
}
|
||||
|
||||
if (value.value.u32 < 32 || value.value.u32 > 65535)
|
||||
value.value.u32 = 0xFFFD; /* unicode replacement char */
|
||||
|
||||
if (value.value.u32 < 0x80) {
|
||||
tmp[chars++] = (char)value.value.u32;
|
||||
}
|
||||
else if (value.value.u32 < 0x0800) {
|
||||
tmp[chars + 1] = (char)(0x80 | (value.value.u32 & 0x3F)); value.value.u32 >>= 6;
|
||||
tmp[chars] = (char)(0xC0 | (value.value.u32 & 0x1F));
|
||||
chars += 2;
|
||||
}
|
||||
else {
|
||||
/* surrogate pair not supported, convert to replacement char */
|
||||
if (value.value.u32 >= 0xD800 && value.value.u32 < 0xE000)
|
||||
value.value.u32 = 0xFFFD;
|
||||
|
||||
tmp[chars + 2] = (char)(0x80 | (value.value.u32 & 0x3F)); value.value.u32 >>= 6;
|
||||
tmp[chars + 1] = (char)(0x80 | (value.value.u32 & 0x3F)); value.value.u32 >>= 6;
|
||||
tmp[chars] = (char)(0xE0 | (value.value.u32 & 0x1F));
|
||||
chars += 3;
|
||||
}
|
||||
|
||||
if (chars >= sizeof(tmp) - 3 || !part->next || part->next->display_type != RC_FORMAT_UNICODECHAR)
|
||||
break;
|
||||
|
||||
part = part->next;
|
||||
} while (1);
|
||||
|
||||
tmp[chars] = '\0';
|
||||
break;
|
||||
|
||||
case RC_FORMAT_UNKNOWN_MACRO:
|
||||
chars = snprintf(tmp, sizeof(tmp), "[Unknown macro]%s", part->text);
|
||||
text = tmp;
|
||||
break;
|
||||
|
||||
default:
|
||||
rc_typed_value_from_memref_value(&value, part->value);
|
||||
chars = rc_format_typed_value(tmp, sizeof(tmp), &value, part->display_type);
|
||||
text = tmp;
|
||||
break;
|
||||
}
|
||||
|
||||
if (chars > 0 && buffersize > 0) {
|
||||
if ((unsigned)chars >= buffersize) {
|
||||
/* prevent write past end of buffer */
|
||||
memcpy(ptr, text, buffersize - 1);
|
||||
ptr[buffersize - 1] = '\0';
|
||||
buffersize = 0;
|
||||
}
|
||||
else {
|
||||
memcpy(ptr, text, chars);
|
||||
ptr[chars] = '\0';
|
||||
buffersize -= (unsigned)chars;
|
||||
}
|
||||
}
|
||||
|
||||
ptr += chars;
|
||||
part = part->next;
|
||||
}
|
||||
|
||||
return (int)(ptr - buffer);
|
||||
}
|
||||
|
||||
int rc_get_richpresence_display_string(rc_richpresence_t* richpresence, char* buffer, size_t buffersize, rc_peek_t peek, void* peek_ud, lua_State* L) {
|
||||
rc_richpresence_display_t* display;
|
||||
|
||||
for (display = richpresence->first_display; display; display = display->next) {
|
||||
/* if we've reached the end of the condition list, process it */
|
||||
if (!display->next)
|
||||
return rc_evaluate_richpresence_display(display->display, buffer, buffersize);
|
||||
|
||||
/* triggers with required hits will be updated in rc_update_richpresence */
|
||||
if (!display->trigger.has_required_hits)
|
||||
rc_test_trigger(&display->trigger, peek, peek_ud, L);
|
||||
|
||||
/* if we've found a valid condition, process it */
|
||||
if (display->trigger.state == RC_TRIGGER_STATE_TRIGGERED)
|
||||
return rc_evaluate_richpresence_display(display->display, buffer, buffersize);
|
||||
}
|
||||
|
||||
buffer[0] = '\0';
|
||||
return 0;
|
||||
}
|
||||
|
||||
int rc_evaluate_richpresence(rc_richpresence_t* richpresence, char* buffer, size_t buffersize, rc_peek_t peek, void* peek_ud, lua_State* L) {
|
||||
rc_update_richpresence(richpresence, peek, peek_ud, L);
|
||||
return rc_get_richpresence_display_string(richpresence, buffer, buffersize, peek, peek_ud, L);
|
||||
}
|
||||
|
||||
void rc_reset_richpresence(rc_richpresence_t* self) {
|
||||
rc_richpresence_display_t* display;
|
||||
rc_value_t* variable;
|
||||
|
||||
for (display = self->first_display; display; display = display->next)
|
||||
rc_reset_trigger(&display->trigger);
|
||||
|
||||
for (variable = self->variables; variable; variable = variable->next)
|
||||
rc_reset_value(variable);
|
||||
}
|
|
@ -0,0 +1,892 @@
|
|||
#include "rc_runtime.h"
|
||||
#include "rc_internal.h"
|
||||
|
||||
#include "../rc_compat.h"
|
||||
#include "../rhash/md5.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#define RC_RICHPRESENCE_DISPLAY_BUFFER_SIZE 256
|
||||
|
||||
rc_runtime_t* rc_runtime_alloc(void) {
|
||||
rc_runtime_t* self = malloc(sizeof(rc_runtime_t));
|
||||
|
||||
if (self) {
|
||||
rc_runtime_init(self);
|
||||
self->owns_self = 1;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
void rc_runtime_init(rc_runtime_t* self) {
|
||||
memset(self, 0, sizeof(rc_runtime_t));
|
||||
self->next_memref = &self->memrefs;
|
||||
self->next_variable = &self->variables;
|
||||
}
|
||||
|
||||
void rc_runtime_destroy(rc_runtime_t* self) {
|
||||
uint32_t i;
|
||||
|
||||
if (self->triggers) {
|
||||
for (i = 0; i < self->trigger_count; ++i)
|
||||
free(self->triggers[i].buffer);
|
||||
|
||||
free(self->triggers);
|
||||
self->triggers = NULL;
|
||||
|
||||
self->trigger_count = self->trigger_capacity = 0;
|
||||
}
|
||||
|
||||
if (self->lboards) {
|
||||
for (i = 0; i < self->lboard_count; ++i)
|
||||
free(self->lboards[i].buffer);
|
||||
|
||||
free(self->lboards);
|
||||
self->lboards = NULL;
|
||||
|
||||
self->lboard_count = self->lboard_capacity = 0;
|
||||
}
|
||||
|
||||
while (self->richpresence) {
|
||||
rc_runtime_richpresence_t* previous = self->richpresence->previous;
|
||||
|
||||
free(self->richpresence->buffer);
|
||||
free(self->richpresence);
|
||||
self->richpresence = previous;
|
||||
}
|
||||
|
||||
self->next_memref = 0;
|
||||
self->memrefs = 0;
|
||||
|
||||
if (self->owns_self) {
|
||||
free(self);
|
||||
}
|
||||
}
|
||||
|
||||
void rc_runtime_checksum(const char* memaddr, uint8_t* md5) {
|
||||
md5_state_t state;
|
||||
md5_init(&state);
|
||||
md5_append(&state, (unsigned char*)memaddr, (int)strlen(memaddr));
|
||||
md5_finish(&state, md5);
|
||||
}
|
||||
|
||||
static char rc_runtime_allocated_memrefs(rc_runtime_t* self) {
|
||||
char owns_memref = 0;
|
||||
|
||||
/* if at least one memref was allocated within the object, we can't free the buffer when the object is deactivated */
|
||||
if (*self->next_memref != NULL) {
|
||||
owns_memref = 1;
|
||||
/* advance through the new memrefs so we're ready for the next allocation */
|
||||
do {
|
||||
self->next_memref = &(*self->next_memref)->next;
|
||||
} while (*self->next_memref != NULL);
|
||||
}
|
||||
|
||||
/* if at least one variable was allocated within the object, we can't free the buffer when the object is deactivated */
|
||||
if (*self->next_variable != NULL) {
|
||||
owns_memref = 1;
|
||||
/* advance through the new variables so we're ready for the next allocation */
|
||||
do {
|
||||
self->next_variable = &(*self->next_variable)->next;
|
||||
} while (*self->next_variable != NULL);
|
||||
}
|
||||
|
||||
return owns_memref;
|
||||
}
|
||||
|
||||
static void rc_runtime_deactivate_trigger_by_index(rc_runtime_t* self, uint32_t index) {
|
||||
if (self->triggers[index].owns_memrefs) {
|
||||
/* if the trigger has one or more memrefs in its buffer, we can't free the buffer.
|
||||
* just null out the trigger so the runtime processor will skip it
|
||||
*/
|
||||
rc_reset_trigger(self->triggers[index].trigger);
|
||||
self->triggers[index].trigger = NULL;
|
||||
}
|
||||
else {
|
||||
/* trigger doesn't own any memrefs, go ahead and free it, then replace it with the last trigger */
|
||||
free(self->triggers[index].buffer);
|
||||
|
||||
if (--self->trigger_count > index)
|
||||
memcpy(&self->triggers[index], &self->triggers[self->trigger_count], sizeof(rc_runtime_trigger_t));
|
||||
}
|
||||
}
|
||||
|
||||
void rc_runtime_deactivate_achievement(rc_runtime_t* self, uint32_t id) {
|
||||
uint32_t i;
|
||||
|
||||
for (i = 0; i < self->trigger_count; ++i) {
|
||||
if (self->triggers[i].id == id && self->triggers[i].trigger != NULL)
|
||||
rc_runtime_deactivate_trigger_by_index(self, i);
|
||||
}
|
||||
}
|
||||
|
||||
int rc_runtime_activate_achievement(rc_runtime_t* self, uint32_t id, const char* memaddr, lua_State* L, int funcs_idx) {
|
||||
void* trigger_buffer;
|
||||
rc_trigger_t* trigger;
|
||||
rc_runtime_trigger_t* runtime_trigger;
|
||||
rc_parse_state_t parse;
|
||||
uint8_t md5[16];
|
||||
int32_t size;
|
||||
uint32_t i;
|
||||
|
||||
if (memaddr == NULL)
|
||||
return RC_INVALID_MEMORY_OPERAND;
|
||||
|
||||
rc_runtime_checksum(memaddr, md5);
|
||||
|
||||
/* check to see if the id is already registered with an active trigger */
|
||||
for (i = 0; i < self->trigger_count; ++i) {
|
||||
if (self->triggers[i].id == id && self->triggers[i].trigger != NULL) {
|
||||
if (memcmp(self->triggers[i].md5, md5, 16) == 0) {
|
||||
/* if the checksum hasn't changed, we can reuse the existing item */
|
||||
rc_reset_trigger(self->triggers[i].trigger);
|
||||
return RC_OK;
|
||||
}
|
||||
|
||||
/* checksum has changed, deactivate the the item */
|
||||
rc_runtime_deactivate_trigger_by_index(self, i);
|
||||
|
||||
/* deactivate may reorder the list so we should continue from the current index. however, we
|
||||
* assume that only one trigger is active per id, so having found that, just stop scanning.
|
||||
*/
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* check to see if a disabled trigger for the specific id matches the trigger being registered */
|
||||
for (i = 0; i < self->trigger_count; ++i) {
|
||||
if (self->triggers[i].id == id && memcmp(self->triggers[i].md5, md5, 16) == 0) {
|
||||
/* retrieve the trigger pointer from the buffer */
|
||||
size = 0;
|
||||
trigger = (rc_trigger_t*)rc_alloc(self->triggers[i].buffer, &size, sizeof(rc_trigger_t), RC_ALIGNOF(rc_trigger_t), NULL, -1);
|
||||
self->triggers[i].trigger = trigger;
|
||||
|
||||
rc_reset_trigger(trigger);
|
||||
return RC_OK;
|
||||
}
|
||||
}
|
||||
|
||||
/* item has not been previously registered, determine how much space we need for it, and allocate it */
|
||||
size = rc_trigger_size(memaddr);
|
||||
if (size < 0)
|
||||
return size;
|
||||
|
||||
trigger_buffer = malloc(size);
|
||||
if (!trigger_buffer)
|
||||
return RC_OUT_OF_MEMORY;
|
||||
|
||||
/* populate the item, using the communal memrefs pool */
|
||||
rc_init_parse_state(&parse, trigger_buffer, L, funcs_idx);
|
||||
parse.first_memref = &self->memrefs;
|
||||
trigger = RC_ALLOC(rc_trigger_t, &parse);
|
||||
rc_parse_trigger_internal(trigger, &memaddr, &parse);
|
||||
rc_destroy_parse_state(&parse);
|
||||
|
||||
if (parse.offset < 0) {
|
||||
free(trigger_buffer);
|
||||
*self->next_memref = NULL; /* disassociate any memrefs allocated by the failed parse */
|
||||
return parse.offset;
|
||||
}
|
||||
|
||||
/* grow the trigger buffer if necessary */
|
||||
if (self->trigger_count == self->trigger_capacity) {
|
||||
self->trigger_capacity += 32;
|
||||
if (!self->triggers)
|
||||
self->triggers = (rc_runtime_trigger_t*)malloc(self->trigger_capacity * sizeof(rc_runtime_trigger_t));
|
||||
else
|
||||
self->triggers = (rc_runtime_trigger_t*)realloc(self->triggers, self->trigger_capacity * sizeof(rc_runtime_trigger_t));
|
||||
|
||||
if (!self->triggers) {
|
||||
free(trigger_buffer);
|
||||
*self->next_memref = NULL; /* disassociate any memrefs allocated by the failed parse */
|
||||
return RC_OUT_OF_MEMORY;
|
||||
}
|
||||
}
|
||||
|
||||
/* assign the new trigger */
|
||||
runtime_trigger = &self->triggers[self->trigger_count];
|
||||
runtime_trigger->id = id;
|
||||
runtime_trigger->trigger = trigger;
|
||||
runtime_trigger->buffer = trigger_buffer;
|
||||
runtime_trigger->invalid_memref = NULL;
|
||||
memcpy(runtime_trigger->md5, md5, 16);
|
||||
runtime_trigger->serialized_size = 0;
|
||||
runtime_trigger->owns_memrefs = rc_runtime_allocated_memrefs(self);
|
||||
++self->trigger_count;
|
||||
|
||||
/* reset it, and return it */
|
||||
trigger->memrefs = NULL;
|
||||
rc_reset_trigger(trigger);
|
||||
return RC_OK;
|
||||
}
|
||||
|
||||
rc_trigger_t* rc_runtime_get_achievement(const rc_runtime_t* self, uint32_t id)
|
||||
{
|
||||
uint32_t i;
|
||||
|
||||
for (i = 0; i < self->trigger_count; ++i) {
|
||||
if (self->triggers[i].id == id && self->triggers[i].trigger != NULL)
|
||||
return self->triggers[i].trigger;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int rc_runtime_get_achievement_measured(const rc_runtime_t* runtime, uint32_t id, unsigned* measured_value, unsigned* measured_target)
|
||||
{
|
||||
const rc_trigger_t* trigger = rc_runtime_get_achievement(runtime, id);
|
||||
if (!measured_value || !measured_target)
|
||||
return 0;
|
||||
|
||||
if (!trigger) {
|
||||
*measured_value = *measured_target = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (rc_trigger_state_active(trigger->state)) {
|
||||
*measured_value = (trigger->measured_value == RC_MEASURED_UNKNOWN) ? 0 : trigger->measured_value;
|
||||
*measured_target = trigger->measured_target;
|
||||
}
|
||||
else {
|
||||
/* don't report measured information for inactive triggers */
|
||||
*measured_value = *measured_target = 0;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
int rc_runtime_format_achievement_measured(const rc_runtime_t* runtime, uint32_t id, char* buffer, size_t buffer_size)
|
||||
{
|
||||
const rc_trigger_t* trigger = rc_runtime_get_achievement(runtime, id);
|
||||
uint32_t value;
|
||||
if (!buffer || !buffer_size)
|
||||
return 0;
|
||||
|
||||
if (!trigger || /* no trigger */
|
||||
trigger->measured_target == 0 || /* not measured */
|
||||
!rc_trigger_state_active(trigger->state)) { /* don't report measured value for inactive triggers */
|
||||
*buffer = '\0';
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* cap the value at the target so we can count past the target: "107 >= 100" */
|
||||
value = (trigger->measured_value == RC_MEASURED_UNKNOWN) ? 0 : trigger->measured_value;
|
||||
if (value > trigger->measured_target)
|
||||
value = trigger->measured_target;
|
||||
|
||||
if (trigger->measured_as_percent) {
|
||||
const uint32_t percent = (uint32_t)(((unsigned long long)value * 100) / trigger->measured_target);
|
||||
return snprintf(buffer, buffer_size, "%u%%", percent);
|
||||
}
|
||||
|
||||
return snprintf(buffer, buffer_size, "%u/%u", value, trigger->measured_target);
|
||||
}
|
||||
|
||||
static void rc_runtime_deactivate_lboard_by_index(rc_runtime_t* self, uint32_t index) {
|
||||
if (self->lboards[index].owns_memrefs) {
|
||||
/* if the lboard has one or more memrefs in its buffer, we can't free the buffer.
|
||||
* just null out the lboard so the runtime processor will skip it
|
||||
*/
|
||||
rc_reset_lboard(self->lboards[index].lboard);
|
||||
self->lboards[index].lboard = NULL;
|
||||
}
|
||||
else {
|
||||
/* lboard doesn't own any memrefs, go ahead and free it, then replace it with the last lboard */
|
||||
free(self->lboards[index].buffer);
|
||||
|
||||
if (--self->lboard_count > index)
|
||||
memcpy(&self->lboards[index], &self->lboards[self->lboard_count], sizeof(rc_runtime_lboard_t));
|
||||
}
|
||||
}
|
||||
|
||||
void rc_runtime_deactivate_lboard(rc_runtime_t* self, uint32_t id) {
|
||||
uint32_t i;
|
||||
|
||||
for (i = 0; i < self->lboard_count; ++i) {
|
||||
if (self->lboards[i].id == id && self->lboards[i].lboard != NULL)
|
||||
rc_runtime_deactivate_lboard_by_index(self, i);
|
||||
}
|
||||
}
|
||||
|
||||
int rc_runtime_activate_lboard(rc_runtime_t* self, uint32_t id, const char* memaddr, lua_State* L, int funcs_idx) {
|
||||
void* lboard_buffer;
|
||||
uint8_t md5[16];
|
||||
rc_lboard_t* lboard;
|
||||
rc_parse_state_t parse;
|
||||
rc_runtime_lboard_t* runtime_lboard;
|
||||
int size;
|
||||
uint32_t i;
|
||||
|
||||
if (memaddr == 0)
|
||||
return RC_INVALID_MEMORY_OPERAND;
|
||||
|
||||
rc_runtime_checksum(memaddr, md5);
|
||||
|
||||
/* check to see if the id is already registered with an active lboard */
|
||||
for (i = 0; i < self->lboard_count; ++i) {
|
||||
if (self->lboards[i].id == id && self->lboards[i].lboard != NULL) {
|
||||
if (memcmp(self->lboards[i].md5, md5, 16) == 0) {
|
||||
/* if the checksum hasn't changed, we can reuse the existing item */
|
||||
rc_reset_lboard(self->lboards[i].lboard);
|
||||
return RC_OK;
|
||||
}
|
||||
|
||||
/* checksum has changed, deactivate the the item */
|
||||
rc_runtime_deactivate_lboard_by_index(self, i);
|
||||
|
||||
/* deactivate may reorder the list so we should continue from the current index. however, we
|
||||
* assume that only one trigger is active per id, so having found that, just stop scanning.
|
||||
*/
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* check to see if a disabled lboard for the specific id matches the lboard being registered */
|
||||
for (i = 0; i < self->lboard_count; ++i) {
|
||||
if (self->lboards[i].id == id && memcmp(self->lboards[i].md5, md5, 16) == 0) {
|
||||
/* retrieve the lboard pointer from the buffer */
|
||||
size = 0;
|
||||
lboard = (rc_lboard_t*)rc_alloc(self->lboards[i].buffer, &size, sizeof(rc_lboard_t), RC_ALIGNOF(rc_lboard_t), NULL, -1);
|
||||
self->lboards[i].lboard = lboard;
|
||||
|
||||
rc_reset_lboard(lboard);
|
||||
return RC_OK;
|
||||
}
|
||||
}
|
||||
|
||||
/* item has not been previously registered, determine how much space we need for it, and allocate it */
|
||||
size = rc_lboard_size(memaddr);
|
||||
if (size < 0)
|
||||
return size;
|
||||
|
||||
lboard_buffer = malloc(size);
|
||||
if (!lboard_buffer)
|
||||
return RC_OUT_OF_MEMORY;
|
||||
|
||||
/* populate the item, using the communal memrefs pool */
|
||||
rc_init_parse_state(&parse, lboard_buffer, L, funcs_idx);
|
||||
lboard = RC_ALLOC(rc_lboard_t, &parse);
|
||||
parse.first_memref = &self->memrefs;
|
||||
rc_parse_lboard_internal(lboard, memaddr, &parse);
|
||||
rc_destroy_parse_state(&parse);
|
||||
|
||||
if (parse.offset < 0) {
|
||||
free(lboard_buffer);
|
||||
*self->next_memref = NULL; /* disassociate any memrefs allocated by the failed parse */
|
||||
return parse.offset;
|
||||
}
|
||||
|
||||
/* grow the lboard buffer if necessary */
|
||||
if (self->lboard_count == self->lboard_capacity) {
|
||||
self->lboard_capacity += 16;
|
||||
if (!self->lboards)
|
||||
self->lboards = (rc_runtime_lboard_t*)malloc(self->lboard_capacity * sizeof(rc_runtime_lboard_t));
|
||||
else
|
||||
self->lboards = (rc_runtime_lboard_t*)realloc(self->lboards, self->lboard_capacity * sizeof(rc_runtime_lboard_t));
|
||||
|
||||
if (!self->lboards) {
|
||||
free(lboard_buffer);
|
||||
*self->next_memref = NULL; /* disassociate any memrefs allocated by the failed parse */
|
||||
return RC_OUT_OF_MEMORY;
|
||||
}
|
||||
}
|
||||
|
||||
/* assign the new lboard */
|
||||
runtime_lboard = &self->lboards[self->lboard_count++];
|
||||
runtime_lboard->id = id;
|
||||
runtime_lboard->value = 0;
|
||||
runtime_lboard->lboard = lboard;
|
||||
runtime_lboard->buffer = lboard_buffer;
|
||||
runtime_lboard->invalid_memref = NULL;
|
||||
memcpy(runtime_lboard->md5, md5, 16);
|
||||
runtime_lboard->serialized_size = 0;
|
||||
runtime_lboard->owns_memrefs = rc_runtime_allocated_memrefs(self);
|
||||
|
||||
/* reset it, and return it */
|
||||
lboard->memrefs = NULL;
|
||||
rc_reset_lboard(lboard);
|
||||
return RC_OK;
|
||||
}
|
||||
|
||||
rc_lboard_t* rc_runtime_get_lboard(const rc_runtime_t* self, uint32_t id)
|
||||
{
|
||||
uint32_t i;
|
||||
|
||||
for (i = 0; i < self->lboard_count; ++i) {
|
||||
if (self->lboards[i].id == id && self->lboards[i].lboard != NULL)
|
||||
return self->lboards[i].lboard;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int rc_runtime_format_lboard_value(char* buffer, int size, int32_t value, int format)
|
||||
{
|
||||
return rc_format_value(buffer, size, value, format);
|
||||
}
|
||||
|
||||
int rc_runtime_activate_richpresence(rc_runtime_t* self, const char* script, lua_State* L, int funcs_idx) {
|
||||
rc_richpresence_t* richpresence;
|
||||
rc_runtime_richpresence_t* previous;
|
||||
rc_runtime_richpresence_t** previous_ptr;
|
||||
rc_parse_state_t parse;
|
||||
uint8_t md5[16];
|
||||
int size;
|
||||
|
||||
if (script == NULL)
|
||||
return RC_MISSING_DISPLAY_STRING;
|
||||
|
||||
rc_runtime_checksum(script, md5);
|
||||
|
||||
/* look for existing match */
|
||||
previous_ptr = NULL;
|
||||
previous = self->richpresence;
|
||||
while (previous) {
|
||||
if (previous && self->richpresence->richpresence && memcmp(self->richpresence->md5, md5, 16) == 0) {
|
||||
/* unchanged. reset all of the conditions */
|
||||
rc_reset_richpresence(self->richpresence->richpresence);
|
||||
|
||||
/* move to front of linked list*/
|
||||
if (previous_ptr) {
|
||||
*previous_ptr = previous->previous;
|
||||
if (!self->richpresence->owns_memrefs) {
|
||||
free(self->richpresence->buffer);
|
||||
previous->previous = self->richpresence->previous;
|
||||
}
|
||||
else {
|
||||
previous->previous = self->richpresence;
|
||||
}
|
||||
|
||||
self->richpresence = previous;
|
||||
}
|
||||
|
||||
/* return success*/
|
||||
return RC_OK;
|
||||
}
|
||||
|
||||
previous_ptr = &previous->previous;
|
||||
previous = previous->previous;
|
||||
}
|
||||
|
||||
/* no existing match found, parse script */
|
||||
size = rc_richpresence_size(script);
|
||||
if (size < 0)
|
||||
return size;
|
||||
|
||||
/* if the previous script doesn't have any memrefs, free it */
|
||||
previous = self->richpresence;
|
||||
if (previous) {
|
||||
if (!previous->owns_memrefs) {
|
||||
free(previous->buffer);
|
||||
previous = previous->previous;
|
||||
}
|
||||
}
|
||||
|
||||
/* allocate and process the new script */
|
||||
self->richpresence = (rc_runtime_richpresence_t*)malloc(sizeof(rc_runtime_richpresence_t));
|
||||
if (!self->richpresence)
|
||||
return RC_OUT_OF_MEMORY;
|
||||
|
||||
self->richpresence->previous = previous;
|
||||
self->richpresence->owns_memrefs = 0;
|
||||
memcpy(self->richpresence->md5, md5, sizeof(md5));
|
||||
self->richpresence->buffer = malloc(size);
|
||||
|
||||
if (!self->richpresence->buffer)
|
||||
return RC_OUT_OF_MEMORY;
|
||||
|
||||
rc_init_parse_state(&parse, self->richpresence->buffer, L, funcs_idx);
|
||||
self->richpresence->richpresence = richpresence = RC_ALLOC(rc_richpresence_t, &parse);
|
||||
parse.first_memref = &self->memrefs;
|
||||
parse.variables = &self->variables;
|
||||
rc_parse_richpresence_internal(richpresence, script, &parse);
|
||||
rc_destroy_parse_state(&parse);
|
||||
|
||||
if (parse.offset < 0) {
|
||||
free(self->richpresence->buffer);
|
||||
free(self->richpresence);
|
||||
self->richpresence = previous;
|
||||
*self->next_memref = NULL; /* disassociate any memrefs allocated by the failed parse */
|
||||
return parse.offset;
|
||||
}
|
||||
|
||||
self->richpresence->owns_memrefs = rc_runtime_allocated_memrefs(self);
|
||||
|
||||
richpresence->memrefs = NULL;
|
||||
richpresence->variables = NULL;
|
||||
|
||||
if (!richpresence->first_display || !richpresence->first_display->display) {
|
||||
/* non-existant rich presence */
|
||||
self->richpresence->richpresence = NULL;
|
||||
}
|
||||
else {
|
||||
/* reset all of the conditions */
|
||||
rc_reset_richpresence(richpresence);
|
||||
}
|
||||
|
||||
return RC_OK;
|
||||
}
|
||||
|
||||
int rc_runtime_get_richpresence(const rc_runtime_t* self, char* buffer, size_t buffersize, rc_runtime_peek_t peek, void* peek_ud, lua_State* L) {
|
||||
if (self->richpresence && self->richpresence->richpresence)
|
||||
return rc_get_richpresence_display_string(self->richpresence->richpresence, buffer, buffersize, peek, peek_ud, L);
|
||||
|
||||
*buffer = '\0';
|
||||
return 0;
|
||||
}
|
||||
|
||||
void rc_runtime_do_frame(rc_runtime_t* self, rc_runtime_event_handler_t event_handler, rc_runtime_peek_t peek, void* ud, lua_State* L) {
|
||||
rc_runtime_event_t runtime_event;
|
||||
int i;
|
||||
|
||||
runtime_event.value = 0;
|
||||
|
||||
rc_update_memref_values(self->memrefs, peek, ud);
|
||||
rc_update_variables(self->variables, peek, ud, L);
|
||||
|
||||
for (i = self->trigger_count - 1; i >= 0; --i) {
|
||||
rc_trigger_t* trigger = self->triggers[i].trigger;
|
||||
int old_state, new_state;
|
||||
uint32_t old_measured_value;
|
||||
|
||||
if (!trigger)
|
||||
continue;
|
||||
|
||||
if (self->triggers[i].invalid_memref) {
|
||||
runtime_event.type = RC_RUNTIME_EVENT_ACHIEVEMENT_DISABLED;
|
||||
runtime_event.id = self->triggers[i].id;
|
||||
runtime_event.value = self->triggers[i].invalid_memref->address;
|
||||
|
||||
trigger->state = RC_TRIGGER_STATE_DISABLED;
|
||||
self->triggers[i].invalid_memref = NULL;
|
||||
|
||||
event_handler(&runtime_event);
|
||||
|
||||
runtime_event.value = 0; /* achievement loop expects this to stay at 0 */
|
||||
continue;
|
||||
}
|
||||
|
||||
old_measured_value = trigger->measured_value;
|
||||
old_state = trigger->state;
|
||||
new_state = rc_evaluate_trigger(trigger, peek, ud, L);
|
||||
|
||||
/* trigger->state doesn't actually change to RESET, RESET just serves as a notification.
|
||||
* handle the notification, then look at the actual state */
|
||||
if (new_state == RC_TRIGGER_STATE_RESET) {
|
||||
runtime_event.type = RC_RUNTIME_EVENT_ACHIEVEMENT_RESET;
|
||||
runtime_event.id = self->triggers[i].id;
|
||||
event_handler(&runtime_event);
|
||||
|
||||
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 */
|
||||
const int32_t old_percent = (int32_t)(((unsigned long long)old_measured_value * 100) / trigger->measured_target);
|
||||
const int32_t new_percent = (int32_t)(((unsigned long long)trigger->measured_value * 100) / trigger->measured_target);
|
||||
if (old_percent != new_percent) {
|
||||
runtime_event.value = new_percent;
|
||||
event_handler(&runtime_event);
|
||||
}
|
||||
}
|
||||
else {
|
||||
runtime_event.value = trigger->measured_value;
|
||||
event_handler(&runtime_event);
|
||||
}
|
||||
|
||||
runtime_event.value = 0; /* achievement loop expects this to stay at 0 */
|
||||
}
|
||||
|
||||
/* if the state hasn't changed, there won't be any events raised */
|
||||
if (new_state == old_state)
|
||||
continue;
|
||||
|
||||
/* raise an UNPRIMED event when changing from PRIMED to anything else */
|
||||
if (old_state == RC_TRIGGER_STATE_PRIMED) {
|
||||
runtime_event.type = RC_RUNTIME_EVENT_ACHIEVEMENT_UNPRIMED;
|
||||
runtime_event.id = self->triggers[i].id;
|
||||
event_handler(&runtime_event);
|
||||
}
|
||||
|
||||
/* raise events for each of the possible new states */
|
||||
switch (new_state)
|
||||
{
|
||||
case RC_TRIGGER_STATE_TRIGGERED:
|
||||
runtime_event.type = RC_RUNTIME_EVENT_ACHIEVEMENT_TRIGGERED;
|
||||
runtime_event.id = self->triggers[i].id;
|
||||
event_handler(&runtime_event);
|
||||
break;
|
||||
|
||||
case RC_TRIGGER_STATE_PAUSED:
|
||||
runtime_event.type = RC_RUNTIME_EVENT_ACHIEVEMENT_PAUSED;
|
||||
runtime_event.id = self->triggers[i].id;
|
||||
event_handler(&runtime_event);
|
||||
break;
|
||||
|
||||
case RC_TRIGGER_STATE_PRIMED:
|
||||
runtime_event.type = RC_RUNTIME_EVENT_ACHIEVEMENT_PRIMED;
|
||||
runtime_event.id = self->triggers[i].id;
|
||||
event_handler(&runtime_event);
|
||||
break;
|
||||
|
||||
case RC_TRIGGER_STATE_ACTIVE:
|
||||
/* only raise ACTIVATED event when transitioning from an inactive state.
|
||||
* note that inactive in this case means active but cannot trigger. */
|
||||
if (old_state == RC_TRIGGER_STATE_WAITING || old_state == RC_TRIGGER_STATE_PAUSED) {
|
||||
runtime_event.type = RC_RUNTIME_EVENT_ACHIEVEMENT_ACTIVATED;
|
||||
runtime_event.id = self->triggers[i].id;
|
||||
event_handler(&runtime_event);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (i = self->lboard_count - 1; i >= 0; --i) {
|
||||
rc_lboard_t* lboard = self->lboards[i].lboard;
|
||||
int lboard_state;
|
||||
|
||||
if (!lboard)
|
||||
continue;
|
||||
|
||||
if (self->lboards[i].invalid_memref) {
|
||||
runtime_event.type = RC_RUNTIME_EVENT_LBOARD_DISABLED;
|
||||
runtime_event.id = self->lboards[i].id;
|
||||
runtime_event.value = self->lboards[i].invalid_memref->address;
|
||||
|
||||
lboard->state = RC_LBOARD_STATE_DISABLED;
|
||||
self->lboards[i].invalid_memref = NULL;
|
||||
|
||||
event_handler(&runtime_event);
|
||||
continue;
|
||||
}
|
||||
|
||||
lboard_state = lboard->state;
|
||||
switch (rc_evaluate_lboard(lboard, &runtime_event.value, peek, ud, L))
|
||||
{
|
||||
case RC_LBOARD_STATE_STARTED: /* leaderboard is running */
|
||||
if (lboard_state != RC_LBOARD_STATE_STARTED) {
|
||||
self->lboards[i].value = runtime_event.value;
|
||||
|
||||
runtime_event.type = RC_RUNTIME_EVENT_LBOARD_STARTED;
|
||||
runtime_event.id = self->lboards[i].id;
|
||||
event_handler(&runtime_event);
|
||||
}
|
||||
else if (runtime_event.value != self->lboards[i].value) {
|
||||
self->lboards[i].value = runtime_event.value;
|
||||
|
||||
runtime_event.type = RC_RUNTIME_EVENT_LBOARD_UPDATED;
|
||||
runtime_event.id = self->lboards[i].id;
|
||||
event_handler(&runtime_event);
|
||||
}
|
||||
break;
|
||||
|
||||
case RC_LBOARD_STATE_CANCELED:
|
||||
if (lboard_state != RC_LBOARD_STATE_CANCELED) {
|
||||
runtime_event.type = RC_RUNTIME_EVENT_LBOARD_CANCELED;
|
||||
runtime_event.id = self->lboards[i].id;
|
||||
event_handler(&runtime_event);
|
||||
}
|
||||
break;
|
||||
|
||||
case RC_LBOARD_STATE_TRIGGERED:
|
||||
if (lboard_state != RC_RUNTIME_EVENT_LBOARD_TRIGGERED) {
|
||||
runtime_event.type = RC_RUNTIME_EVENT_LBOARD_TRIGGERED;
|
||||
runtime_event.id = self->lboards[i].id;
|
||||
event_handler(&runtime_event);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (self->richpresence && self->richpresence->richpresence)
|
||||
rc_update_richpresence(self->richpresence->richpresence, peek, ud, L);
|
||||
}
|
||||
|
||||
void rc_runtime_reset(rc_runtime_t* self) {
|
||||
rc_value_t* variable;
|
||||
uint32_t i;
|
||||
|
||||
for (i = 0; i < self->trigger_count; ++i) {
|
||||
if (self->triggers[i].trigger)
|
||||
rc_reset_trigger(self->triggers[i].trigger);
|
||||
}
|
||||
|
||||
for (i = 0; i < self->lboard_count; ++i) {
|
||||
if (self->lboards[i].lboard)
|
||||
rc_reset_lboard(self->lboards[i].lboard);
|
||||
}
|
||||
|
||||
if (self->richpresence && self->richpresence->richpresence)
|
||||
rc_reset_richpresence(self->richpresence->richpresence);
|
||||
|
||||
for (variable = self->variables; variable; variable = variable->next)
|
||||
rc_reset_value(variable);
|
||||
}
|
||||
|
||||
static int rc_condset_contains_memref(const rc_condset_t* condset, const rc_memref_t* memref) {
|
||||
rc_condition_t* cond;
|
||||
if (!condset)
|
||||
return 0;
|
||||
|
||||
for (cond = condset->conditions; cond; cond = cond->next) {
|
||||
if (rc_operand_is_memref(&cond->operand1) && cond->operand1.value.memref == memref)
|
||||
return 1;
|
||||
if (rc_operand_is_memref(&cond->operand2) && cond->operand2.value.memref == memref)
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int rc_value_contains_memref(const rc_value_t* value, const rc_memref_t* memref) {
|
||||
rc_condset_t* condset;
|
||||
if (!value)
|
||||
return 0;
|
||||
|
||||
for (condset = value->conditions; condset; condset = condset->next) {
|
||||
if (rc_condset_contains_memref(condset, memref))
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int rc_trigger_contains_memref(const rc_trigger_t* trigger, const rc_memref_t* memref) {
|
||||
rc_condset_t* condset;
|
||||
if (!trigger)
|
||||
return 0;
|
||||
|
||||
if (rc_condset_contains_memref(trigger->requirement, memref))
|
||||
return 1;
|
||||
|
||||
for (condset = trigger->alternative; condset; condset = condset->next) {
|
||||
if (rc_condset_contains_memref(condset, memref))
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void rc_runtime_invalidate_memref(rc_runtime_t* self, rc_memref_t* memref) {
|
||||
uint32_t i;
|
||||
|
||||
/* disable any achievements dependent on the address */
|
||||
for (i = 0; i < self->trigger_count; ++i) {
|
||||
if (!self->triggers[i].invalid_memref && rc_trigger_contains_memref(self->triggers[i].trigger, memref))
|
||||
self->triggers[i].invalid_memref = memref;
|
||||
}
|
||||
|
||||
/* disable any leaderboards dependent on the address */
|
||||
for (i = 0; i < self->lboard_count; ++i) {
|
||||
if (!self->lboards[i].invalid_memref) {
|
||||
rc_lboard_t* lboard = self->lboards[i].lboard;
|
||||
if (lboard) {
|
||||
if (rc_trigger_contains_memref(&lboard->start, memref)) {
|
||||
lboard->start.state = RC_TRIGGER_STATE_DISABLED;
|
||||
self->lboards[i].invalid_memref = memref;
|
||||
}
|
||||
|
||||
if (rc_trigger_contains_memref(&lboard->cancel, memref)) {
|
||||
lboard->cancel.state = RC_TRIGGER_STATE_DISABLED;
|
||||
self->lboards[i].invalid_memref = memref;
|
||||
}
|
||||
|
||||
if (rc_trigger_contains_memref(&lboard->submit, memref)) {
|
||||
lboard->submit.state = RC_TRIGGER_STATE_DISABLED;
|
||||
self->lboards[i].invalid_memref = memref;
|
||||
}
|
||||
|
||||
if (rc_value_contains_memref(&lboard->value, memref))
|
||||
self->lboards[i].invalid_memref = memref;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void rc_runtime_invalidate_address(rc_runtime_t* self, uint32_t address) {
|
||||
rc_memref_t** last_memref = &self->memrefs;
|
||||
rc_memref_t* memref = self->memrefs;
|
||||
|
||||
while (memref) {
|
||||
if (memref->address == address && !memref->value.is_indirect) {
|
||||
/* remove the invalid memref from the chain so we don't try to evaluate it in the future.
|
||||
* it's still there, so anything referencing it will continue to fetch 0.
|
||||
*/
|
||||
*last_memref = memref->next;
|
||||
|
||||
rc_runtime_invalidate_memref(self, memref);
|
||||
break;
|
||||
}
|
||||
|
||||
last_memref = &memref->next;
|
||||
memref = *last_memref;
|
||||
}
|
||||
}
|
||||
|
||||
void rc_runtime_validate_addresses(rc_runtime_t* self, rc_runtime_event_handler_t event_handler,
|
||||
rc_runtime_validate_address_t validate_handler) {
|
||||
rc_memref_t** last_memref = &self->memrefs;
|
||||
rc_memref_t* memref = self->memrefs;
|
||||
int num_invalid = 0;
|
||||
|
||||
while (memref) {
|
||||
if (!memref->value.is_indirect && !validate_handler(memref->address)) {
|
||||
/* remove the invalid memref from the chain so we don't try to evaluate it in the future.
|
||||
* it's still there, so anything referencing it will continue to fetch 0.
|
||||
*/
|
||||
*last_memref = memref->next;
|
||||
|
||||
rc_runtime_invalidate_memref(self, memref);
|
||||
++num_invalid;
|
||||
}
|
||||
else {
|
||||
last_memref = &memref->next;
|
||||
}
|
||||
|
||||
memref = *last_memref;
|
||||
}
|
||||
|
||||
if (num_invalid) {
|
||||
rc_runtime_event_t runtime_event;
|
||||
int i;
|
||||
|
||||
for (i = self->trigger_count - 1; i >= 0; --i) {
|
||||
rc_trigger_t* trigger = self->triggers[i].trigger;
|
||||
if (trigger && self->triggers[i].invalid_memref) {
|
||||
runtime_event.type = RC_RUNTIME_EVENT_ACHIEVEMENT_DISABLED;
|
||||
runtime_event.id = self->triggers[i].id;
|
||||
runtime_event.value = self->triggers[i].invalid_memref->address;
|
||||
|
||||
trigger->state = RC_TRIGGER_STATE_DISABLED;
|
||||
self->triggers[i].invalid_memref = NULL;
|
||||
|
||||
event_handler(&runtime_event);
|
||||
}
|
||||
}
|
||||
|
||||
for (i = self->lboard_count - 1; i >= 0; --i) {
|
||||
rc_lboard_t* lboard = self->lboards[i].lboard;
|
||||
if (lboard && self->lboards[i].invalid_memref) {
|
||||
runtime_event.type = RC_RUNTIME_EVENT_LBOARD_DISABLED;
|
||||
runtime_event.id = self->lboards[i].id;
|
||||
runtime_event.value = self->lboards[i].invalid_memref->address;
|
||||
|
||||
lboard->state = RC_LBOARD_STATE_DISABLED;
|
||||
self->lboards[i].invalid_memref = NULL;
|
||||
|
||||
event_handler(&runtime_event);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,881 @@
|
|||
#include "rc_runtime.h"
|
||||
#include "rc_internal.h"
|
||||
|
||||
#include "rc_util.h"
|
||||
#include "../rhash/md5.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#define RC_RUNTIME_MARKER 0x0A504152 /* RAP\n */
|
||||
|
||||
#define RC_RUNTIME_CHUNK_MEMREFS 0x4645524D /* MREF */
|
||||
#define RC_RUNTIME_CHUNK_VARIABLES 0x53524156 /* VARS */
|
||||
#define RC_RUNTIME_CHUNK_ACHIEVEMENT 0x56484341 /* ACHV */
|
||||
#define RC_RUNTIME_CHUNK_LEADERBOARD 0x4452424C /* LBRD */
|
||||
#define RC_RUNTIME_CHUNK_RICHPRESENCE 0x48434952 /* RICH */
|
||||
|
||||
#define RC_RUNTIME_CHUNK_DONE 0x454E4F44 /* DONE */
|
||||
|
||||
typedef struct rc_runtime_progress_t {
|
||||
const rc_runtime_t* runtime;
|
||||
|
||||
uint32_t offset;
|
||||
uint8_t* buffer;
|
||||
|
||||
uint32_t chunk_size_offset;
|
||||
|
||||
lua_State* L;
|
||||
} rc_runtime_progress_t;
|
||||
|
||||
#define RC_TRIGGER_STATE_UNUPDATED 0x7F
|
||||
|
||||
#define RC_MEMREF_FLAG_CHANGED_THIS_FRAME 0x00010000
|
||||
|
||||
#define RC_VAR_FLAG_HAS_COND_DATA 0x01000000
|
||||
|
||||
#define RC_COND_FLAG_IS_TRUE 0x00000001
|
||||
#define RC_COND_FLAG_OPERAND1_IS_INDIRECT_MEMREF 0x00010000
|
||||
#define RC_COND_FLAG_OPERAND1_MEMREF_CHANGED_THIS_FRAME 0x00020000
|
||||
#define RC_COND_FLAG_OPERAND2_IS_INDIRECT_MEMREF 0x00100000
|
||||
#define RC_COND_FLAG_OPERAND2_MEMREF_CHANGED_THIS_FRAME 0x00200000
|
||||
|
||||
static void rc_runtime_progress_write_uint(rc_runtime_progress_t* progress, uint32_t value)
|
||||
{
|
||||
if (progress->buffer) {
|
||||
progress->buffer[progress->offset + 0] = value & 0xFF; value >>= 8;
|
||||
progress->buffer[progress->offset + 1] = value & 0xFF; value >>= 8;
|
||||
progress->buffer[progress->offset + 2] = value & 0xFF; value >>= 8;
|
||||
progress->buffer[progress->offset + 3] = value & 0xFF;
|
||||
}
|
||||
|
||||
progress->offset += 4;
|
||||
}
|
||||
|
||||
static uint32_t rc_runtime_progress_read_uint(rc_runtime_progress_t* progress)
|
||||
{
|
||||
uint32_t value = progress->buffer[progress->offset + 0] |
|
||||
(progress->buffer[progress->offset + 1] << 8) |
|
||||
(progress->buffer[progress->offset + 2] << 16) |
|
||||
(progress->buffer[progress->offset + 3] << 24);
|
||||
|
||||
progress->offset += 4;
|
||||
return value;
|
||||
}
|
||||
|
||||
static void rc_runtime_progress_write_md5(rc_runtime_progress_t* progress, uint8_t* md5)
|
||||
{
|
||||
if (progress->buffer)
|
||||
memcpy(&progress->buffer[progress->offset], md5, 16);
|
||||
|
||||
progress->offset += 16;
|
||||
}
|
||||
|
||||
static int rc_runtime_progress_match_md5(rc_runtime_progress_t* progress, uint8_t* md5)
|
||||
{
|
||||
int result = 0;
|
||||
if (progress->buffer)
|
||||
result = (memcmp(&progress->buffer[progress->offset], md5, 16) == 0);
|
||||
|
||||
progress->offset += 16;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static void rc_runtime_progress_start_chunk(rc_runtime_progress_t* progress, uint32_t chunk_id)
|
||||
{
|
||||
rc_runtime_progress_write_uint(progress, chunk_id);
|
||||
|
||||
progress->chunk_size_offset = progress->offset;
|
||||
|
||||
progress->offset += 4;
|
||||
}
|
||||
|
||||
static void rc_runtime_progress_end_chunk(rc_runtime_progress_t* progress)
|
||||
{
|
||||
uint32_t length;
|
||||
uint32_t offset;
|
||||
|
||||
progress->offset = (progress->offset + 3) & ~0x03; /* align to 4 byte boundary */
|
||||
|
||||
if (progress->buffer) {
|
||||
/* ignore chunk size field when calculating chunk size */
|
||||
length = (uint32_t)(progress->offset - progress->chunk_size_offset - 4);
|
||||
|
||||
/* temporarily update the write pointer to write the chunk size field */
|
||||
offset = progress->offset;
|
||||
progress->offset = progress->chunk_size_offset;
|
||||
rc_runtime_progress_write_uint(progress, length);
|
||||
progress->offset = offset;
|
||||
}
|
||||
}
|
||||
|
||||
static void rc_runtime_progress_init(rc_runtime_progress_t* progress, const rc_runtime_t* runtime, lua_State* L)
|
||||
{
|
||||
memset(progress, 0, sizeof(rc_runtime_progress_t));
|
||||
progress->runtime = runtime;
|
||||
progress->L = L;
|
||||
}
|
||||
|
||||
static int rc_runtime_progress_write_memrefs(rc_runtime_progress_t* progress)
|
||||
{
|
||||
rc_memref_t* memref = progress->runtime->memrefs;
|
||||
uint32_t flags = 0;
|
||||
|
||||
rc_runtime_progress_start_chunk(progress, RC_RUNTIME_CHUNK_MEMREFS);
|
||||
|
||||
if (!progress->buffer) {
|
||||
while (memref) {
|
||||
progress->offset += 16;
|
||||
memref = memref->next;
|
||||
}
|
||||
}
|
||||
else {
|
||||
while (memref) {
|
||||
flags = memref->value.size;
|
||||
if (memref->value.changed)
|
||||
flags |= RC_MEMREF_FLAG_CHANGED_THIS_FRAME;
|
||||
|
||||
rc_runtime_progress_write_uint(progress, memref->address);
|
||||
rc_runtime_progress_write_uint(progress, flags);
|
||||
rc_runtime_progress_write_uint(progress, memref->value.value);
|
||||
rc_runtime_progress_write_uint(progress, memref->value.prior);
|
||||
|
||||
memref = memref->next;
|
||||
}
|
||||
}
|
||||
|
||||
rc_runtime_progress_end_chunk(progress);
|
||||
return RC_OK;
|
||||
}
|
||||
|
||||
static int rc_runtime_progress_read_memrefs(rc_runtime_progress_t* progress)
|
||||
{
|
||||
uint32_t entries;
|
||||
uint32_t address, flags, value, prior;
|
||||
uint8_t size;
|
||||
rc_memref_t* memref;
|
||||
rc_memref_t* first_unmatched_memref = progress->runtime->memrefs;
|
||||
|
||||
/* re-read the chunk size to determine how many memrefs are present */
|
||||
progress->offset -= 4;
|
||||
entries = rc_runtime_progress_read_uint(progress) / 16;
|
||||
|
||||
while (entries != 0) {
|
||||
address = rc_runtime_progress_read_uint(progress);
|
||||
flags = rc_runtime_progress_read_uint(progress);
|
||||
value = rc_runtime_progress_read_uint(progress);
|
||||
prior = rc_runtime_progress_read_uint(progress);
|
||||
|
||||
size = flags & 0xFF;
|
||||
|
||||
memref = first_unmatched_memref;
|
||||
while (memref) {
|
||||
if (memref->address == address && memref->value.size == size) {
|
||||
memref->value.value = value;
|
||||
memref->value.changed = (flags & RC_MEMREF_FLAG_CHANGED_THIS_FRAME) ? 1 : 0;
|
||||
memref->value.prior = prior;
|
||||
|
||||
if (memref == first_unmatched_memref)
|
||||
first_unmatched_memref = memref->next;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
memref = memref->next;
|
||||
}
|
||||
|
||||
--entries;
|
||||
}
|
||||
|
||||
return RC_OK;
|
||||
}
|
||||
|
||||
static int rc_runtime_progress_is_indirect_memref(rc_operand_t* oper)
|
||||
{
|
||||
switch (oper->type)
|
||||
{
|
||||
case RC_OPERAND_CONST:
|
||||
case RC_OPERAND_FP:
|
||||
case RC_OPERAND_LUA:
|
||||
return 0;
|
||||
|
||||
default:
|
||||
return oper->value.memref->value.is_indirect;
|
||||
}
|
||||
}
|
||||
|
||||
static int rc_runtime_progress_write_condset(rc_runtime_progress_t* progress, rc_condset_t* condset)
|
||||
{
|
||||
rc_condition_t* cond;
|
||||
uint32_t flags;
|
||||
|
||||
rc_runtime_progress_write_uint(progress, condset->is_paused);
|
||||
|
||||
cond = condset->conditions;
|
||||
while (cond) {
|
||||
flags = 0;
|
||||
if (cond->is_true)
|
||||
flags |= RC_COND_FLAG_IS_TRUE;
|
||||
|
||||
if (rc_runtime_progress_is_indirect_memref(&cond->operand1)) {
|
||||
flags |= RC_COND_FLAG_OPERAND1_IS_INDIRECT_MEMREF;
|
||||
if (cond->operand1.value.memref->value.changed)
|
||||
flags |= RC_COND_FLAG_OPERAND1_MEMREF_CHANGED_THIS_FRAME;
|
||||
}
|
||||
|
||||
if (rc_runtime_progress_is_indirect_memref(&cond->operand2)) {
|
||||
flags |= RC_COND_FLAG_OPERAND2_IS_INDIRECT_MEMREF;
|
||||
if (cond->operand2.value.memref->value.changed)
|
||||
flags |= RC_COND_FLAG_OPERAND2_MEMREF_CHANGED_THIS_FRAME;
|
||||
}
|
||||
|
||||
rc_runtime_progress_write_uint(progress, cond->current_hits);
|
||||
rc_runtime_progress_write_uint(progress, flags);
|
||||
|
||||
if (flags & RC_COND_FLAG_OPERAND1_IS_INDIRECT_MEMREF) {
|
||||
rc_runtime_progress_write_uint(progress, cond->operand1.value.memref->value.value);
|
||||
rc_runtime_progress_write_uint(progress, cond->operand1.value.memref->value.prior);
|
||||
}
|
||||
|
||||
if (flags & RC_COND_FLAG_OPERAND2_IS_INDIRECT_MEMREF) {
|
||||
rc_runtime_progress_write_uint(progress, cond->operand2.value.memref->value.value);
|
||||
rc_runtime_progress_write_uint(progress, cond->operand2.value.memref->value.prior);
|
||||
}
|
||||
|
||||
cond = cond->next;
|
||||
}
|
||||
|
||||
return RC_OK;
|
||||
}
|
||||
|
||||
static int rc_runtime_progress_read_condset(rc_runtime_progress_t* progress, rc_condset_t* condset)
|
||||
{
|
||||
rc_condition_t* cond;
|
||||
uint32_t flags;
|
||||
|
||||
condset->is_paused = (char)rc_runtime_progress_read_uint(progress);
|
||||
|
||||
cond = condset->conditions;
|
||||
while (cond) {
|
||||
cond->current_hits = rc_runtime_progress_read_uint(progress);
|
||||
flags = rc_runtime_progress_read_uint(progress);
|
||||
|
||||
cond->is_true = (flags & RC_COND_FLAG_IS_TRUE) ? 1 : 0;
|
||||
|
||||
if (flags & RC_COND_FLAG_OPERAND1_IS_INDIRECT_MEMREF) {
|
||||
if (!rc_operand_is_memref(&cond->operand1)) /* this should never happen, but better safe than sorry */
|
||||
return RC_INVALID_STATE;
|
||||
|
||||
cond->operand1.value.memref->value.value = rc_runtime_progress_read_uint(progress);
|
||||
cond->operand1.value.memref->value.prior = rc_runtime_progress_read_uint(progress);
|
||||
cond->operand1.value.memref->value.changed = (flags & RC_COND_FLAG_OPERAND1_MEMREF_CHANGED_THIS_FRAME) ? 1 : 0;
|
||||
}
|
||||
|
||||
if (flags & RC_COND_FLAG_OPERAND2_IS_INDIRECT_MEMREF) {
|
||||
if (!rc_operand_is_memref(&cond->operand2)) /* this should never happen, but better safe than sorry */
|
||||
return RC_INVALID_STATE;
|
||||
|
||||
cond->operand2.value.memref->value.value = rc_runtime_progress_read_uint(progress);
|
||||
cond->operand2.value.memref->value.prior = rc_runtime_progress_read_uint(progress);
|
||||
cond->operand2.value.memref->value.changed = (flags & RC_COND_FLAG_OPERAND2_MEMREF_CHANGED_THIS_FRAME) ? 1 : 0;
|
||||
}
|
||||
|
||||
cond = cond->next;
|
||||
}
|
||||
|
||||
return RC_OK;
|
||||
}
|
||||
|
||||
static uint32_t rc_runtime_progress_should_serialize_variable_condset(const rc_condset_t* conditions)
|
||||
{
|
||||
const rc_condition_t* condition;
|
||||
|
||||
/* predetermined presence of pause flag or indirect memrefs - must serialize */
|
||||
if (conditions->has_pause || conditions->has_indirect_memrefs)
|
||||
return RC_VAR_FLAG_HAS_COND_DATA;
|
||||
|
||||
/* if any conditions has required hits, must serialize */
|
||||
/* ASSERT: Measured with comparison and no explicit target will set hit target to 0xFFFFFFFF */
|
||||
for (condition = conditions->conditions; condition; condition = condition->next) {
|
||||
if (condition->required_hits > 0)
|
||||
return RC_VAR_FLAG_HAS_COND_DATA;
|
||||
}
|
||||
|
||||
/* can safely be reset without affecting behavior */
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rc_runtime_progress_write_variable(rc_runtime_progress_t* progress, const rc_value_t* variable)
|
||||
{
|
||||
uint32_t flags;
|
||||
|
||||
flags = rc_runtime_progress_should_serialize_variable_condset(variable->conditions);
|
||||
if (variable->value.changed)
|
||||
flags |= RC_MEMREF_FLAG_CHANGED_THIS_FRAME;
|
||||
|
||||
rc_runtime_progress_write_uint(progress, flags);
|
||||
rc_runtime_progress_write_uint(progress, variable->value.value);
|
||||
rc_runtime_progress_write_uint(progress, variable->value.prior);
|
||||
|
||||
if (flags & RC_VAR_FLAG_HAS_COND_DATA) {
|
||||
int result = rc_runtime_progress_write_condset(progress, variable->conditions);
|
||||
if (result != RC_OK)
|
||||
return result;
|
||||
}
|
||||
|
||||
return RC_OK;
|
||||
}
|
||||
|
||||
static int rc_runtime_progress_write_variables(rc_runtime_progress_t* progress)
|
||||
{
|
||||
uint32_t count = 0;
|
||||
const rc_value_t* variable;
|
||||
|
||||
for (variable = progress->runtime->variables; variable; variable = variable->next)
|
||||
++count;
|
||||
if (count == 0)
|
||||
return RC_OK;
|
||||
|
||||
rc_runtime_progress_start_chunk(progress, RC_RUNTIME_CHUNK_VARIABLES);
|
||||
rc_runtime_progress_write_uint(progress, count);
|
||||
|
||||
for (variable = progress->runtime->variables; variable; variable = variable->next)
|
||||
{
|
||||
uint32_t djb2 = rc_djb2(variable->name);
|
||||
rc_runtime_progress_write_uint(progress, djb2);
|
||||
|
||||
rc_runtime_progress_write_variable(progress, variable);
|
||||
}
|
||||
|
||||
rc_runtime_progress_end_chunk(progress);
|
||||
return RC_OK;
|
||||
}
|
||||
|
||||
static int rc_runtime_progress_read_variable(rc_runtime_progress_t* progress, rc_value_t* variable)
|
||||
{
|
||||
uint32_t flags = rc_runtime_progress_read_uint(progress);
|
||||
variable->value.changed = (flags & RC_MEMREF_FLAG_CHANGED_THIS_FRAME) ? 1 : 0;
|
||||
variable->value.value = rc_runtime_progress_read_uint(progress);
|
||||
variable->value.prior = rc_runtime_progress_read_uint(progress);
|
||||
|
||||
if (flags & RC_VAR_FLAG_HAS_COND_DATA) {
|
||||
int result = rc_runtime_progress_read_condset(progress, variable->conditions);
|
||||
if (result != RC_OK)
|
||||
return result;
|
||||
}
|
||||
else {
|
||||
rc_reset_condset(variable->conditions);
|
||||
}
|
||||
|
||||
return RC_OK;
|
||||
}
|
||||
|
||||
static int rc_runtime_progress_read_variables(rc_runtime_progress_t* progress)
|
||||
{
|
||||
struct rc_pending_value_t
|
||||
{
|
||||
rc_value_t* variable;
|
||||
uint32_t djb2;
|
||||
};
|
||||
struct rc_pending_value_t local_pending_variables[32];
|
||||
struct rc_pending_value_t* pending_variables;
|
||||
rc_value_t* variable;
|
||||
uint32_t count, serialized_count;
|
||||
int result;
|
||||
uint32_t i;
|
||||
|
||||
serialized_count = rc_runtime_progress_read_uint(progress);
|
||||
if (serialized_count == 0)
|
||||
return RC_OK;
|
||||
|
||||
count = 0;
|
||||
for (variable = progress->runtime->variables; variable; variable = variable->next)
|
||||
++count;
|
||||
|
||||
if (count == 0)
|
||||
return RC_OK;
|
||||
|
||||
if (count <= sizeof(local_pending_variables) / sizeof(local_pending_variables[0])) {
|
||||
pending_variables = local_pending_variables;
|
||||
}
|
||||
else {
|
||||
pending_variables = (struct rc_pending_value_t*)malloc(count * sizeof(struct rc_pending_value_t));
|
||||
if (pending_variables == NULL)
|
||||
return RC_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
count = 0;
|
||||
for (variable = progress->runtime->variables; variable; variable = variable->next) {
|
||||
pending_variables[count].variable = variable;
|
||||
pending_variables[count].djb2 = rc_djb2(variable->name);
|
||||
++count;
|
||||
}
|
||||
|
||||
result = RC_OK;
|
||||
for (; serialized_count > 0 && result == RC_OK; --serialized_count) {
|
||||
uint32_t djb2 = rc_runtime_progress_read_uint(progress);
|
||||
for (i = 0; i < count; ++i) {
|
||||
if (pending_variables[i].djb2 == djb2) {
|
||||
variable = pending_variables[i].variable;
|
||||
result = rc_runtime_progress_read_variable(progress, variable);
|
||||
if (result == RC_OK) {
|
||||
if (i < count - 1)
|
||||
memcpy(&pending_variables[i], &pending_variables[count - 1], sizeof(struct rc_pending_value_t));
|
||||
count--;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
while (count > 0)
|
||||
rc_reset_value(pending_variables[--count].variable);
|
||||
|
||||
if (pending_variables != local_pending_variables)
|
||||
free(pending_variables);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static int rc_runtime_progress_write_trigger(rc_runtime_progress_t* progress, const rc_trigger_t* trigger)
|
||||
{
|
||||
rc_condset_t* condset;
|
||||
int result;
|
||||
|
||||
rc_runtime_progress_write_uint(progress, trigger->state);
|
||||
rc_runtime_progress_write_uint(progress, trigger->measured_value);
|
||||
|
||||
if (trigger->requirement) {
|
||||
result = rc_runtime_progress_write_condset(progress, trigger->requirement);
|
||||
if (result != RC_OK)
|
||||
return result;
|
||||
}
|
||||
|
||||
condset = trigger->alternative;
|
||||
while (condset) {
|
||||
result = rc_runtime_progress_write_condset(progress, condset);
|
||||
if (result != RC_OK)
|
||||
return result;
|
||||
|
||||
condset = condset->next;
|
||||
}
|
||||
|
||||
return RC_OK;
|
||||
}
|
||||
|
||||
static int rc_runtime_progress_read_trigger(rc_runtime_progress_t* progress, rc_trigger_t* trigger)
|
||||
{
|
||||
rc_condset_t* condset;
|
||||
int result;
|
||||
|
||||
trigger->state = (char)rc_runtime_progress_read_uint(progress);
|
||||
trigger->measured_value = rc_runtime_progress_read_uint(progress);
|
||||
|
||||
if (trigger->requirement) {
|
||||
result = rc_runtime_progress_read_condset(progress, trigger->requirement);
|
||||
if (result != RC_OK)
|
||||
return result;
|
||||
}
|
||||
|
||||
condset = trigger->alternative;
|
||||
while (condset) {
|
||||
result = rc_runtime_progress_read_condset(progress, condset);
|
||||
if (result != RC_OK)
|
||||
return result;
|
||||
|
||||
condset = condset->next;
|
||||
}
|
||||
|
||||
return RC_OK;
|
||||
}
|
||||
|
||||
static int rc_runtime_progress_write_achievements(rc_runtime_progress_t* progress)
|
||||
{
|
||||
uint32_t i;
|
||||
int offset = 0;
|
||||
int result;
|
||||
|
||||
for (i = 0; i < progress->runtime->trigger_count; ++i) {
|
||||
rc_runtime_trigger_t* runtime_trigger = &progress->runtime->triggers[i];
|
||||
if (!runtime_trigger->trigger)
|
||||
continue;
|
||||
|
||||
/* don't store state for inactive or triggered achievements */
|
||||
if (!rc_trigger_state_active(runtime_trigger->trigger->state))
|
||||
continue;
|
||||
|
||||
if (!progress->buffer) {
|
||||
if (runtime_trigger->serialized_size) {
|
||||
progress->offset += runtime_trigger->serialized_size;
|
||||
continue;
|
||||
}
|
||||
|
||||
offset = progress->offset;
|
||||
}
|
||||
|
||||
rc_runtime_progress_start_chunk(progress, RC_RUNTIME_CHUNK_ACHIEVEMENT);
|
||||
rc_runtime_progress_write_uint(progress, runtime_trigger->id);
|
||||
rc_runtime_progress_write_md5(progress, runtime_trigger->md5);
|
||||
|
||||
result = rc_runtime_progress_write_trigger(progress, runtime_trigger->trigger);
|
||||
if (result != RC_OK)
|
||||
return result;
|
||||
|
||||
rc_runtime_progress_end_chunk(progress);
|
||||
|
||||
if (!progress->buffer)
|
||||
runtime_trigger->serialized_size = progress->offset - offset;
|
||||
}
|
||||
|
||||
return RC_OK;
|
||||
}
|
||||
|
||||
static int rc_runtime_progress_read_achievement(rc_runtime_progress_t* progress)
|
||||
{
|
||||
uint32_t id = rc_runtime_progress_read_uint(progress);
|
||||
uint32_t i;
|
||||
|
||||
for (i = 0; i < progress->runtime->trigger_count; ++i) {
|
||||
rc_runtime_trigger_t* runtime_trigger = &progress->runtime->triggers[i];
|
||||
if (runtime_trigger->id == id && runtime_trigger->trigger != NULL) {
|
||||
/* ignore triggered and waiting achievements */
|
||||
if (runtime_trigger->trigger->state == RC_TRIGGER_STATE_UNUPDATED) {
|
||||
/* only update state if definition hasn't changed (md5 matches) */
|
||||
if (rc_runtime_progress_match_md5(progress, runtime_trigger->md5))
|
||||
return rc_runtime_progress_read_trigger(progress, runtime_trigger->trigger);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return RC_OK;
|
||||
}
|
||||
|
||||
static int rc_runtime_progress_write_leaderboards(rc_runtime_progress_t* progress)
|
||||
{
|
||||
uint32_t i;
|
||||
uint32_t flags;
|
||||
int offset = 0;
|
||||
int result;
|
||||
|
||||
for (i = 0; i < progress->runtime->lboard_count; ++i) {
|
||||
rc_runtime_lboard_t* runtime_lboard = &progress->runtime->lboards[i];
|
||||
if (!runtime_lboard->lboard)
|
||||
continue;
|
||||
|
||||
/* don't store state for inactive leaderboards */
|
||||
if (!rc_lboard_state_active(runtime_lboard->lboard->state))
|
||||
continue;
|
||||
|
||||
if (!progress->buffer) {
|
||||
if (runtime_lboard->serialized_size) {
|
||||
progress->offset += runtime_lboard->serialized_size;
|
||||
continue;
|
||||
}
|
||||
|
||||
offset = progress->offset;
|
||||
}
|
||||
|
||||
rc_runtime_progress_start_chunk(progress, RC_RUNTIME_CHUNK_LEADERBOARD);
|
||||
rc_runtime_progress_write_uint(progress, runtime_lboard->id);
|
||||
rc_runtime_progress_write_md5(progress, runtime_lboard->md5);
|
||||
|
||||
flags = runtime_lboard->lboard->state;
|
||||
rc_runtime_progress_write_uint(progress, flags);
|
||||
|
||||
result = rc_runtime_progress_write_trigger(progress, &runtime_lboard->lboard->start);
|
||||
if (result != RC_OK)
|
||||
return result;
|
||||
|
||||
result = rc_runtime_progress_write_trigger(progress, &runtime_lboard->lboard->submit);
|
||||
if (result != RC_OK)
|
||||
return result;
|
||||
|
||||
result = rc_runtime_progress_write_trigger(progress, &runtime_lboard->lboard->cancel);
|
||||
if (result != RC_OK)
|
||||
return result;
|
||||
|
||||
result = rc_runtime_progress_write_variable(progress, &runtime_lboard->lboard->value);
|
||||
if (result != RC_OK)
|
||||
return result;
|
||||
|
||||
rc_runtime_progress_end_chunk(progress);
|
||||
|
||||
if (!progress->buffer)
|
||||
runtime_lboard->serialized_size = progress->offset - offset;
|
||||
}
|
||||
|
||||
return RC_OK;
|
||||
}
|
||||
|
||||
static int rc_runtime_progress_read_leaderboard(rc_runtime_progress_t* progress)
|
||||
{
|
||||
uint32_t id = rc_runtime_progress_read_uint(progress);
|
||||
uint32_t i;
|
||||
int result;
|
||||
|
||||
for (i = 0; i < progress->runtime->lboard_count; ++i) {
|
||||
rc_runtime_lboard_t* runtime_lboard = &progress->runtime->lboards[i];
|
||||
if (runtime_lboard->id == id && runtime_lboard->lboard != NULL) {
|
||||
/* ignore triggered and waiting achievements */
|
||||
if (runtime_lboard->lboard->state == RC_TRIGGER_STATE_UNUPDATED) {
|
||||
/* only update state if definition hasn't changed (md5 matches) */
|
||||
if (rc_runtime_progress_match_md5(progress, runtime_lboard->md5)) {
|
||||
uint32_t flags = rc_runtime_progress_read_uint(progress);
|
||||
|
||||
result = rc_runtime_progress_read_trigger(progress, &runtime_lboard->lboard->start);
|
||||
if (result != RC_OK)
|
||||
return result;
|
||||
|
||||
result = rc_runtime_progress_read_trigger(progress, &runtime_lboard->lboard->submit);
|
||||
if (result != RC_OK)
|
||||
return result;
|
||||
|
||||
result = rc_runtime_progress_read_trigger(progress, &runtime_lboard->lboard->cancel);
|
||||
if (result != RC_OK)
|
||||
return result;
|
||||
|
||||
result = rc_runtime_progress_read_variable(progress, &runtime_lboard->lboard->value);
|
||||
if (result != RC_OK)
|
||||
return result;
|
||||
|
||||
runtime_lboard->lboard->state = (char)(flags & 0x7F);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return RC_OK;
|
||||
}
|
||||
|
||||
static int rc_runtime_progress_write_rich_presence(rc_runtime_progress_t* progress)
|
||||
{
|
||||
const rc_richpresence_display_t* display;
|
||||
int result;
|
||||
|
||||
if (!progress->runtime->richpresence || !progress->runtime->richpresence->richpresence)
|
||||
return RC_OK;
|
||||
|
||||
/* if there are no conditional display strings, there's nothing to capture */
|
||||
display = progress->runtime->richpresence->richpresence->first_display;
|
||||
if (!display->next)
|
||||
return RC_OK;
|
||||
|
||||
rc_runtime_progress_start_chunk(progress, RC_RUNTIME_CHUNK_RICHPRESENCE);
|
||||
rc_runtime_progress_write_md5(progress, progress->runtime->richpresence->md5);
|
||||
|
||||
for (; display->next; display = display->next) {
|
||||
result = rc_runtime_progress_write_trigger(progress, &display->trigger);
|
||||
if (result != RC_OK)
|
||||
return result;
|
||||
}
|
||||
|
||||
rc_runtime_progress_end_chunk(progress);
|
||||
return RC_OK;
|
||||
}
|
||||
|
||||
static int rc_runtime_progress_read_rich_presence(rc_runtime_progress_t* progress)
|
||||
{
|
||||
rc_richpresence_display_t* display;
|
||||
int result;
|
||||
|
||||
if (!progress->runtime->richpresence || !progress->runtime->richpresence->richpresence)
|
||||
return RC_OK;
|
||||
|
||||
if (!rc_runtime_progress_match_md5(progress, progress->runtime->richpresence->md5)) {
|
||||
rc_reset_richpresence(progress->runtime->richpresence->richpresence);
|
||||
return RC_OK;
|
||||
}
|
||||
|
||||
display = progress->runtime->richpresence->richpresence->first_display;
|
||||
for (; display->next; display = display->next) {
|
||||
result = rc_runtime_progress_read_trigger(progress, &display->trigger);
|
||||
if (result != RC_OK)
|
||||
return result;
|
||||
}
|
||||
|
||||
return RC_OK;
|
||||
}
|
||||
|
||||
static int rc_runtime_progress_serialize_internal(rc_runtime_progress_t* progress)
|
||||
{
|
||||
md5_state_t state;
|
||||
uint8_t md5[16];
|
||||
int result;
|
||||
|
||||
rc_runtime_progress_write_uint(progress, RC_RUNTIME_MARKER);
|
||||
|
||||
if ((result = rc_runtime_progress_write_memrefs(progress)) != RC_OK)
|
||||
return result;
|
||||
|
||||
if ((result = rc_runtime_progress_write_variables(progress)) != RC_OK)
|
||||
return result;
|
||||
|
||||
if ((result = rc_runtime_progress_write_achievements(progress)) != RC_OK)
|
||||
return result;
|
||||
|
||||
if ((result = rc_runtime_progress_write_leaderboards(progress)) != RC_OK)
|
||||
return result;
|
||||
|
||||
if ((result = rc_runtime_progress_write_rich_presence(progress)) != RC_OK)
|
||||
return result;
|
||||
|
||||
rc_runtime_progress_write_uint(progress, RC_RUNTIME_CHUNK_DONE);
|
||||
rc_runtime_progress_write_uint(progress, 16);
|
||||
|
||||
if (progress->buffer) {
|
||||
md5_init(&state);
|
||||
md5_append(&state, progress->buffer, progress->offset);
|
||||
md5_finish(&state, md5);
|
||||
}
|
||||
|
||||
rc_runtime_progress_write_md5(progress, md5);
|
||||
|
||||
return RC_OK;
|
||||
}
|
||||
|
||||
int rc_runtime_progress_size(const rc_runtime_t* runtime, lua_State* L)
|
||||
{
|
||||
rc_runtime_progress_t progress;
|
||||
int result;
|
||||
|
||||
rc_runtime_progress_init(&progress, runtime, L);
|
||||
|
||||
result = rc_runtime_progress_serialize_internal(&progress);
|
||||
if (result != RC_OK)
|
||||
return result;
|
||||
|
||||
return progress.offset;
|
||||
}
|
||||
|
||||
int rc_runtime_serialize_progress(void* buffer, const rc_runtime_t* runtime, lua_State* L)
|
||||
{
|
||||
rc_runtime_progress_t progress;
|
||||
|
||||
if (!buffer)
|
||||
return RC_INVALID_STATE;
|
||||
|
||||
rc_runtime_progress_init(&progress, runtime, L);
|
||||
progress.buffer = (uint8_t*)buffer;
|
||||
|
||||
return rc_runtime_progress_serialize_internal(&progress);
|
||||
}
|
||||
|
||||
int rc_runtime_deserialize_progress(rc_runtime_t* runtime, const uint8_t* serialized, lua_State* L)
|
||||
{
|
||||
rc_runtime_progress_t progress;
|
||||
md5_state_t state;
|
||||
uint8_t md5[16];
|
||||
uint32_t chunk_id;
|
||||
uint32_t chunk_size;
|
||||
uint32_t next_chunk_offset;
|
||||
uint32_t i;
|
||||
int seen_rich_presence = 0;
|
||||
int result = RC_OK;
|
||||
|
||||
if (!serialized) {
|
||||
rc_runtime_reset(runtime);
|
||||
return RC_INVALID_STATE;
|
||||
}
|
||||
|
||||
rc_runtime_progress_init(&progress, runtime, L);
|
||||
progress.buffer = (uint8_t*)serialized;
|
||||
|
||||
if (rc_runtime_progress_read_uint(&progress) != RC_RUNTIME_MARKER) {
|
||||
rc_runtime_reset(runtime);
|
||||
return RC_INVALID_STATE;
|
||||
}
|
||||
|
||||
for (i = 0; i < runtime->trigger_count; ++i) {
|
||||
rc_runtime_trigger_t* runtime_trigger = &runtime->triggers[i];
|
||||
if (runtime_trigger->trigger) {
|
||||
/* don't update state for inactive or triggered achievements */
|
||||
if (rc_trigger_state_active(runtime_trigger->trigger->state)) {
|
||||
/* mark active achievements as unupdated. anything that's still unupdated
|
||||
* after deserializing the progress will be reset to waiting */
|
||||
runtime_trigger->trigger->state = RC_TRIGGER_STATE_UNUPDATED;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (i = 0; i < runtime->lboard_count; ++i) {
|
||||
rc_runtime_lboard_t* runtime_lboard = &runtime->lboards[i];
|
||||
if (runtime_lboard->lboard) {
|
||||
/* don't update state for inactive or triggered achievements */
|
||||
if (rc_lboard_state_active(runtime_lboard->lboard->state)) {
|
||||
/* mark active achievements as unupdated. anything that's still unupdated
|
||||
* after deserializing the progress will be reset to waiting */
|
||||
runtime_lboard->lboard->state = RC_TRIGGER_STATE_UNUPDATED;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
do {
|
||||
chunk_id = rc_runtime_progress_read_uint(&progress);
|
||||
chunk_size = rc_runtime_progress_read_uint(&progress);
|
||||
next_chunk_offset = progress.offset + chunk_size;
|
||||
|
||||
switch (chunk_id)
|
||||
{
|
||||
case RC_RUNTIME_CHUNK_MEMREFS:
|
||||
result = rc_runtime_progress_read_memrefs(&progress);
|
||||
break;
|
||||
|
||||
case RC_RUNTIME_CHUNK_VARIABLES:
|
||||
result = rc_runtime_progress_read_variables(&progress);
|
||||
break;
|
||||
|
||||
case RC_RUNTIME_CHUNK_ACHIEVEMENT:
|
||||
result = rc_runtime_progress_read_achievement(&progress);
|
||||
break;
|
||||
|
||||
case RC_RUNTIME_CHUNK_LEADERBOARD:
|
||||
result = rc_runtime_progress_read_leaderboard(&progress);
|
||||
break;
|
||||
|
||||
case RC_RUNTIME_CHUNK_RICHPRESENCE:
|
||||
seen_rich_presence = 1;
|
||||
result = rc_runtime_progress_read_rich_presence(&progress);
|
||||
break;
|
||||
|
||||
case RC_RUNTIME_CHUNK_DONE:
|
||||
md5_init(&state);
|
||||
md5_append(&state, progress.buffer, progress.offset);
|
||||
md5_finish(&state, md5);
|
||||
if (!rc_runtime_progress_match_md5(&progress, md5))
|
||||
result = RC_INVALID_STATE;
|
||||
break;
|
||||
|
||||
default:
|
||||
if (chunk_size & 0xFFFF0000)
|
||||
result = RC_INVALID_STATE; /* assume unknown chunk > 64KB is invalid */
|
||||
break;
|
||||
}
|
||||
|
||||
progress.offset = next_chunk_offset;
|
||||
} while (result == RC_OK && chunk_id != RC_RUNTIME_CHUNK_DONE);
|
||||
|
||||
if (result != RC_OK) {
|
||||
rc_runtime_reset(runtime);
|
||||
}
|
||||
else {
|
||||
for (i = 0; i < runtime->trigger_count; ++i) {
|
||||
rc_trigger_t* trigger = runtime->triggers[i].trigger;
|
||||
if (trigger && trigger->state == RC_TRIGGER_STATE_UNUPDATED)
|
||||
rc_reset_trigger(trigger);
|
||||
}
|
||||
|
||||
for (i = 0; i < runtime->lboard_count; ++i) {
|
||||
rc_lboard_t* lboard = runtime->lboards[i].lboard;
|
||||
if (lboard && lboard->state == RC_TRIGGER_STATE_UNUPDATED)
|
||||
rc_reset_lboard(lboard);
|
||||
}
|
||||
|
||||
if (!seen_rich_presence && runtime->richpresence && runtime->richpresence->richpresence)
|
||||
rc_reset_richpresence(runtime->richpresence->richpresence);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
|
@ -0,0 +1,294 @@
|
|||
#include "rc_internal.h"
|
||||
|
||||
#include <stddef.h>
|
||||
#include <string.h> /* memset */
|
||||
|
||||
void rc_parse_trigger_internal(rc_trigger_t* self, const char** memaddr, rc_parse_state_t* parse) {
|
||||
rc_condset_t** next;
|
||||
const char* aux;
|
||||
|
||||
aux = *memaddr;
|
||||
next = &self->alternative;
|
||||
|
||||
/* reset in case multiple triggers are parsed by the same parse_state */
|
||||
parse->measured_target = 0;
|
||||
parse->has_required_hits = 0;
|
||||
parse->measured_as_percent = 0;
|
||||
|
||||
if (*aux == 's' || *aux == 'S') {
|
||||
self->requirement = 0;
|
||||
}
|
||||
else {
|
||||
self->requirement = rc_parse_condset(&aux, parse, 0);
|
||||
|
||||
if (parse->offset < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
self->requirement->next = 0;
|
||||
}
|
||||
|
||||
while (*aux == 's' || *aux == 'S') {
|
||||
aux++;
|
||||
*next = rc_parse_condset(&aux, parse, 0);
|
||||
|
||||
if (parse->offset < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
next = &(*next)->next;
|
||||
}
|
||||
|
||||
*next = 0;
|
||||
*memaddr = aux;
|
||||
|
||||
self->measured_target = parse->measured_target;
|
||||
self->measured_value = parse->measured_target ? RC_MEASURED_UNKNOWN : 0;
|
||||
self->measured_as_percent = parse->measured_as_percent;
|
||||
self->state = RC_TRIGGER_STATE_WAITING;
|
||||
self->has_hits = 0;
|
||||
self->has_required_hits = parse->has_required_hits;
|
||||
}
|
||||
|
||||
int rc_trigger_size(const char* memaddr) {
|
||||
rc_trigger_t* self;
|
||||
rc_parse_state_t parse;
|
||||
rc_memref_t* memrefs;
|
||||
rc_init_parse_state(&parse, 0, 0, 0);
|
||||
rc_init_parse_state_memrefs(&parse, &memrefs);
|
||||
|
||||
self = RC_ALLOC(rc_trigger_t, &parse);
|
||||
rc_parse_trigger_internal(self, &memaddr, &parse);
|
||||
|
||||
rc_destroy_parse_state(&parse);
|
||||
return parse.offset;
|
||||
}
|
||||
|
||||
rc_trigger_t* rc_parse_trigger(void* buffer, const char* memaddr, lua_State* L, int funcs_ndx) {
|
||||
rc_trigger_t* self;
|
||||
rc_parse_state_t parse;
|
||||
|
||||
if (!buffer || !memaddr)
|
||||
return NULL;
|
||||
|
||||
rc_init_parse_state(&parse, buffer, L, funcs_ndx);
|
||||
|
||||
self = RC_ALLOC(rc_trigger_t, &parse);
|
||||
rc_init_parse_state_memrefs(&parse, &self->memrefs);
|
||||
|
||||
rc_parse_trigger_internal(self, &memaddr, &parse);
|
||||
|
||||
rc_destroy_parse_state(&parse);
|
||||
return (parse.offset >= 0) ? self : NULL;
|
||||
}
|
||||
|
||||
int rc_trigger_state_active(int state)
|
||||
{
|
||||
switch (state)
|
||||
{
|
||||
case RC_TRIGGER_STATE_DISABLED:
|
||||
case RC_TRIGGER_STATE_INACTIVE:
|
||||
case RC_TRIGGER_STATE_TRIGGERED:
|
||||
return 0;
|
||||
|
||||
default:
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
static int rc_condset_is_measured_from_hitcount(const rc_condset_t* condset, uint32_t measured_value)
|
||||
{
|
||||
const rc_condition_t* condition;
|
||||
for (condition = condset->conditions; condition; condition = condition->next) {
|
||||
if (condition->type == RC_CONDITION_MEASURED && condition->required_hits &&
|
||||
condition->current_hits == measured_value) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void rc_reset_trigger_hitcounts(rc_trigger_t* self) {
|
||||
rc_condset_t* condset;
|
||||
|
||||
if (self->requirement) {
|
||||
rc_reset_condset(self->requirement);
|
||||
}
|
||||
|
||||
condset = self->alternative;
|
||||
|
||||
while (condset) {
|
||||
rc_reset_condset(condset);
|
||||
condset = condset->next;
|
||||
}
|
||||
}
|
||||
|
||||
int rc_evaluate_trigger(rc_trigger_t* self, rc_peek_t peek, void* ud, lua_State* L) {
|
||||
rc_eval_state_t eval_state;
|
||||
rc_condset_t* condset;
|
||||
int ret;
|
||||
char is_paused;
|
||||
char is_primed;
|
||||
|
||||
switch (self->state)
|
||||
{
|
||||
case RC_TRIGGER_STATE_TRIGGERED:
|
||||
/* previously triggered. do nothing - return INACTIVE so caller doesn't think it triggered again */
|
||||
return RC_TRIGGER_STATE_INACTIVE;
|
||||
|
||||
case RC_TRIGGER_STATE_DISABLED:
|
||||
/* unsupported. do nothing - return INACTIVE */
|
||||
return RC_TRIGGER_STATE_INACTIVE;
|
||||
|
||||
case RC_TRIGGER_STATE_INACTIVE:
|
||||
/* not yet active. update the memrefs so deltas are correct when it becomes active, then return INACTIVE */
|
||||
rc_update_memref_values(self->memrefs, peek, ud);
|
||||
return RC_TRIGGER_STATE_INACTIVE;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
/* update the memory references */
|
||||
rc_update_memref_values(self->memrefs, peek, ud);
|
||||
|
||||
/* process the trigger */
|
||||
memset(&eval_state, 0, sizeof(eval_state));
|
||||
eval_state.peek = peek;
|
||||
eval_state.peek_userdata = ud;
|
||||
eval_state.L = L;
|
||||
|
||||
if (self->requirement != NULL) {
|
||||
ret = rc_test_condset(self->requirement, &eval_state);
|
||||
is_paused = self->requirement->is_paused;
|
||||
is_primed = eval_state.primed;
|
||||
} else {
|
||||
ret = 1;
|
||||
is_paused = 0;
|
||||
is_primed = 1;
|
||||
}
|
||||
|
||||
condset = self->alternative;
|
||||
if (condset) {
|
||||
int sub = 0;
|
||||
char sub_paused = 1;
|
||||
char sub_primed = 0;
|
||||
|
||||
do {
|
||||
sub |= rc_test_condset(condset, &eval_state);
|
||||
sub_paused &= condset->is_paused;
|
||||
sub_primed |= eval_state.primed;
|
||||
|
||||
condset = condset->next;
|
||||
} while (condset);
|
||||
|
||||
/* to trigger, the core must be true and at least one alt must be true */
|
||||
ret &= sub;
|
||||
is_primed &= sub_primed;
|
||||
|
||||
/* if the core is not paused, all alts must be paused to count as a paused trigger */
|
||||
is_paused |= sub_paused;
|
||||
}
|
||||
|
||||
/* if paused, the measured value may not be captured, keep the old value */
|
||||
if (!is_paused) {
|
||||
rc_typed_value_convert(&eval_state.measured_value, RC_VALUE_TYPE_UNSIGNED);
|
||||
self->measured_value = eval_state.measured_value.value.u32;
|
||||
}
|
||||
|
||||
/* if any ResetIf condition was true, reset the hit counts */
|
||||
if (eval_state.was_reset) {
|
||||
/* if the measured value came from a hit count, reset it. do this before calling
|
||||
* rc_reset_trigger_hitcounts in case we need to call rc_condset_is_measured_from_hitcount */
|
||||
if (eval_state.measured_from_hits) {
|
||||
self->measured_value = 0;
|
||||
}
|
||||
else if (is_paused && self->measured_value) {
|
||||
/* if the measured value is in a paused group, measured_from_hits won't have been set.
|
||||
* attempt to determine if it should have been */
|
||||
if (self->requirement && self->requirement->is_paused &&
|
||||
rc_condset_is_measured_from_hitcount(self->requirement, self->measured_value)) {
|
||||
self->measured_value = 0;
|
||||
}
|
||||
else {
|
||||
for (condset = self->alternative; condset; condset = condset->next) {
|
||||
if (condset->is_paused && rc_condset_is_measured_from_hitcount(condset, self->measured_value)) {
|
||||
self->measured_value = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
rc_reset_trigger_hitcounts(self);
|
||||
|
||||
/* if there were hit counts to clear, return RESET, but don't change the state */
|
||||
if (self->has_hits) {
|
||||
self->has_hits = 0;
|
||||
|
||||
/* cannot be PRIMED while ResetIf is true */
|
||||
if (self->state == RC_TRIGGER_STATE_PRIMED)
|
||||
self->state = RC_TRIGGER_STATE_ACTIVE;
|
||||
|
||||
return RC_TRIGGER_STATE_RESET;
|
||||
}
|
||||
|
||||
/* any hits that were tallied were just reset */
|
||||
eval_state.has_hits = 0;
|
||||
is_primed = 0;
|
||||
}
|
||||
else if (ret) {
|
||||
/* if the state is WAITING and the trigger is ready to fire, ignore it and reset the hit counts */
|
||||
if (self->state == RC_TRIGGER_STATE_WAITING) {
|
||||
rc_reset_trigger(self);
|
||||
self->has_hits = 0;
|
||||
return RC_TRIGGER_STATE_WAITING;
|
||||
}
|
||||
|
||||
/* trigger was triggered */
|
||||
self->state = RC_TRIGGER_STATE_TRIGGERED;
|
||||
return RC_TRIGGER_STATE_TRIGGERED;
|
||||
}
|
||||
|
||||
/* did not trigger this frame - update the information we'll need for next time */
|
||||
self->has_hits = eval_state.has_hits;
|
||||
|
||||
if (is_paused) {
|
||||
self->state = RC_TRIGGER_STATE_PAUSED;
|
||||
}
|
||||
else if (is_primed) {
|
||||
self->state = RC_TRIGGER_STATE_PRIMED;
|
||||
}
|
||||
else {
|
||||
self->state = RC_TRIGGER_STATE_ACTIVE;
|
||||
}
|
||||
|
||||
/* if an individual condition was reset, notify the caller */
|
||||
if (eval_state.was_cond_reset)
|
||||
return RC_TRIGGER_STATE_RESET;
|
||||
|
||||
/* otherwise, just return the current state */
|
||||
return self->state;
|
||||
}
|
||||
|
||||
int rc_test_trigger(rc_trigger_t* self, rc_peek_t peek, void* ud, lua_State* L) {
|
||||
/* for backwards compatibilty, rc_test_trigger always assumes the achievement is active */
|
||||
self->state = RC_TRIGGER_STATE_ACTIVE;
|
||||
|
||||
return (rc_evaluate_trigger(self, peek, ud, L) == RC_TRIGGER_STATE_TRIGGERED);
|
||||
}
|
||||
|
||||
void rc_reset_trigger(rc_trigger_t* self) {
|
||||
if (!self)
|
||||
return;
|
||||
|
||||
rc_reset_trigger_hitcounts(self);
|
||||
|
||||
self->state = RC_TRIGGER_STATE_WAITING;
|
||||
|
||||
if (self->measured_target)
|
||||
self->measured_value = RC_MEASURED_UNKNOWN;
|
||||
|
||||
self->has_hits = 0;
|
||||
}
|
|
@ -0,0 +1,719 @@
|
|||
#include "rc_internal.h"
|
||||
|
||||
#include <string.h> /* memset */
|
||||
#include <ctype.h> /* isdigit */
|
||||
#include <float.h> /* FLT_EPSILON */
|
||||
|
||||
static void rc_parse_cond_value(rc_value_t* self, const char** memaddr, rc_parse_state_t* parse) {
|
||||
rc_condset_t** next_clause;
|
||||
|
||||
next_clause = &self->conditions;
|
||||
|
||||
do
|
||||
{
|
||||
parse->measured_target = 0; /* passing is_value=1 should prevent any conflicts, but clear it out anyway */
|
||||
*next_clause = rc_parse_condset(memaddr, parse, 1);
|
||||
if (parse->offset < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (**memaddr == 'S' || **memaddr == 's') {
|
||||
/* alt groups not supported */
|
||||
parse->offset = RC_INVALID_VALUE_FLAG;
|
||||
}
|
||||
else if (parse->measured_target == 0) {
|
||||
parse->offset = RC_MISSING_VALUE_MEASURED;
|
||||
}
|
||||
else if (**memaddr == '$') {
|
||||
/* maximum of */
|
||||
++(*memaddr);
|
||||
next_clause = &(*next_clause)->next;
|
||||
continue;
|
||||
}
|
||||
|
||||
break;
|
||||
} while (1);
|
||||
|
||||
(*next_clause)->next = 0;
|
||||
}
|
||||
|
||||
void rc_parse_legacy_value(rc_value_t* self, const char** memaddr, rc_parse_state_t* parse) {
|
||||
rc_condition_t** next;
|
||||
rc_condset_t** next_clause;
|
||||
rc_condition_t* cond;
|
||||
char buffer[64] = "A:";
|
||||
const char* buffer_ptr;
|
||||
char* ptr;
|
||||
|
||||
/* convert legacy format into condset */
|
||||
self->conditions = RC_ALLOC(rc_condset_t, parse);
|
||||
memset(self->conditions, 0, sizeof(rc_condset_t));
|
||||
|
||||
next = &self->conditions->conditions;
|
||||
next_clause = &self->conditions->next;
|
||||
|
||||
for (;; ++(*memaddr)) {
|
||||
buffer[0] = 'A'; /* reset to AddSource */
|
||||
ptr = &buffer[2];
|
||||
|
||||
/* extract the next clause */
|
||||
for (;; ++(*memaddr)) {
|
||||
switch (**memaddr) {
|
||||
case '_': /* add next */
|
||||
case '$': /* maximum of */
|
||||
case '\0': /* end of string */
|
||||
case ':': /* end of leaderboard clause */
|
||||
case ')': /* end of rich presence macro */
|
||||
*ptr = '\0';
|
||||
break;
|
||||
|
||||
case '*':
|
||||
*ptr++ = '*';
|
||||
|
||||
buffer_ptr = *memaddr + 1;
|
||||
if (*buffer_ptr == '-') {
|
||||
buffer[0] = 'B'; /* change to SubSource */
|
||||
++(*memaddr); /* don't copy sign */
|
||||
++buffer_ptr; /* ignore sign when doing floating point check */
|
||||
}
|
||||
else if (*buffer_ptr == '+') {
|
||||
++buffer_ptr; /* ignore sign when doing floating point check */
|
||||
}
|
||||
|
||||
/* if it looks like a floating point number, add the 'f' prefix */
|
||||
while (isdigit((unsigned char)*buffer_ptr))
|
||||
++buffer_ptr;
|
||||
if (*buffer_ptr == '.')
|
||||
*ptr++ = 'f';
|
||||
continue;
|
||||
|
||||
default:
|
||||
*ptr++ = **memaddr;
|
||||
continue;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
/* process the clause */
|
||||
buffer_ptr = buffer;
|
||||
cond = rc_parse_condition(&buffer_ptr, parse, 0);
|
||||
if (parse->offset < 0)
|
||||
return;
|
||||
|
||||
if (*buffer_ptr) {
|
||||
/* whatever we copied as a single condition was not fully consumed */
|
||||
parse->offset = RC_INVALID_COMPARISON;
|
||||
return;
|
||||
}
|
||||
|
||||
switch (cond->oper) {
|
||||
case RC_OPERATOR_MULT:
|
||||
case RC_OPERATOR_DIV:
|
||||
case RC_OPERATOR_AND:
|
||||
case RC_OPERATOR_XOR:
|
||||
case RC_OPERATOR_NONE:
|
||||
break;
|
||||
|
||||
default:
|
||||
parse->offset = RC_INVALID_OPERATOR;
|
||||
return;
|
||||
}
|
||||
|
||||
*next = cond;
|
||||
|
||||
if (**memaddr == '_') {
|
||||
/* add next */
|
||||
next = &cond->next;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (cond->type == RC_CONDITION_SUB_SOURCE) {
|
||||
/* cannot change SubSource to Measured. add a dummy condition */
|
||||
next = &cond->next;
|
||||
buffer_ptr = "A:0";
|
||||
cond = rc_parse_condition(&buffer_ptr, parse, 0);
|
||||
*next = cond;
|
||||
}
|
||||
|
||||
/* convert final AddSource condition to Measured */
|
||||
cond->type = RC_CONDITION_MEASURED;
|
||||
cond->next = 0;
|
||||
|
||||
if (**memaddr != '$') {
|
||||
/* end of valid string */
|
||||
*next_clause = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
/* max of ($), start a new clause */
|
||||
*next_clause = RC_ALLOC(rc_condset_t, parse);
|
||||
|
||||
if (parse->buffer) /* don't clear in sizing mode or pointer will break */
|
||||
memset(*next_clause, 0, sizeof(rc_condset_t));
|
||||
|
||||
next = &(*next_clause)->conditions;
|
||||
next_clause = &(*next_clause)->next;
|
||||
}
|
||||
}
|
||||
|
||||
void rc_parse_value_internal(rc_value_t* self, const char** memaddr, rc_parse_state_t* parse) {
|
||||
/* if it starts with a condition flag (M: A: B: C:), parse the conditions */
|
||||
if ((*memaddr)[1] == ':') {
|
||||
rc_parse_cond_value(self, memaddr, parse);
|
||||
}
|
||||
else {
|
||||
rc_parse_legacy_value(self, memaddr, parse);
|
||||
}
|
||||
|
||||
self->name = "(unnamed)";
|
||||
self->value.value = self->value.prior = 0;
|
||||
self->value.changed = 0;
|
||||
self->next = 0;
|
||||
}
|
||||
|
||||
int rc_value_size(const char* memaddr) {
|
||||
rc_value_t* self;
|
||||
rc_parse_state_t parse;
|
||||
rc_memref_t* first_memref;
|
||||
rc_init_parse_state(&parse, 0, 0, 0);
|
||||
rc_init_parse_state_memrefs(&parse, &first_memref);
|
||||
|
||||
self = RC_ALLOC(rc_value_t, &parse);
|
||||
rc_parse_value_internal(self, &memaddr, &parse);
|
||||
|
||||
rc_destroy_parse_state(&parse);
|
||||
return parse.offset;
|
||||
}
|
||||
|
||||
rc_value_t* rc_parse_value(void* buffer, const char* memaddr, lua_State* L, int funcs_ndx) {
|
||||
rc_value_t* self;
|
||||
rc_parse_state_t parse;
|
||||
|
||||
if (!buffer || !memaddr)
|
||||
return NULL;
|
||||
|
||||
rc_init_parse_state(&parse, buffer, L, funcs_ndx);
|
||||
|
||||
self = RC_ALLOC(rc_value_t, &parse);
|
||||
rc_init_parse_state_memrefs(&parse, &self->memrefs);
|
||||
|
||||
rc_parse_value_internal(self, &memaddr, &parse);
|
||||
|
||||
rc_destroy_parse_state(&parse);
|
||||
return (parse.offset >= 0) ? self : NULL;
|
||||
}
|
||||
|
||||
int rc_evaluate_value_typed(rc_value_t* self, rc_typed_value_t* value, rc_peek_t peek, void* ud, lua_State* L) {
|
||||
rc_eval_state_t eval_state;
|
||||
rc_condset_t* condset;
|
||||
int valid = 0;
|
||||
|
||||
rc_update_memref_values(self->memrefs, peek, ud);
|
||||
|
||||
value->value.i32 = 0;
|
||||
value->type = RC_VALUE_TYPE_SIGNED;
|
||||
|
||||
for (condset = self->conditions; condset != NULL; condset = condset->next) {
|
||||
memset(&eval_state, 0, sizeof(eval_state));
|
||||
eval_state.peek = peek;
|
||||
eval_state.peek_userdata = ud;
|
||||
eval_state.L = L;
|
||||
|
||||
rc_test_condset(condset, &eval_state);
|
||||
|
||||
if (condset->is_paused)
|
||||
continue;
|
||||
|
||||
if (eval_state.was_reset) {
|
||||
/* if any ResetIf condition was true, reset the hit counts
|
||||
* NOTE: ResetIf only affects the current condset when used in values!
|
||||
*/
|
||||
rc_reset_condset(condset);
|
||||
|
||||
/* if the measured value came from a hit count, reset it too */
|
||||
if (eval_state.measured_from_hits) {
|
||||
eval_state.measured_value.value.u32 = 0;
|
||||
eval_state.measured_value.type = RC_VALUE_TYPE_UNSIGNED;
|
||||
}
|
||||
}
|
||||
|
||||
if (!valid) {
|
||||
/* capture the first valid measurement */
|
||||
memcpy(value, &eval_state.measured_value, sizeof(*value));
|
||||
valid = 1;
|
||||
}
|
||||
else {
|
||||
/* multiple condsets are currently only used for the MAX_OF operation.
|
||||
* only keep the condset's value if it's higher than the current highest value.
|
||||
*/
|
||||
if (rc_typed_value_compare(&eval_state.measured_value, value, RC_OPERATOR_GT))
|
||||
memcpy(value, &eval_state.measured_value, sizeof(*value));
|
||||
}
|
||||
}
|
||||
|
||||
return valid;
|
||||
}
|
||||
|
||||
int32_t rc_evaluate_value(rc_value_t* self, rc_peek_t peek, void* ud, lua_State* L) {
|
||||
rc_typed_value_t result;
|
||||
int valid = rc_evaluate_value_typed(self, &result, peek, ud, L);
|
||||
|
||||
if (valid) {
|
||||
/* if not paused, store the value so that it's available when paused. */
|
||||
rc_typed_value_convert(&result, RC_VALUE_TYPE_UNSIGNED);
|
||||
rc_update_memref_value(&self->value, result.value.u32);
|
||||
}
|
||||
else {
|
||||
/* when paused, the Measured value will not be captured, use the last captured value. */
|
||||
result.value.u32 = self->value.value;
|
||||
result.type = RC_VALUE_TYPE_UNSIGNED;
|
||||
}
|
||||
|
||||
rc_typed_value_convert(&result, RC_VALUE_TYPE_SIGNED);
|
||||
return result.value.i32;
|
||||
}
|
||||
|
||||
void rc_reset_value(rc_value_t* self) {
|
||||
rc_condset_t* condset = self->conditions;
|
||||
while (condset != NULL) {
|
||||
rc_reset_condset(condset);
|
||||
condset = condset->next;
|
||||
}
|
||||
|
||||
self->value.value = self->value.prior = 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) {
|
||||
parse->variables = variables;
|
||||
*variables = 0;
|
||||
}
|
||||
|
||||
rc_value_t* rc_alloc_helper_variable(const char* memaddr, size_t memaddr_len, rc_parse_state_t* parse)
|
||||
{
|
||||
rc_value_t** variables = parse->variables;
|
||||
rc_value_t* value;
|
||||
const char* name;
|
||||
uint32_t measured_target;
|
||||
|
||||
while ((value = *variables) != NULL) {
|
||||
if (strncmp(value->name, memaddr, memaddr_len) == 0 && value->name[memaddr_len] == 0)
|
||||
return value;
|
||||
|
||||
variables = &value->next;
|
||||
}
|
||||
|
||||
value = RC_ALLOC_SCRATCH(rc_value_t, parse);
|
||||
memset(&value->value, 0, sizeof(value->value));
|
||||
value->value.size = RC_MEMSIZE_VARIABLE;
|
||||
value->memrefs = NULL;
|
||||
|
||||
/* capture name before calling parse as parse will update memaddr pointer */
|
||||
name = rc_alloc_str(parse, memaddr, memaddr_len);
|
||||
if (!name)
|
||||
return NULL;
|
||||
|
||||
/* the helper variable likely has a Measured condition. capture the current measured_target so we can restore it
|
||||
* after generating the variable so the variable's Measured target doesn't conflict with the rest of the trigger. */
|
||||
measured_target = parse->measured_target;
|
||||
|
||||
/* disable variable resolution when defining a variable to prevent infinite recursion */
|
||||
variables = parse->variables;
|
||||
parse->variables = NULL;
|
||||
rc_parse_value_internal(value, &memaddr, parse);
|
||||
parse->variables = variables;
|
||||
|
||||
/* restore the measured target */
|
||||
parse->measured_target = measured_target;
|
||||
|
||||
/* store name after calling parse as parse will set name to (unnamed) */
|
||||
value->name = name;
|
||||
|
||||
/* append the new variable to the end of the list (have to re-evaluate in case any others were added) */
|
||||
while (*variables != NULL)
|
||||
variables = &(*variables)->next;
|
||||
*variables = value;
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
void rc_update_variables(rc_value_t* variable, rc_peek_t peek, void* ud, lua_State* L) {
|
||||
rc_typed_value_t result;
|
||||
|
||||
while (variable) {
|
||||
if (rc_evaluate_value_typed(variable, &result, peek, ud, L)) {
|
||||
/* store the raw bytes and type to be restored by rc_typed_value_from_memref_value */
|
||||
rc_update_memref_value(&variable->value, result.value.u32);
|
||||
variable->value.type = result.type;
|
||||
}
|
||||
|
||||
variable = variable->next;
|
||||
}
|
||||
}
|
||||
|
||||
void rc_typed_value_from_memref_value(rc_typed_value_t* value, const rc_memref_value_t* memref) {
|
||||
value->value.u32 = memref->value;
|
||||
|
||||
if (memref->size == RC_MEMSIZE_VARIABLE) {
|
||||
/* a variable can be any of the supported types, but the raw data was copied into u32 */
|
||||
value->type = memref->type;
|
||||
}
|
||||
else {
|
||||
/* not a variable, only u32 is supported */
|
||||
value->type = RC_VALUE_TYPE_UNSIGNED;
|
||||
}
|
||||
}
|
||||
|
||||
void rc_typed_value_convert(rc_typed_value_t* value, char new_type) {
|
||||
switch (new_type) {
|
||||
case RC_VALUE_TYPE_UNSIGNED:
|
||||
switch (value->type) {
|
||||
case RC_VALUE_TYPE_UNSIGNED:
|
||||
return;
|
||||
case RC_VALUE_TYPE_SIGNED:
|
||||
value->value.u32 = (unsigned)value->value.i32;
|
||||
break;
|
||||
case RC_VALUE_TYPE_FLOAT:
|
||||
value->value.u32 = (unsigned)value->value.f32;
|
||||
break;
|
||||
default:
|
||||
value->value.u32 = 0;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case RC_VALUE_TYPE_SIGNED:
|
||||
switch (value->type) {
|
||||
case RC_VALUE_TYPE_SIGNED:
|
||||
return;
|
||||
case RC_VALUE_TYPE_UNSIGNED:
|
||||
value->value.i32 = (int)value->value.u32;
|
||||
break;
|
||||
case RC_VALUE_TYPE_FLOAT:
|
||||
value->value.i32 = (int)value->value.f32;
|
||||
break;
|
||||
default:
|
||||
value->value.i32 = 0;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case RC_VALUE_TYPE_FLOAT:
|
||||
switch (value->type) {
|
||||
case RC_VALUE_TYPE_FLOAT:
|
||||
return;
|
||||
case RC_VALUE_TYPE_UNSIGNED:
|
||||
value->value.f32 = (float)value->value.u32;
|
||||
break;
|
||||
case RC_VALUE_TYPE_SIGNED:
|
||||
value->value.f32 = (float)value->value.i32;
|
||||
break;
|
||||
default:
|
||||
value->value.f32 = 0.0;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
value->type = new_type;
|
||||
}
|
||||
|
||||
static rc_typed_value_t* rc_typed_value_convert_into(rc_typed_value_t* dest, const rc_typed_value_t* source, char new_type) {
|
||||
memcpy(dest, source, sizeof(rc_typed_value_t));
|
||||
rc_typed_value_convert(dest, new_type);
|
||||
return dest;
|
||||
}
|
||||
|
||||
void rc_typed_value_negate(rc_typed_value_t* value) {
|
||||
switch (value->type)
|
||||
{
|
||||
case RC_VALUE_TYPE_UNSIGNED:
|
||||
rc_typed_value_convert(value, RC_VALUE_TYPE_SIGNED);
|
||||
/* fallthrough */ /* to RC_VALUE_TYPE_SIGNED */
|
||||
|
||||
case RC_VALUE_TYPE_SIGNED:
|
||||
value->value.i32 = -(value->value.i32);
|
||||
break;
|
||||
|
||||
case RC_VALUE_TYPE_FLOAT:
|
||||
value->value.f32 = -(value->value.f32);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void rc_typed_value_add(rc_typed_value_t* value, const rc_typed_value_t* amount) {
|
||||
rc_typed_value_t converted;
|
||||
|
||||
if (amount->type != value->type && value->type != RC_VALUE_TYPE_NONE)
|
||||
amount = rc_typed_value_convert_into(&converted, amount, value->type);
|
||||
|
||||
switch (value->type)
|
||||
{
|
||||
case RC_VALUE_TYPE_UNSIGNED:
|
||||
value->value.u32 += amount->value.u32;
|
||||
break;
|
||||
|
||||
case RC_VALUE_TYPE_SIGNED:
|
||||
value->value.i32 += amount->value.i32;
|
||||
break;
|
||||
|
||||
case RC_VALUE_TYPE_FLOAT:
|
||||
value->value.f32 += amount->value.f32;
|
||||
break;
|
||||
|
||||
case RC_VALUE_TYPE_NONE:
|
||||
memcpy(value, amount, sizeof(rc_typed_value_t));
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void rc_typed_value_multiply(rc_typed_value_t* value, const rc_typed_value_t* amount) {
|
||||
rc_typed_value_t converted;
|
||||
|
||||
switch (value->type)
|
||||
{
|
||||
case RC_VALUE_TYPE_UNSIGNED:
|
||||
switch (amount->type)
|
||||
{
|
||||
case RC_VALUE_TYPE_UNSIGNED:
|
||||
/* the c standard for unsigned multiplication is well defined as non-overflowing truncation
|
||||
* to the type's size. this allows negative multiplication through twos-complements. i.e.
|
||||
* 1 * -1 (0xFFFFFFFF) = 0xFFFFFFFF = -1
|
||||
* 3 * -2 (0xFFFFFFFE) = 0x2FFFFFFFA & 0xFFFFFFFF = 0xFFFFFFFA = -6
|
||||
* 10 * -5 (0xFFFFFFFB) = 0x9FFFFFFCE & 0xFFFFFFFF = 0xFFFFFFCE = -50
|
||||
*/
|
||||
value->value.u32 *= amount->value.u32;
|
||||
break;
|
||||
|
||||
case RC_VALUE_TYPE_SIGNED:
|
||||
value->value.u32 *= (unsigned)amount->value.i32;
|
||||
break;
|
||||
|
||||
case RC_VALUE_TYPE_FLOAT:
|
||||
rc_typed_value_convert(value, RC_VALUE_TYPE_FLOAT);
|
||||
value->value.f32 *= amount->value.f32;
|
||||
break;
|
||||
|
||||
default:
|
||||
value->type = RC_VALUE_TYPE_NONE;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case RC_VALUE_TYPE_SIGNED:
|
||||
switch (amount->type)
|
||||
{
|
||||
case RC_VALUE_TYPE_SIGNED:
|
||||
value->value.i32 *= amount->value.i32;
|
||||
break;
|
||||
|
||||
case RC_VALUE_TYPE_UNSIGNED:
|
||||
value->value.i32 *= (int)amount->value.u32;
|
||||
break;
|
||||
|
||||
case RC_VALUE_TYPE_FLOAT:
|
||||
rc_typed_value_convert(value, RC_VALUE_TYPE_FLOAT);
|
||||
value->value.f32 *= amount->value.f32;
|
||||
break;
|
||||
|
||||
default:
|
||||
value->type = RC_VALUE_TYPE_NONE;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case RC_VALUE_TYPE_FLOAT:
|
||||
if (amount->type == RC_VALUE_TYPE_NONE) {
|
||||
value->type = RC_VALUE_TYPE_NONE;
|
||||
}
|
||||
else {
|
||||
amount = rc_typed_value_convert_into(&converted, amount, RC_VALUE_TYPE_FLOAT);
|
||||
value->value.f32 *= amount->value.f32;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
value->type = RC_VALUE_TYPE_NONE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void rc_typed_value_divide(rc_typed_value_t* value, const rc_typed_value_t* amount) {
|
||||
rc_typed_value_t converted;
|
||||
|
||||
switch (amount->type)
|
||||
{
|
||||
case RC_VALUE_TYPE_UNSIGNED:
|
||||
if (amount->value.u32 == 0) { /* divide by zero */
|
||||
value->type = RC_VALUE_TYPE_NONE;
|
||||
return;
|
||||
}
|
||||
|
||||
switch (value->type) {
|
||||
case RC_VALUE_TYPE_UNSIGNED: /* integer math */
|
||||
value->value.u32 /= amount->value.u32;
|
||||
return;
|
||||
case RC_VALUE_TYPE_SIGNED: /* integer math */
|
||||
value->value.i32 /= (int)amount->value.u32;
|
||||
return;
|
||||
case RC_VALUE_TYPE_FLOAT:
|
||||
amount = rc_typed_value_convert_into(&converted, amount, RC_VALUE_TYPE_FLOAT);
|
||||
break;
|
||||
default:
|
||||
value->type = RC_VALUE_TYPE_NONE;
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
case RC_VALUE_TYPE_SIGNED:
|
||||
if (amount->value.i32 == 0) { /* divide by zero */
|
||||
value->type = RC_VALUE_TYPE_NONE;
|
||||
return;
|
||||
}
|
||||
|
||||
switch (value->type) {
|
||||
case RC_VALUE_TYPE_SIGNED: /* integer math */
|
||||
value->value.i32 /= amount->value.i32;
|
||||
return;
|
||||
case RC_VALUE_TYPE_UNSIGNED: /* integer math */
|
||||
value->value.u32 /= (unsigned)amount->value.i32;
|
||||
return;
|
||||
case RC_VALUE_TYPE_FLOAT:
|
||||
amount = rc_typed_value_convert_into(&converted, amount, RC_VALUE_TYPE_FLOAT);
|
||||
break;
|
||||
default:
|
||||
value->type = RC_VALUE_TYPE_NONE;
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
case RC_VALUE_TYPE_FLOAT:
|
||||
break;
|
||||
|
||||
default:
|
||||
value->type = RC_VALUE_TYPE_NONE;
|
||||
return;
|
||||
}
|
||||
|
||||
if (amount->value.f32 == 0.0) { /* divide by zero */
|
||||
value->type = RC_VALUE_TYPE_NONE;
|
||||
return;
|
||||
}
|
||||
|
||||
rc_typed_value_convert(value, RC_VALUE_TYPE_FLOAT);
|
||||
value->value.f32 /= amount->value.f32;
|
||||
}
|
||||
|
||||
static int rc_typed_value_compare_floats(float f1, float f2, char oper) {
|
||||
if (f1 == f2) {
|
||||
/* exactly equal */
|
||||
}
|
||||
else {
|
||||
/* attempt to match 7 significant digits (24-bit mantissa supports just over 7 significant decimal digits) */
|
||||
/* https://stackoverflow.com/questions/17333/what-is-the-most-effective-way-for-float-and-double-comparison */
|
||||
const float abs1 = (f1 < 0) ? -f1 : f1;
|
||||
const float abs2 = (f2 < 0) ? -f2 : f2;
|
||||
const float threshold = ((abs1 < abs2) ? abs1 : abs2) * FLT_EPSILON;
|
||||
const float diff = f1 - f2;
|
||||
const float abs_diff = (diff < 0) ? -diff : diff;
|
||||
|
||||
if (abs_diff <= threshold) {
|
||||
/* approximately equal */
|
||||
}
|
||||
else if (diff > threshold) {
|
||||
/* greater */
|
||||
switch (oper) {
|
||||
case RC_OPERATOR_NE:
|
||||
case RC_OPERATOR_GT:
|
||||
case RC_OPERATOR_GE:
|
||||
return 1;
|
||||
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
else {
|
||||
/* lesser */
|
||||
switch (oper) {
|
||||
case RC_OPERATOR_NE:
|
||||
case RC_OPERATOR_LT:
|
||||
case RC_OPERATOR_LE:
|
||||
return 1;
|
||||
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* exactly or approximately equal */
|
||||
switch (oper) {
|
||||
case RC_OPERATOR_EQ:
|
||||
case RC_OPERATOR_GE:
|
||||
case RC_OPERATOR_LE:
|
||||
return 1;
|
||||
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
int rc_typed_value_compare(const rc_typed_value_t* value1, const rc_typed_value_t* value2, char oper) {
|
||||
rc_typed_value_t converted_value2;
|
||||
if (value2->type != value1->type)
|
||||
value2 = rc_typed_value_convert_into(&converted_value2, value2, value1->type);
|
||||
|
||||
switch (value1->type) {
|
||||
case RC_VALUE_TYPE_UNSIGNED:
|
||||
switch (oper) {
|
||||
case RC_OPERATOR_EQ: return value1->value.u32 == value2->value.u32;
|
||||
case RC_OPERATOR_NE: return value1->value.u32 != value2->value.u32;
|
||||
case RC_OPERATOR_LT: return value1->value.u32 < value2->value.u32;
|
||||
case RC_OPERATOR_LE: return value1->value.u32 <= value2->value.u32;
|
||||
case RC_OPERATOR_GT: return value1->value.u32 > value2->value.u32;
|
||||
case RC_OPERATOR_GE: return value1->value.u32 >= value2->value.u32;
|
||||
default: return 1;
|
||||
}
|
||||
|
||||
case RC_VALUE_TYPE_SIGNED:
|
||||
switch (oper) {
|
||||
case RC_OPERATOR_EQ: return value1->value.i32 == value2->value.i32;
|
||||
case RC_OPERATOR_NE: return value1->value.i32 != value2->value.i32;
|
||||
case RC_OPERATOR_LT: return value1->value.i32 < value2->value.i32;
|
||||
case RC_OPERATOR_LE: return value1->value.i32 <= value2->value.i32;
|
||||
case RC_OPERATOR_GT: return value1->value.i32 > value2->value.i32;
|
||||
case RC_OPERATOR_GE: return value1->value.i32 >= value2->value.i32;
|
||||
default: return 1;
|
||||
}
|
||||
|
||||
case RC_VALUE_TYPE_FLOAT:
|
||||
return rc_typed_value_compare_floats(value1->value.f32, value2->value.f32, oper);
|
||||
|
||||
default:
|
||||
return 1;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,879 @@
|
|||
#include "rc_hash.h"
|
||||
|
||||
#include "../rc_compat.h"
|
||||
|
||||
#include <ctype.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.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_hash_error(const char* message);
|
||||
extern const char* rc_path_get_filename(const char* path);
|
||||
extern int rc_path_compare_extension(const char* path, const char* ext);
|
||||
extern rc_hash_message_callback verbose_message_callback;
|
||||
|
||||
struct cdrom_t
|
||||
{
|
||||
void* file_handle; /* the file handle for reading the track data */
|
||||
int sector_size; /* the size of each sector in the track data */
|
||||
int sector_header_size; /* the offset to the raw data within a sector block */
|
||||
int raw_data_size; /* the amount of raw data within a sector block */
|
||||
int64_t file_track_offset;/* the offset of the track data within the file */
|
||||
int track_first_sector; /* the first absolute sector associated to the track (includes pregap) */
|
||||
int track_pregap_sectors; /* the number of pregap sectors */
|
||||
#ifndef NDEBUG
|
||||
uint32_t track_id; /* the index of the track */
|
||||
#endif
|
||||
};
|
||||
|
||||
static int cdreader_get_sector(uint8_t header[16])
|
||||
{
|
||||
int minutes = (header[12] >> 4) * 10 + (header[12] & 0x0F);
|
||||
int seconds = (header[13] >> 4) * 10 + (header[13] & 0x0F);
|
||||
int frames = (header[14] >> 4) * 10 + (header[14] & 0x0F);
|
||||
|
||||
/* convert the MSF value to a sector index, and subtract 150 (2 seconds) per:
|
||||
* For data and mixed mode media (those conforming to ISO/IEC 10149), logical block address
|
||||
* zero shall be assigned to the block at MSF address 00/02/00 */
|
||||
return ((minutes * 60) + seconds) * 75 + frames - 150;
|
||||
}
|
||||
|
||||
static void cdreader_determine_sector_size(struct cdrom_t* cdrom)
|
||||
{
|
||||
/* Attempt to determine the sector and header sizes. The CUE file may be lying.
|
||||
* Look for the sync pattern using each of the supported sector sizes.
|
||||
* Then check for the presence of "CD001", which is gauranteed to be in either the
|
||||
* boot record or primary volume descriptor, one of which is always at sector 16.
|
||||
*/
|
||||
const uint8_t sync_pattern[] = {
|
||||
0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00
|
||||
};
|
||||
|
||||
uint8_t header[32];
|
||||
const int64_t toc_sector = 16 + cdrom->track_pregap_sectors;
|
||||
|
||||
cdrom->sector_size = 0;
|
||||
cdrom->sector_header_size = 0;
|
||||
cdrom->raw_data_size = 2048;
|
||||
|
||||
rc_file_seek(cdrom->file_handle, toc_sector * 2352 + cdrom->file_track_offset, SEEK_SET);
|
||||
if (rc_file_read(cdrom->file_handle, header, sizeof(header)) < sizeof(header))
|
||||
return;
|
||||
|
||||
if (memcmp(header, sync_pattern, 12) == 0)
|
||||
{
|
||||
cdrom->sector_size = 2352;
|
||||
|
||||
if (memcmp(&header[25], "CD001", 5) == 0)
|
||||
cdrom->sector_header_size = 24;
|
||||
else
|
||||
cdrom->sector_header_size = 16;
|
||||
|
||||
cdrom->track_first_sector = cdreader_get_sector(header) - (int)toc_sector;
|
||||
}
|
||||
else
|
||||
{
|
||||
rc_file_seek(cdrom->file_handle, toc_sector * 2336 + cdrom->file_track_offset, SEEK_SET);
|
||||
rc_file_read(cdrom->file_handle, header, sizeof(header));
|
||||
|
||||
if (memcmp(header, sync_pattern, 12) == 0)
|
||||
{
|
||||
cdrom->sector_size = 2336;
|
||||
|
||||
if (memcmp(&header[25], "CD001", 5) == 0)
|
||||
cdrom->sector_header_size = 24;
|
||||
else
|
||||
cdrom->sector_header_size = 16;
|
||||
|
||||
cdrom->track_first_sector = cdreader_get_sector(header) - (int)toc_sector;
|
||||
}
|
||||
else
|
||||
{
|
||||
rc_file_seek(cdrom->file_handle, toc_sector * 2048 + cdrom->file_track_offset, SEEK_SET);
|
||||
rc_file_read(cdrom->file_handle, header, sizeof(header));
|
||||
|
||||
if (memcmp(&header[1], "CD001", 5) == 0)
|
||||
{
|
||||
cdrom->sector_size = 2048;
|
||||
cdrom->sector_header_size = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void* cdreader_open_bin_track(const char* path, uint32_t track)
|
||||
{
|
||||
void* file_handle;
|
||||
struct cdrom_t* cdrom;
|
||||
|
||||
if (track > 1)
|
||||
{
|
||||
if (verbose_message_callback)
|
||||
verbose_message_callback("Cannot locate secondary tracks without a cue sheet");
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
file_handle = rc_file_open(path);
|
||||
if (!file_handle)
|
||||
return NULL;
|
||||
|
||||
cdrom = (struct cdrom_t*)calloc(1, sizeof(*cdrom));
|
||||
if (!cdrom)
|
||||
return NULL;
|
||||
cdrom->file_handle = file_handle;
|
||||
#ifndef NDEBUG
|
||||
cdrom->track_id = track;
|
||||
#endif
|
||||
|
||||
cdreader_determine_sector_size(cdrom);
|
||||
|
||||
if (cdrom->sector_size == 0)
|
||||
{
|
||||
int64_t size;
|
||||
|
||||
rc_file_seek(cdrom->file_handle, 0, SEEK_END);
|
||||
size = rc_file_tell(cdrom->file_handle);
|
||||
|
||||
if ((size % 2352) == 0)
|
||||
{
|
||||
/* raw tracks use all 2352 bytes and have a 24 byte header */
|
||||
cdrom->sector_size = 2352;
|
||||
cdrom->sector_header_size = 24;
|
||||
}
|
||||
else if ((size % 2048) == 0)
|
||||
{
|
||||
/* cooked tracks eliminate all header/footer data */
|
||||
cdrom->sector_size = 2048;
|
||||
cdrom->sector_header_size = 0;
|
||||
}
|
||||
else if ((size % 2336) == 0)
|
||||
{
|
||||
/* MODE 2 format without 16-byte sync data */
|
||||
cdrom->sector_size = 2336;
|
||||
cdrom->sector_header_size = 8;
|
||||
}
|
||||
else
|
||||
{
|
||||
free(cdrom);
|
||||
|
||||
if (verbose_message_callback)
|
||||
verbose_message_callback("Could not determine sector size");
|
||||
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
return cdrom;
|
||||
}
|
||||
|
||||
static int cdreader_open_bin(struct cdrom_t* cdrom, const char* path, const char* mode)
|
||||
{
|
||||
cdrom->file_handle = rc_file_open(path);
|
||||
if (!cdrom->file_handle)
|
||||
return 0;
|
||||
|
||||
/* determine sector size */
|
||||
cdreader_determine_sector_size(cdrom);
|
||||
|
||||
/* could not determine, which means we'll probably have more issues later
|
||||
* but use the CUE provided information anyway
|
||||
*/
|
||||
if (cdrom->sector_size == 0)
|
||||
{
|
||||
/* All of these modes have 2048 byte payloads. In MODE1/2352 and MODE2/2352
|
||||
* modes, the mode can actually be specified per sector to change the payload
|
||||
* size, but that reduces the ability to recover from errors when the disc
|
||||
* is damaged, so it's seldomly used, and when it is, it's mostly for audio
|
||||
* or video data where a blip or two probably won't be noticed by the user.
|
||||
* So, while we techincally support all of the following modes, we only do
|
||||
* so with 2048 byte payloads.
|
||||
* http://totalsonicmastering.com/cuesheetsyntax.htm
|
||||
* MODE1/2048 ? CDROM Mode1 Data (cooked) [no header, no footer]
|
||||
* MODE1/2352 ? CDROM Mode1 Data (raw) [16 byte header, 288 byte footer]
|
||||
* MODE2/2336 ? CDROM-XA Mode2 Data [8 byte header, 280 byte footer]
|
||||
* MODE2/2352 ? CDROM-XA Mode2 Data [24 byte header, 280 byte footer]
|
||||
*/
|
||||
if (memcmp(mode, "MODE2/2352", 10) == 0)
|
||||
{
|
||||
cdrom->sector_size = 2352;
|
||||
cdrom->sector_header_size = 24;
|
||||
}
|
||||
else if (memcmp(mode, "MODE1/2048", 10) == 0)
|
||||
{
|
||||
cdrom->sector_size = 2048;
|
||||
cdrom->sector_header_size = 0;
|
||||
}
|
||||
else if (memcmp(mode, "MODE2/2336", 10) == 0)
|
||||
{
|
||||
cdrom->sector_size = 2336;
|
||||
cdrom->sector_header_size = 8;
|
||||
}
|
||||
else if (memcmp(mode, "MODE1/2352", 10) == 0)
|
||||
{
|
||||
cdrom->sector_size = 2352;
|
||||
cdrom->sector_header_size = 16;
|
||||
}
|
||||
else if (memcmp(mode, "AUDIO", 5) == 0)
|
||||
{
|
||||
cdrom->sector_size = 2352;
|
||||
cdrom->sector_header_size = 0;
|
||||
cdrom->raw_data_size = 2352; /* no header or footer data on audio tracks */
|
||||
}
|
||||
}
|
||||
|
||||
return (cdrom->sector_size != 0);
|
||||
}
|
||||
|
||||
static char* cdreader_get_bin_path(const char* cue_path, const char* bin_name)
|
||||
{
|
||||
const char* filename = rc_path_get_filename(cue_path);
|
||||
const size_t bin_name_len = strlen(bin_name);
|
||||
const size_t cue_path_len = filename - cue_path;
|
||||
const size_t needed = cue_path_len + bin_name_len + 1;
|
||||
|
||||
char* bin_filename = (char*)malloc(needed);
|
||||
if (!bin_filename)
|
||||
{
|
||||
char buffer[64];
|
||||
snprintf(buffer, sizeof(buffer), "Failed to allocate %u bytes", (unsigned)needed);
|
||||
rc_hash_error((const char*)buffer);
|
||||
}
|
||||
else
|
||||
{
|
||||
memcpy(bin_filename, cue_path, cue_path_len);
|
||||
memcpy(bin_filename + cue_path_len, bin_name, bin_name_len + 1);
|
||||
}
|
||||
|
||||
return bin_filename;
|
||||
}
|
||||
|
||||
static int64_t cdreader_get_bin_size(const char* cue_path, const char* bin_name)
|
||||
{
|
||||
int64_t size = 0;
|
||||
char* bin_filename = cdreader_get_bin_path(cue_path, bin_name);
|
||||
if (bin_filename)
|
||||
{
|
||||
/* disable verbose messaging while getting file size */
|
||||
rc_hash_message_callback old_verbose_message_callback = verbose_message_callback;
|
||||
void* file_handle;
|
||||
verbose_message_callback = NULL;
|
||||
|
||||
file_handle = rc_file_open(bin_filename);
|
||||
if (file_handle)
|
||||
{
|
||||
rc_file_seek(file_handle, 0, SEEK_END);
|
||||
size = rc_file_tell(file_handle);
|
||||
rc_file_close(file_handle);
|
||||
}
|
||||
|
||||
verbose_message_callback = old_verbose_message_callback;
|
||||
free(bin_filename);
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
static void* cdreader_open_cue_track(const char* path, uint32_t track)
|
||||
{
|
||||
void* cue_handle;
|
||||
int64_t cue_offset = 0;
|
||||
char buffer[1024];
|
||||
char* bin_filename = NULL;
|
||||
char *ptr, *ptr2, *end;
|
||||
int done = 0;
|
||||
int session = 1;
|
||||
size_t num_read = 0;
|
||||
struct cdrom_t* cdrom = NULL;
|
||||
|
||||
struct track_t
|
||||
{
|
||||
uint32_t id;
|
||||
int sector_size;
|
||||
int sector_count;
|
||||
int first_sector;
|
||||
int pregap_sectors;
|
||||
int is_data;
|
||||
int file_track_offset;
|
||||
int file_first_sector;
|
||||
char mode[16];
|
||||
char filename[256];
|
||||
} current_track, previous_track, largest_track;
|
||||
|
||||
cue_handle = rc_file_open(path);
|
||||
if (!cue_handle)
|
||||
return NULL;
|
||||
|
||||
memset(¤t_track, 0, sizeof(current_track));
|
||||
memset(&previous_track, 0, sizeof(previous_track));
|
||||
memset(&largest_track, 0, sizeof(largest_track));
|
||||
|
||||
do
|
||||
{
|
||||
num_read = rc_file_read(cue_handle, buffer, sizeof(buffer) - 1);
|
||||
if (num_read == 0)
|
||||
break;
|
||||
|
||||
buffer[num_read] = 0;
|
||||
if (num_read == sizeof(buffer) - 1)
|
||||
end = buffer + sizeof(buffer) * 3 / 4;
|
||||
else
|
||||
end = buffer + num_read;
|
||||
|
||||
for (ptr = buffer; ptr < end; ++ptr)
|
||||
{
|
||||
while (*ptr == ' ')
|
||||
++ptr;
|
||||
|
||||
if (strncasecmp(ptr, "INDEX ", 6) == 0)
|
||||
{
|
||||
int m = 0, s = 0, f = 0;
|
||||
int index;
|
||||
int sector_offset;
|
||||
|
||||
ptr += 6;
|
||||
index = atoi(ptr);
|
||||
|
||||
while (*ptr != ' ' && *ptr != '\n')
|
||||
++ptr;
|
||||
while (*ptr == ' ')
|
||||
++ptr;
|
||||
|
||||
/* convert mm:ss:ff to sector count */
|
||||
sscanf_s(ptr, "%d:%d:%d", &m, &s, &f);
|
||||
sector_offset = ((m * 60) + s) * 75 + f;
|
||||
|
||||
if (current_track.first_sector == -1)
|
||||
{
|
||||
current_track.first_sector = sector_offset;
|
||||
if (strcmp(current_track.filename, previous_track.filename) == 0)
|
||||
{
|
||||
previous_track.sector_count = current_track.first_sector - previous_track.first_sector;
|
||||
current_track.file_track_offset += previous_track.sector_count * previous_track.sector_size;
|
||||
}
|
||||
|
||||
/* if looking for the largest data track, determine previous track size */
|
||||
if (track == RC_HASH_CDTRACK_LARGEST && previous_track.sector_count > largest_track.sector_count &&
|
||||
previous_track.is_data)
|
||||
{
|
||||
memcpy(&largest_track, &previous_track, sizeof(largest_track));
|
||||
}
|
||||
}
|
||||
|
||||
if (index == 1)
|
||||
{
|
||||
current_track.pregap_sectors = (sector_offset - current_track.first_sector);
|
||||
|
||||
if (verbose_message_callback)
|
||||
{
|
||||
char message[128];
|
||||
char* scan = current_track.mode;
|
||||
while (*scan && !isspace((unsigned char)*scan))
|
||||
++scan;
|
||||
*scan = '\0';
|
||||
|
||||
/* it's undesirable to truncate offset to 32-bits, but %lld isn't defined in c89. */
|
||||
snprintf(message, sizeof(message), "Found %s track %d (first sector %d, sector size %d, %d pregap sectors)",
|
||||
current_track.mode, current_track.id, current_track.first_sector, current_track.sector_size, current_track.pregap_sectors);
|
||||
verbose_message_callback(message);
|
||||
}
|
||||
|
||||
if (current_track.id == track)
|
||||
{
|
||||
done = 1;
|
||||
break;
|
||||
}
|
||||
|
||||
if (track == RC_HASH_CDTRACK_FIRST_DATA && current_track.is_data)
|
||||
{
|
||||
track = current_track.id;
|
||||
done = 1;
|
||||
break;
|
||||
}
|
||||
|
||||
if (track == RC_HASH_CDTRACK_FIRST_OF_SECOND_SESSION && session == 2)
|
||||
{
|
||||
track = current_track.id;
|
||||
done = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (strncasecmp(ptr, "TRACK ", 6) == 0)
|
||||
{
|
||||
if (current_track.sector_size)
|
||||
memcpy(&previous_track, ¤t_track, sizeof(current_track));
|
||||
|
||||
ptr += 6;
|
||||
current_track.id = atoi(ptr);
|
||||
|
||||
current_track.pregap_sectors = -1;
|
||||
current_track.first_sector = -1;
|
||||
|
||||
while (*ptr != ' ')
|
||||
++ptr;
|
||||
while (*ptr == ' ')
|
||||
++ptr;
|
||||
memcpy(current_track.mode, ptr, sizeof(current_track.mode));
|
||||
current_track.is_data = (memcmp(current_track.mode, "MODE", 4) == 0);
|
||||
|
||||
if (current_track.is_data)
|
||||
{
|
||||
current_track.sector_size = atoi(ptr + 6);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* assume AUDIO */
|
||||
current_track.sector_size = 2352;
|
||||
}
|
||||
}
|
||||
else if (strncasecmp(ptr, "FILE ", 5) == 0)
|
||||
{
|
||||
if (current_track.sector_size)
|
||||
{
|
||||
memcpy(&previous_track, ¤t_track, sizeof(previous_track));
|
||||
|
||||
if (previous_track.sector_count == 0)
|
||||
{
|
||||
const uint32_t file_sector_count = (uint32_t)cdreader_get_bin_size(path, previous_track.filename) / previous_track.sector_size;
|
||||
previous_track.sector_count = file_sector_count - previous_track.first_sector;
|
||||
}
|
||||
|
||||
/* if looking for the largest data track, check to see if this one is larger */
|
||||
if (track == RC_HASH_CDTRACK_LARGEST && previous_track.is_data &&
|
||||
previous_track.sector_count > largest_track.sector_count)
|
||||
{
|
||||
memcpy(&largest_track, &previous_track, sizeof(largest_track));
|
||||
}
|
||||
}
|
||||
|
||||
memset(¤t_track, 0, sizeof(current_track));
|
||||
|
||||
current_track.file_first_sector = previous_track.file_first_sector +
|
||||
previous_track.first_sector + previous_track.sector_count;
|
||||
|
||||
ptr += 5;
|
||||
ptr2 = ptr;
|
||||
if (*ptr == '"')
|
||||
{
|
||||
++ptr;
|
||||
do
|
||||
{
|
||||
++ptr2;
|
||||
} while (*ptr2 && *ptr2 != '\n' && *ptr2 != '"');
|
||||
}
|
||||
else
|
||||
{
|
||||
do
|
||||
{
|
||||
++ptr2;
|
||||
} while (*ptr2 && *ptr2 != '\n' && *ptr2 != ' ');
|
||||
}
|
||||
|
||||
if (ptr2 - ptr < (int)sizeof(current_track.filename))
|
||||
memcpy(current_track.filename, ptr, ptr2 - ptr);
|
||||
}
|
||||
else if (strncasecmp(ptr, "REM ", 4) == 0)
|
||||
{
|
||||
ptr += 4;
|
||||
while (*ptr == ' ')
|
||||
++ptr;
|
||||
|
||||
if (strncasecmp(ptr, "SESSION ", 8) == 0)
|
||||
{
|
||||
ptr += 8;
|
||||
while (*ptr == ' ')
|
||||
++ptr;
|
||||
session = atoi(ptr);
|
||||
}
|
||||
}
|
||||
|
||||
while (*ptr && *ptr != '\n')
|
||||
++ptr;
|
||||
}
|
||||
|
||||
if (done)
|
||||
break;
|
||||
|
||||
cue_offset += (ptr - buffer);
|
||||
rc_file_seek(cue_handle, cue_offset, SEEK_SET);
|
||||
|
||||
} while (1);
|
||||
|
||||
rc_file_close(cue_handle);
|
||||
|
||||
if (track == RC_HASH_CDTRACK_LARGEST)
|
||||
{
|
||||
if (current_track.sector_size && current_track.is_data)
|
||||
{
|
||||
const uint32_t file_sector_count = (uint32_t)cdreader_get_bin_size(path, current_track.filename) / current_track.sector_size;
|
||||
current_track.sector_count = file_sector_count - current_track.first_sector;
|
||||
|
||||
if (largest_track.sector_count > current_track.sector_count)
|
||||
memcpy(¤t_track, &largest_track, sizeof(current_track));
|
||||
}
|
||||
else
|
||||
{
|
||||
memcpy(¤t_track, &largest_track, sizeof(current_track));
|
||||
}
|
||||
|
||||
track = current_track.id;
|
||||
}
|
||||
else if (track == RC_HASH_CDTRACK_LAST && !done)
|
||||
{
|
||||
track = current_track.id;
|
||||
}
|
||||
|
||||
if (current_track.id == track)
|
||||
{
|
||||
cdrom = (struct cdrom_t*)calloc(1, sizeof(*cdrom));
|
||||
if (!cdrom)
|
||||
{
|
||||
snprintf((char*)buffer, sizeof(buffer), "Failed to allocate %u bytes", (unsigned)sizeof(*cdrom));
|
||||
rc_hash_error((const char*)buffer);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
cdrom->file_track_offset = current_track.file_track_offset;
|
||||
cdrom->track_pregap_sectors = current_track.pregap_sectors;
|
||||
cdrom->track_first_sector = current_track.file_first_sector + current_track.first_sector;
|
||||
#ifndef NDEBUG
|
||||
cdrom->track_id = current_track.id;
|
||||
#endif
|
||||
|
||||
/* verify existance of bin file */
|
||||
bin_filename = cdreader_get_bin_path(path, current_track.filename);
|
||||
if (bin_filename)
|
||||
{
|
||||
if (cdreader_open_bin(cdrom, bin_filename, current_track.mode))
|
||||
{
|
||||
if (verbose_message_callback)
|
||||
{
|
||||
if (cdrom->track_pregap_sectors)
|
||||
snprintf((char*)buffer, sizeof(buffer), "Opened track %d (sector size %d, %d pregap sectors)",
|
||||
track, cdrom->sector_size, cdrom->track_pregap_sectors);
|
||||
else
|
||||
snprintf((char*)buffer, sizeof(buffer), "Opened track %d (sector size %d)", track, cdrom->sector_size);
|
||||
|
||||
verbose_message_callback((const char*)buffer);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (cdrom->file_handle)
|
||||
{
|
||||
rc_file_close(cdrom->file_handle);
|
||||
snprintf((char*)buffer, sizeof(buffer), "Could not determine sector size for %s track", current_track.mode);
|
||||
}
|
||||
else
|
||||
{
|
||||
snprintf((char*)buffer, sizeof(buffer), "Could not open %s", bin_filename);
|
||||
}
|
||||
|
||||
rc_hash_error((const char*)buffer);
|
||||
|
||||
free(cdrom);
|
||||
cdrom = NULL;
|
||||
}
|
||||
|
||||
free(bin_filename);
|
||||
}
|
||||
}
|
||||
|
||||
return cdrom;
|
||||
}
|
||||
|
||||
static void* cdreader_open_gdi_track(const char* path, uint32_t track)
|
||||
{
|
||||
void* file_handle;
|
||||
char buffer[1024];
|
||||
char mode[16] = "MODE1/";
|
||||
char sector_size[16];
|
||||
char file[256];
|
||||
int64_t track_size;
|
||||
int track_type;
|
||||
char* bin_path = NULL;
|
||||
uint32_t current_track = 0;
|
||||
char* ptr, *ptr2, *end;
|
||||
int lba = 0;
|
||||
|
||||
uint32_t largest_track = 0;
|
||||
int64_t largest_track_size = 0;
|
||||
char largest_track_file[256];
|
||||
char largest_track_sector_size[16];
|
||||
int largest_track_lba = 0;
|
||||
|
||||
int found = 0;
|
||||
size_t num_read = 0;
|
||||
int64_t file_offset = 0;
|
||||
struct cdrom_t* cdrom = NULL;
|
||||
|
||||
file_handle = rc_file_open(path);
|
||||
if (!file_handle)
|
||||
return NULL;
|
||||
|
||||
file[0] = '\0';
|
||||
do
|
||||
{
|
||||
num_read = rc_file_read(file_handle, buffer, sizeof(buffer) - 1);
|
||||
if (num_read == 0)
|
||||
break;
|
||||
|
||||
buffer[num_read] = 0;
|
||||
if (num_read == sizeof(buffer) - 1)
|
||||
end = buffer + sizeof(buffer) * 3 / 4;
|
||||
else
|
||||
end = buffer + num_read;
|
||||
|
||||
ptr = buffer;
|
||||
|
||||
/* the first line contains the number of tracks, so we can get the last track index from it */
|
||||
if (track == RC_HASH_CDTRACK_LAST)
|
||||
track = atoi(ptr);
|
||||
|
||||
/* first line contains the number of tracks and will be skipped */
|
||||
while (ptr < end)
|
||||
{
|
||||
/* skip until next newline */
|
||||
while (*ptr != '\n' && ptr < end)
|
||||
++ptr;
|
||||
|
||||
/* skip newlines */
|
||||
while ((*ptr == '\n' || *ptr == '\r') && ptr < end)
|
||||
++ptr;
|
||||
|
||||
/* line format: [trackid] [lba] [type] [sectorsize] [file] [?] */
|
||||
while (isspace((unsigned char)*ptr))
|
||||
++ptr;
|
||||
|
||||
current_track = (uint32_t)atoi(ptr);
|
||||
if (track && current_track != track && track != RC_HASH_CDTRACK_FIRST_DATA)
|
||||
continue;
|
||||
|
||||
while (isdigit((unsigned char)*ptr))
|
||||
++ptr;
|
||||
++ptr;
|
||||
|
||||
while (isspace((unsigned char)*ptr))
|
||||
++ptr;
|
||||
|
||||
lba = atoi(ptr);
|
||||
while (isdigit((unsigned char)*ptr))
|
||||
++ptr;
|
||||
++ptr;
|
||||
|
||||
while (isspace((unsigned char)*ptr))
|
||||
++ptr;
|
||||
|
||||
track_type = atoi(ptr);
|
||||
while (isdigit((unsigned char)*ptr))
|
||||
++ptr;
|
||||
++ptr;
|
||||
|
||||
while (isspace((unsigned char)*ptr))
|
||||
++ptr;
|
||||
|
||||
ptr2 = sector_size;
|
||||
while (isdigit((unsigned char)*ptr))
|
||||
*ptr2++ = *ptr++;
|
||||
*ptr2 = '\0';
|
||||
++ptr;
|
||||
|
||||
while (isspace((unsigned char)*ptr))
|
||||
++ptr;
|
||||
|
||||
ptr2 = file;
|
||||
if (*ptr == '\"')
|
||||
{
|
||||
++ptr;
|
||||
while (*ptr != '\"')
|
||||
*ptr2++ = *ptr++;
|
||||
++ptr;
|
||||
}
|
||||
else
|
||||
{
|
||||
while (*ptr != ' ')
|
||||
*ptr2++ = *ptr++;
|
||||
}
|
||||
*ptr2 = '\0';
|
||||
|
||||
if (track == current_track)
|
||||
{
|
||||
found = 1;
|
||||
break;
|
||||
}
|
||||
else if (track == RC_HASH_CDTRACK_FIRST_DATA && track_type == 4)
|
||||
{
|
||||
track = current_track;
|
||||
found = 1;
|
||||
break;
|
||||
}
|
||||
else if (track == RC_HASH_CDTRACK_LARGEST && track_type == 4)
|
||||
{
|
||||
track_size = cdreader_get_bin_size(path, file);
|
||||
if (track_size > largest_track_size)
|
||||
{
|
||||
largest_track_size = track_size;
|
||||
largest_track = current_track;
|
||||
largest_track_lba = lba;
|
||||
strcpy_s(largest_track_file, sizeof(largest_track_file), file);
|
||||
strcpy_s(largest_track_sector_size, sizeof(largest_track_sector_size), sector_size);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (found)
|
||||
break;
|
||||
|
||||
file_offset += (ptr - buffer);
|
||||
rc_file_seek(file_handle, file_offset, SEEK_SET);
|
||||
|
||||
} while (1);
|
||||
|
||||
rc_file_close(file_handle);
|
||||
|
||||
cdrom = (struct cdrom_t*)calloc(1, sizeof(*cdrom));
|
||||
if (!cdrom)
|
||||
{
|
||||
snprintf((char*)buffer, sizeof(buffer), "Failed to allocate %u bytes", (unsigned)sizeof(*cdrom));
|
||||
rc_hash_error((const char*)buffer);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* if we were tracking the largest track, make it the current track.
|
||||
* otherwise, current_track will be the requested track, or last track. */
|
||||
if (largest_track != 0 && largest_track != current_track)
|
||||
{
|
||||
current_track = largest_track;
|
||||
strcpy_s(file, sizeof(file), largest_track_file);
|
||||
strcpy_s(sector_size, sizeof(sector_size), largest_track_sector_size);
|
||||
lba = largest_track_lba;
|
||||
}
|
||||
|
||||
/* open the bin file for the track - construct mode parameter from sector_size */
|
||||
ptr = &mode[6];
|
||||
ptr2 = sector_size;
|
||||
while (*ptr2 && *ptr2 != '\"')
|
||||
*ptr++ = *ptr2++;
|
||||
*ptr = '\0';
|
||||
|
||||
bin_path = cdreader_get_bin_path(path, file);
|
||||
if (cdreader_open_bin(cdrom, bin_path, mode))
|
||||
{
|
||||
cdrom->track_pregap_sectors = 0;
|
||||
cdrom->track_first_sector = lba;
|
||||
#ifndef NDEBUG
|
||||
cdrom->track_id = current_track;
|
||||
#endif
|
||||
|
||||
if (verbose_message_callback)
|
||||
{
|
||||
snprintf((char*)buffer, sizeof(buffer), "Opened track %d (sector size %d)", current_track, cdrom->sector_size);
|
||||
verbose_message_callback((const char*)buffer);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
snprintf((char*)buffer, sizeof(buffer), "Could not open %s", bin_path);
|
||||
rc_hash_error((const char*)buffer);
|
||||
|
||||
free(cdrom);
|
||||
cdrom = NULL;
|
||||
}
|
||||
|
||||
free(bin_path);
|
||||
|
||||
return cdrom;
|
||||
}
|
||||
|
||||
static void* cdreader_open_track(const char* path, uint32_t track)
|
||||
{
|
||||
/* backwards compatibility - 0 used to mean largest */
|
||||
if (track == 0)
|
||||
track = RC_HASH_CDTRACK_LARGEST;
|
||||
|
||||
if (rc_path_compare_extension(path, "cue"))
|
||||
return cdreader_open_cue_track(path, track);
|
||||
if (rc_path_compare_extension(path, "gdi"))
|
||||
return cdreader_open_gdi_track(path, track);
|
||||
|
||||
return cdreader_open_bin_track(path, track);
|
||||
}
|
||||
|
||||
static size_t cdreader_read_sector(void* track_handle, uint32_t sector, void* buffer, size_t requested_bytes)
|
||||
{
|
||||
int64_t sector_start;
|
||||
size_t num_read, total_read = 0;
|
||||
uint8_t* buffer_ptr = (uint8_t*)buffer;
|
||||
|
||||
struct cdrom_t* cdrom = (struct cdrom_t*)track_handle;
|
||||
if (!cdrom)
|
||||
return 0;
|
||||
|
||||
if (sector < (uint32_t)cdrom->track_first_sector)
|
||||
return 0;
|
||||
|
||||
sector_start = (int64_t)(sector - cdrom->track_first_sector) * cdrom->sector_size +
|
||||
cdrom->sector_header_size + cdrom->file_track_offset;
|
||||
|
||||
while (requested_bytes > (size_t)cdrom->raw_data_size)
|
||||
{
|
||||
rc_file_seek(cdrom->file_handle, sector_start, SEEK_SET);
|
||||
num_read = rc_file_read(cdrom->file_handle, buffer_ptr, cdrom->raw_data_size);
|
||||
total_read += num_read;
|
||||
|
||||
if (num_read < (size_t)cdrom->raw_data_size)
|
||||
return total_read;
|
||||
|
||||
buffer_ptr += cdrom->raw_data_size;
|
||||
sector_start += cdrom->sector_size;
|
||||
requested_bytes -= cdrom->raw_data_size;
|
||||
}
|
||||
|
||||
rc_file_seek(cdrom->file_handle, sector_start, SEEK_SET);
|
||||
num_read = rc_file_read(cdrom->file_handle, buffer_ptr, (int)requested_bytes);
|
||||
total_read += num_read;
|
||||
|
||||
return total_read;
|
||||
}
|
||||
|
||||
static void cdreader_close_track(void* track_handle)
|
||||
{
|
||||
struct cdrom_t* cdrom = (struct cdrom_t*)track_handle;
|
||||
if (cdrom)
|
||||
{
|
||||
if (cdrom->file_handle)
|
||||
rc_file_close(cdrom->file_handle);
|
||||
|
||||
free(track_handle);
|
||||
}
|
||||
}
|
||||
|
||||
static uint32_t cdreader_first_track_sector(void* track_handle)
|
||||
{
|
||||
struct cdrom_t* cdrom = (struct cdrom_t*)track_handle;
|
||||
if (cdrom)
|
||||
return cdrom->track_first_sector + cdrom->track_pregap_sectors;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void rc_hash_get_default_cdreader(struct rc_hash_cdreader* cdreader)
|
||||
{
|
||||
cdreader->open_track = cdreader_open_track;
|
||||
cdreader->read_sector = cdreader_read_sector;
|
||||
cdreader->close_track = cdreader_close_track;
|
||||
cdreader->first_track_sector = cdreader_first_track_sector;
|
||||
}
|
||||
|
||||
void rc_hash_init_default_cdreader(void)
|
||||
{
|
||||
struct rc_hash_cdreader cdreader;
|
||||
rc_hash_get_default_cdreader(&cdreader);
|
||||
rc_hash_init_custom_cdreader(&cdreader);
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,382 @@
|
|||
/*
|
||||
Copyright (C) 1999, 2000, 2002 Aladdin Enterprises. All rights reserved.
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
|
||||
L. Peter Deutsch
|
||||
ghost@aladdin.com
|
||||
|
||||
*/
|
||||
/* $Id: md5.c,v 1.6 2002/04/13 19:20:28 lpd Exp $ */
|
||||
/*
|
||||
Independent implementation of MD5 (RFC 1321).
|
||||
|
||||
This code implements the MD5 Algorithm defined in RFC 1321, whose
|
||||
text is available at
|
||||
http://www.ietf.org/rfc/rfc1321.txt
|
||||
The code is derived from the text of the RFC, including the test suite
|
||||
(section A.5) but excluding the rest of Appendix A. It does not include
|
||||
any code or documentation that is identified in the RFC as being
|
||||
copyrighted.
|
||||
|
||||
The original and principal author of md5.c is L. Peter Deutsch
|
||||
<ghost@aladdin.com>. Other authors are noted in the change history
|
||||
that follows (in reverse chronological order):
|
||||
|
||||
2002-04-13 lpd Clarified derivation from RFC 1321; now handles byte order
|
||||
either statically or dynamically; added missing #include <string.h>
|
||||
in library.
|
||||
2002-03-11 lpd Corrected argument list for main(), and added int return
|
||||
type, in test program and T value program.
|
||||
2002-02-21 lpd Added missing #include <stdio.h> in test program.
|
||||
2000-07-03 lpd Patched to eliminate warnings about "constant is
|
||||
unsigned in ANSI C, signed in traditional"; made test program
|
||||
self-checking.
|
||||
1999-11-04 lpd Edited comments slightly for automatic TOC extraction.
|
||||
1999-10-18 lpd Fixed typo in header comment (ansi2knr rather than md5).
|
||||
1999-05-03 lpd Original version.
|
||||
*/
|
||||
|
||||
#include "md5.h"
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
|
||||
#undef BYTE_ORDER /* 1 = big-endian, -1 = little-endian, 0 = unknown */
|
||||
#ifdef ARCH_IS_BIG_ENDIAN
|
||||
# define BYTE_ORDER (ARCH_IS_BIG_ENDIAN ? 1 : -1)
|
||||
#else
|
||||
# define BYTE_ORDER 0
|
||||
#endif
|
||||
|
||||
#define T_MASK ((md5_word_t)~0)
|
||||
#define T1 /* 0xd76aa478 */ (T_MASK ^ 0x28955b87)
|
||||
#define T2 /* 0xe8c7b756 */ (T_MASK ^ 0x173848a9)
|
||||
#define T3 0x242070db
|
||||
#define T4 /* 0xc1bdceee */ (T_MASK ^ 0x3e423111)
|
||||
#define T5 /* 0xf57c0faf */ (T_MASK ^ 0x0a83f050)
|
||||
#define T6 0x4787c62a
|
||||
#define T7 /* 0xa8304613 */ (T_MASK ^ 0x57cfb9ec)
|
||||
#define T8 /* 0xfd469501 */ (T_MASK ^ 0x02b96afe)
|
||||
#define T9 0x698098d8
|
||||
#define T10 /* 0x8b44f7af */ (T_MASK ^ 0x74bb0850)
|
||||
#define T11 /* 0xffff5bb1 */ (T_MASK ^ 0x0000a44e)
|
||||
#define T12 /* 0x895cd7be */ (T_MASK ^ 0x76a32841)
|
||||
#define T13 0x6b901122
|
||||
#define T14 /* 0xfd987193 */ (T_MASK ^ 0x02678e6c)
|
||||
#define T15 /* 0xa679438e */ (T_MASK ^ 0x5986bc71)
|
||||
#define T16 0x49b40821
|
||||
#define T17 /* 0xf61e2562 */ (T_MASK ^ 0x09e1da9d)
|
||||
#define T18 /* 0xc040b340 */ (T_MASK ^ 0x3fbf4cbf)
|
||||
#define T19 0x265e5a51
|
||||
#define T20 /* 0xe9b6c7aa */ (T_MASK ^ 0x16493855)
|
||||
#define T21 /* 0xd62f105d */ (T_MASK ^ 0x29d0efa2)
|
||||
#define T22 0x02441453
|
||||
#define T23 /* 0xd8a1e681 */ (T_MASK ^ 0x275e197e)
|
||||
#define T24 /* 0xe7d3fbc8 */ (T_MASK ^ 0x182c0437)
|
||||
#define T25 0x21e1cde6
|
||||
#define T26 /* 0xc33707d6 */ (T_MASK ^ 0x3cc8f829)
|
||||
#define T27 /* 0xf4d50d87 */ (T_MASK ^ 0x0b2af278)
|
||||
#define T28 0x455a14ed
|
||||
#define T29 /* 0xa9e3e905 */ (T_MASK ^ 0x561c16fa)
|
||||
#define T30 /* 0xfcefa3f8 */ (T_MASK ^ 0x03105c07)
|
||||
#define T31 0x676f02d9
|
||||
#define T32 /* 0x8d2a4c8a */ (T_MASK ^ 0x72d5b375)
|
||||
#define T33 /* 0xfffa3942 */ (T_MASK ^ 0x0005c6bd)
|
||||
#define T34 /* 0x8771f681 */ (T_MASK ^ 0x788e097e)
|
||||
#define T35 0x6d9d6122
|
||||
#define T36 /* 0xfde5380c */ (T_MASK ^ 0x021ac7f3)
|
||||
#define T37 /* 0xa4beea44 */ (T_MASK ^ 0x5b4115bb)
|
||||
#define T38 0x4bdecfa9
|
||||
#define T39 /* 0xf6bb4b60 */ (T_MASK ^ 0x0944b49f)
|
||||
#define T40 /* 0xbebfbc70 */ (T_MASK ^ 0x4140438f)
|
||||
#define T41 0x289b7ec6
|
||||
#define T42 /* 0xeaa127fa */ (T_MASK ^ 0x155ed805)
|
||||
#define T43 /* 0xd4ef3085 */ (T_MASK ^ 0x2b10cf7a)
|
||||
#define T44 0x04881d05
|
||||
#define T45 /* 0xd9d4d039 */ (T_MASK ^ 0x262b2fc6)
|
||||
#define T46 /* 0xe6db99e5 */ (T_MASK ^ 0x1924661a)
|
||||
#define T47 0x1fa27cf8
|
||||
#define T48 /* 0xc4ac5665 */ (T_MASK ^ 0x3b53a99a)
|
||||
#define T49 /* 0xf4292244 */ (T_MASK ^ 0x0bd6ddbb)
|
||||
#define T50 0x432aff97
|
||||
#define T51 /* 0xab9423a7 */ (T_MASK ^ 0x546bdc58)
|
||||
#define T52 /* 0xfc93a039 */ (T_MASK ^ 0x036c5fc6)
|
||||
#define T53 0x655b59c3
|
||||
#define T54 /* 0x8f0ccc92 */ (T_MASK ^ 0x70f3336d)
|
||||
#define T55 /* 0xffeff47d */ (T_MASK ^ 0x00100b82)
|
||||
#define T56 /* 0x85845dd1 */ (T_MASK ^ 0x7a7ba22e)
|
||||
#define T57 0x6fa87e4f
|
||||
#define T58 /* 0xfe2ce6e0 */ (T_MASK ^ 0x01d3191f)
|
||||
#define T59 /* 0xa3014314 */ (T_MASK ^ 0x5cfebceb)
|
||||
#define T60 0x4e0811a1
|
||||
#define T61 /* 0xf7537e82 */ (T_MASK ^ 0x08ac817d)
|
||||
#define T62 /* 0xbd3af235 */ (T_MASK ^ 0x42c50dca)
|
||||
#define T63 0x2ad7d2bb
|
||||
#define T64 /* 0xeb86d391 */ (T_MASK ^ 0x14792c6e)
|
||||
|
||||
|
||||
static void
|
||||
md5_process(md5_state_t *pms, const md5_byte_t *data /*[64]*/)
|
||||
{
|
||||
md5_word_t
|
||||
a = pms->abcd[0], b = pms->abcd[1],
|
||||
c = pms->abcd[2], d = pms->abcd[3];
|
||||
md5_word_t t;
|
||||
#if BYTE_ORDER > 0
|
||||
/* Define storage only for big-endian CPUs. */
|
||||
md5_word_t X[16];
|
||||
#else
|
||||
/* Define storage for little-endian or both types of CPUs. */
|
||||
md5_word_t xbuf[16];
|
||||
const md5_word_t *X;
|
||||
#endif
|
||||
|
||||
{
|
||||
#if BYTE_ORDER == 0
|
||||
/*
|
||||
* Determine dynamically whether this is a big-endian or
|
||||
* little-endian machine, since we can use a more efficient
|
||||
* algorithm on the latter.
|
||||
*/
|
||||
static const int w = 1;
|
||||
|
||||
if (*((const md5_byte_t *)&w)) /* dynamic little-endian */
|
||||
#endif
|
||||
#if BYTE_ORDER <= 0 /* little-endian */
|
||||
{
|
||||
/*
|
||||
* On little-endian machines, we can process properly aligned
|
||||
* data without copying it.
|
||||
*/
|
||||
if (!((ptrdiff_t)data & 3)) {
|
||||
/* data are properly aligned */
|
||||
X = (const md5_word_t *)data;
|
||||
} else {
|
||||
/* not aligned */
|
||||
memcpy(xbuf, data, 64);
|
||||
X = xbuf;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#if BYTE_ORDER == 0
|
||||
else /* dynamic big-endian */
|
||||
#endif
|
||||
#if BYTE_ORDER >= 0 /* big-endian */
|
||||
{
|
||||
/*
|
||||
* On big-endian machines, we must arrange the bytes in the
|
||||
* right order.
|
||||
*/
|
||||
const md5_byte_t *xp = data;
|
||||
int i;
|
||||
|
||||
# if BYTE_ORDER == 0
|
||||
X = xbuf; /* (dynamic only) */
|
||||
# else
|
||||
# define xbuf X /* (static only) */
|
||||
# endif
|
||||
for (i = 0; i < 16; ++i, xp += 4)
|
||||
xbuf[i] = xp[0] + (xp[1] << 8) + (xp[2] << 16) + (xp[3] << 24);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#define ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32 - (n))))
|
||||
|
||||
/* Round 1. */
|
||||
/* Let [abcd k s i] denote the operation
|
||||
a = b + ((a + F(b,c,d) + X[k] + T[i]) <<< s). */
|
||||
#define F(x, y, z) (((x) & (y)) | (~(x) & (z)))
|
||||
#define SET(a, b, c, d, k, s, Ti)\
|
||||
t = a + F(b,c,d) + X[k] + Ti;\
|
||||
a = ROTATE_LEFT(t, s) + b
|
||||
/* Do the following 16 operations. */
|
||||
SET(a, b, c, d, 0, 7, T1);
|
||||
SET(d, a, b, c, 1, 12, T2);
|
||||
SET(c, d, a, b, 2, 17, T3);
|
||||
SET(b, c, d, a, 3, 22, T4);
|
||||
SET(a, b, c, d, 4, 7, T5);
|
||||
SET(d, a, b, c, 5, 12, T6);
|
||||
SET(c, d, a, b, 6, 17, T7);
|
||||
SET(b, c, d, a, 7, 22, T8);
|
||||
SET(a, b, c, d, 8, 7, T9);
|
||||
SET(d, a, b, c, 9, 12, T10);
|
||||
SET(c, d, a, b, 10, 17, T11);
|
||||
SET(b, c, d, a, 11, 22, T12);
|
||||
SET(a, b, c, d, 12, 7, T13);
|
||||
SET(d, a, b, c, 13, 12, T14);
|
||||
SET(c, d, a, b, 14, 17, T15);
|
||||
SET(b, c, d, a, 15, 22, T16);
|
||||
#undef SET
|
||||
|
||||
/* Round 2. */
|
||||
/* Let [abcd k s i] denote the operation
|
||||
a = b + ((a + G(b,c,d) + X[k] + T[i]) <<< s). */
|
||||
#define G(x, y, z) (((x) & (z)) | ((y) & ~(z)))
|
||||
#define SET(a, b, c, d, k, s, Ti)\
|
||||
t = a + G(b,c,d) + X[k] + Ti;\
|
||||
a = ROTATE_LEFT(t, s) + b
|
||||
/* Do the following 16 operations. */
|
||||
SET(a, b, c, d, 1, 5, T17);
|
||||
SET(d, a, b, c, 6, 9, T18);
|
||||
SET(c, d, a, b, 11, 14, T19);
|
||||
SET(b, c, d, a, 0, 20, T20);
|
||||
SET(a, b, c, d, 5, 5, T21);
|
||||
SET(d, a, b, c, 10, 9, T22);
|
||||
SET(c, d, a, b, 15, 14, T23);
|
||||
SET(b, c, d, a, 4, 20, T24);
|
||||
SET(a, b, c, d, 9, 5, T25);
|
||||
SET(d, a, b, c, 14, 9, T26);
|
||||
SET(c, d, a, b, 3, 14, T27);
|
||||
SET(b, c, d, a, 8, 20, T28);
|
||||
SET(a, b, c, d, 13, 5, T29);
|
||||
SET(d, a, b, c, 2, 9, T30);
|
||||
SET(c, d, a, b, 7, 14, T31);
|
||||
SET(b, c, d, a, 12, 20, T32);
|
||||
#undef SET
|
||||
|
||||
/* Round 3. */
|
||||
/* Let [abcd k s t] denote the operation
|
||||
a = b + ((a + H(b,c,d) + X[k] + T[i]) <<< s). */
|
||||
#define H(x, y, z) ((x) ^ (y) ^ (z))
|
||||
#define SET(a, b, c, d, k, s, Ti)\
|
||||
t = a + H(b,c,d) + X[k] + Ti;\
|
||||
a = ROTATE_LEFT(t, s) + b
|
||||
/* Do the following 16 operations. */
|
||||
SET(a, b, c, d, 5, 4, T33);
|
||||
SET(d, a, b, c, 8, 11, T34);
|
||||
SET(c, d, a, b, 11, 16, T35);
|
||||
SET(b, c, d, a, 14, 23, T36);
|
||||
SET(a, b, c, d, 1, 4, T37);
|
||||
SET(d, a, b, c, 4, 11, T38);
|
||||
SET(c, d, a, b, 7, 16, T39);
|
||||
SET(b, c, d, a, 10, 23, T40);
|
||||
SET(a, b, c, d, 13, 4, T41);
|
||||
SET(d, a, b, c, 0, 11, T42);
|
||||
SET(c, d, a, b, 3, 16, T43);
|
||||
SET(b, c, d, a, 6, 23, T44);
|
||||
SET(a, b, c, d, 9, 4, T45);
|
||||
SET(d, a, b, c, 12, 11, T46);
|
||||
SET(c, d, a, b, 15, 16, T47);
|
||||
SET(b, c, d, a, 2, 23, T48);
|
||||
#undef SET
|
||||
|
||||
/* Round 4. */
|
||||
/* Let [abcd k s t] denote the operation
|
||||
a = b + ((a + I(b,c,d) + X[k] + T[i]) <<< s). */
|
||||
#define I(x, y, z) ((y) ^ ((x) | ~(z)))
|
||||
#define SET(a, b, c, d, k, s, Ti)\
|
||||
t = a + I(b,c,d) + X[k] + Ti;\
|
||||
a = ROTATE_LEFT(t, s) + b
|
||||
/* Do the following 16 operations. */
|
||||
SET(a, b, c, d, 0, 6, T49);
|
||||
SET(d, a, b, c, 7, 10, T50);
|
||||
SET(c, d, a, b, 14, 15, T51);
|
||||
SET(b, c, d, a, 5, 21, T52);
|
||||
SET(a, b, c, d, 12, 6, T53);
|
||||
SET(d, a, b, c, 3, 10, T54);
|
||||
SET(c, d, a, b, 10, 15, T55);
|
||||
SET(b, c, d, a, 1, 21, T56);
|
||||
SET(a, b, c, d, 8, 6, T57);
|
||||
SET(d, a, b, c, 15, 10, T58);
|
||||
SET(c, d, a, b, 6, 15, T59);
|
||||
SET(b, c, d, a, 13, 21, T60);
|
||||
SET(a, b, c, d, 4, 6, T61);
|
||||
SET(d, a, b, c, 11, 10, T62);
|
||||
SET(c, d, a, b, 2, 15, T63);
|
||||
SET(b, c, d, a, 9, 21, T64);
|
||||
#undef SET
|
||||
|
||||
/* Then perform the following additions. (That is increment each
|
||||
of the four registers by the value it had before this block
|
||||
was started.) */
|
||||
pms->abcd[0] += a;
|
||||
pms->abcd[1] += b;
|
||||
pms->abcd[2] += c;
|
||||
pms->abcd[3] += d;
|
||||
}
|
||||
|
||||
void
|
||||
md5_init(md5_state_t *pms)
|
||||
{
|
||||
pms->count[0] = pms->count[1] = 0;
|
||||
pms->abcd[0] = 0x67452301;
|
||||
pms->abcd[1] = /*0xefcdab89*/ T_MASK ^ 0x10325476;
|
||||
pms->abcd[2] = /*0x98badcfe*/ T_MASK ^ 0x67452301;
|
||||
pms->abcd[3] = 0x10325476;
|
||||
}
|
||||
|
||||
void
|
||||
md5_append(md5_state_t *pms, const md5_byte_t *data, int nbytes)
|
||||
{
|
||||
const md5_byte_t *p = data;
|
||||
int left = nbytes;
|
||||
int offset = (pms->count[0] >> 3) & 63;
|
||||
md5_word_t nbits = (md5_word_t)(nbytes << 3);
|
||||
|
||||
if (nbytes <= 0)
|
||||
return;
|
||||
|
||||
/* Update the message length. */
|
||||
pms->count[1] += nbytes >> 29;
|
||||
pms->count[0] += nbits;
|
||||
if (pms->count[0] < nbits)
|
||||
pms->count[1]++;
|
||||
|
||||
/* Process an initial partial block. */
|
||||
if (offset) {
|
||||
int copy = (offset + nbytes > 64 ? 64 - offset : nbytes);
|
||||
|
||||
memcpy(pms->buf + offset, p, copy);
|
||||
if (offset + copy < 64)
|
||||
return;
|
||||
p += copy;
|
||||
left -= copy;
|
||||
md5_process(pms, pms->buf);
|
||||
}
|
||||
|
||||
/* Process full blocks. */
|
||||
for (; left >= 64; p += 64, left -= 64)
|
||||
md5_process(pms, p);
|
||||
|
||||
/* Process a final partial block. */
|
||||
if (left)
|
||||
memcpy(pms->buf, p, left);
|
||||
}
|
||||
|
||||
void
|
||||
md5_finish(md5_state_t *pms, md5_byte_t digest[16])
|
||||
{
|
||||
static const md5_byte_t pad[64] = {
|
||||
0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
|
||||
};
|
||||
md5_byte_t data[8];
|
||||
int i;
|
||||
|
||||
/* Save the length before padding. */
|
||||
for (i = 0; i < 8; ++i)
|
||||
data[i] = (md5_byte_t)(pms->count[i >> 2] >> ((i & 3) << 3));
|
||||
/* Pad to 56 bytes mod 64. */
|
||||
md5_append(pms, pad, ((55 - (pms->count[0] >> 3)) & 63) + 1);
|
||||
/* Append the length. */
|
||||
md5_append(pms, data, 8);
|
||||
for (i = 0; i < 16; ++i)
|
||||
digest[i] = (md5_byte_t)(pms->abcd[i >> 2] >> ((i & 3) << 3));
|
||||
}
|
|
@ -0,0 +1,91 @@
|
|||
/*
|
||||
Copyright (C) 1999, 2002 Aladdin Enterprises. All rights reserved.
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
|
||||
L. Peter Deutsch
|
||||
ghost@aladdin.com
|
||||
|
||||
*/
|
||||
/* $Id: md5.h,v 1.4 2002/04/13 19:20:28 lpd Exp $ */
|
||||
/*
|
||||
Independent implementation of MD5 (RFC 1321).
|
||||
|
||||
This code implements the MD5 Algorithm defined in RFC 1321, whose
|
||||
text is available at
|
||||
http://www.ietf.org/rfc/rfc1321.txt
|
||||
The code is derived from the text of the RFC, including the test suite
|
||||
(section A.5) but excluding the rest of Appendix A. It does not include
|
||||
any code or documentation that is identified in the RFC as being
|
||||
copyrighted.
|
||||
|
||||
The original and principal author of md5.h is L. Peter Deutsch
|
||||
<ghost@aladdin.com>. Other authors are noted in the change history
|
||||
that follows (in reverse chronological order):
|
||||
|
||||
2002-04-13 lpd Removed support for non-ANSI compilers; removed
|
||||
references to Ghostscript; clarified derivation from RFC 1321;
|
||||
now handles byte order either statically or dynamically.
|
||||
1999-11-04 lpd Edited comments slightly for automatic TOC extraction.
|
||||
1999-10-18 lpd Fixed typo in header comment (ansi2knr rather than md5);
|
||||
added conditionalization for C++ compilation from Martin
|
||||
Purschke <purschke@bnl.gov>.
|
||||
1999-05-03 lpd Original version.
|
||||
*/
|
||||
|
||||
#ifndef md5_INCLUDED
|
||||
# define md5_INCLUDED
|
||||
|
||||
/*
|
||||
* This package supports both compile-time and run-time determination of CPU
|
||||
* byte order. If ARCH_IS_BIG_ENDIAN is defined as 0, the code will be
|
||||
* compiled to run only on little-endian CPUs; if ARCH_IS_BIG_ENDIAN is
|
||||
* defined as non-zero, the code will be compiled to run only on big-endian
|
||||
* CPUs; if ARCH_IS_BIG_ENDIAN is not defined, the code will be compiled to
|
||||
* run on either big- or little-endian CPUs, but will run slightly less
|
||||
* efficiently on either one than if ARCH_IS_BIG_ENDIAN is defined.
|
||||
*/
|
||||
|
||||
typedef unsigned char md5_byte_t; /* 8-bit byte */
|
||||
typedef unsigned int md5_word_t; /* 32-bit word */
|
||||
|
||||
/* Define the state of the MD5 Algorithm. */
|
||||
typedef struct md5_state_s {
|
||||
md5_word_t count[2]; /* message length in bits, lsw first */
|
||||
md5_word_t abcd[4]; /* digest buffer */
|
||||
md5_byte_t buf[64]; /* accumulate block */
|
||||
} md5_state_t;
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C"
|
||||
{
|
||||
#endif
|
||||
|
||||
/* Initialize the algorithm. */
|
||||
void md5_init(md5_state_t *pms);
|
||||
|
||||
/* Append a string to the message. */
|
||||
void md5_append(md5_state_t *pms, const md5_byte_t *data, int nbytes);
|
||||
|
||||
/* Finish the message and return the digest. */
|
||||
void md5_finish(md5_state_t *pms, md5_byte_t digest[16]);
|
||||
|
||||
#ifdef __cplusplus
|
||||
} /* end extern "C" */
|
||||
#endif
|
||||
|
||||
#endif /* md5_INCLUDED */
|
|
@ -0,0 +1,402 @@
|
|||
#include "rc_url.h"
|
||||
|
||||
#include "../rc_compat.h"
|
||||
#include "../rhash/md5.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#if RCHEEVOS_URL_SSL
|
||||
#define RCHEEVOS_URL_PROTOCOL "https"
|
||||
#else
|
||||
#define RCHEEVOS_URL_PROTOCOL "http"
|
||||
#endif
|
||||
|
||||
static int rc_url_encode(char* encoded, size_t len, const char* str) {
|
||||
for (;;) {
|
||||
switch (*str) {
|
||||
case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'g': case 'h': case 'i': case 'j':
|
||||
case 'k': case 'l': case 'm': case 'n': case 'o': case 'p': case 'q': case 'r': case 's': case 't':
|
||||
case 'u': case 'v': case 'w': case 'x': case 'y': case 'z':
|
||||
case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G': case 'H': case 'I': case 'J':
|
||||
case 'K': case 'L': case 'M': case 'N': case 'O': case 'P': case 'Q': case 'R': case 'S': case 'T':
|
||||
case 'U': case 'V': case 'W': case 'X': case 'Y': case 'Z':
|
||||
case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9':
|
||||
case '-': case '_': case '.': case '~':
|
||||
if (len < 2)
|
||||
return -1;
|
||||
|
||||
*encoded++ = *str++;
|
||||
--len;
|
||||
break;
|
||||
|
||||
case ' ':
|
||||
if (len < 2)
|
||||
return -1;
|
||||
|
||||
*encoded++ = '+';
|
||||
++str;
|
||||
--len;
|
||||
break;
|
||||
|
||||
default:
|
||||
if (len < 4)
|
||||
return -1;
|
||||
|
||||
snprintf(encoded, len, "%%%02x", (unsigned char)*str);
|
||||
encoded += 3;
|
||||
++str;
|
||||
len -= 3;
|
||||
break;
|
||||
|
||||
case '\0':
|
||||
*encoded = 0;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int rc_url_award_cheevo(char* buffer, size_t size, const char* user_name, const char* login_token,
|
||||
unsigned cheevo_id, int hardcore, const char* game_hash) {
|
||||
char urle_user_name[64];
|
||||
char urle_login_token[64];
|
||||
int written;
|
||||
|
||||
if (rc_url_encode(urle_user_name, sizeof(urle_user_name), user_name) != 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (rc_url_encode(urle_login_token, sizeof(urle_login_token), login_token) != 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
written = snprintf(
|
||||
buffer,
|
||||
size,
|
||||
RCHEEVOS_URL_PROTOCOL"://retroachievements.org/dorequest.php?r=awardachievement&u=%s&t=%s&a=%u&h=%d",
|
||||
urle_user_name,
|
||||
urle_login_token,
|
||||
cheevo_id,
|
||||
hardcore ? 1 : 0
|
||||
);
|
||||
|
||||
if (game_hash && strlen(game_hash) == 32 && (size - (size_t)written) >= 35) {
|
||||
written += snprintf(buffer + written, size - (size_t)written, "&m=%s", game_hash);
|
||||
}
|
||||
|
||||
return (size_t)written >= size ? -1 : 0;
|
||||
}
|
||||
|
||||
int rc_url_submit_lboard(char* buffer, size_t size, const char* user_name, const char* login_token, unsigned lboard_id, int value) {
|
||||
char urle_user_name[64];
|
||||
char urle_login_token[64];
|
||||
char signature[64];
|
||||
unsigned char hash[16];
|
||||
md5_state_t state;
|
||||
int written;
|
||||
|
||||
if (rc_url_encode(urle_user_name, sizeof(urle_user_name), user_name) != 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (rc_url_encode(urle_login_token, sizeof(urle_login_token), login_token) != 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Evaluate the signature. */
|
||||
snprintf(signature, sizeof(signature), "%u%s%d", lboard_id, user_name, value);
|
||||
md5_init(&state);
|
||||
md5_append(&state, (unsigned char*)signature, (int)strlen(signature));
|
||||
md5_finish(&state, hash);
|
||||
|
||||
written = snprintf(
|
||||
buffer,
|
||||
size,
|
||||
RCHEEVOS_URL_PROTOCOL"://retroachievements.org/dorequest.php?r=submitlbentry&u=%s&t=%s&i=%u&s=%d&v=%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
|
||||
urle_user_name,
|
||||
urle_login_token,
|
||||
lboard_id,
|
||||
value,
|
||||
hash[ 0], hash[ 1], hash[ 2], hash[ 3], hash[ 4], hash[ 5], hash[ 6], hash[ 7],
|
||||
hash[ 8], hash[ 9], hash[10], hash[11],hash[12], hash[13], hash[14], hash[15]
|
||||
);
|
||||
|
||||
return (size_t)written >= size ? -1 : 0;
|
||||
}
|
||||
|
||||
int rc_url_get_gameid(char* buffer, size_t size, const char* hash) {
|
||||
int written = snprintf(
|
||||
buffer,
|
||||
size,
|
||||
RCHEEVOS_URL_PROTOCOL"://retroachievements.org/dorequest.php?r=gameid&m=%s",
|
||||
hash
|
||||
);
|
||||
|
||||
return (size_t)written >= size ? -1 : 0;
|
||||
}
|
||||
|
||||
int rc_url_get_patch(char* buffer, size_t size, const char* user_name, const char* login_token, unsigned gameid) {
|
||||
char urle_user_name[64];
|
||||
char urle_login_token[64];
|
||||
int written;
|
||||
|
||||
if (rc_url_encode(urle_user_name, sizeof(urle_user_name), user_name) != 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (rc_url_encode(urle_login_token, sizeof(urle_login_token), login_token) != 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
written = snprintf(
|
||||
buffer,
|
||||
size,
|
||||
RCHEEVOS_URL_PROTOCOL"://retroachievements.org/dorequest.php?r=patch&u=%s&t=%s&g=%u",
|
||||
urle_user_name,
|
||||
urle_login_token,
|
||||
gameid
|
||||
);
|
||||
|
||||
return (size_t)written >= size ? -1 : 0;
|
||||
}
|
||||
|
||||
int rc_url_get_badge_image(char* buffer, size_t size, const char* badge_name) {
|
||||
int written = snprintf(
|
||||
buffer,
|
||||
size,
|
||||
"http://i.retroachievements.org/Badge/%s",
|
||||
badge_name
|
||||
);
|
||||
|
||||
return (size_t)written >= size ? -1 : 0;
|
||||
}
|
||||
|
||||
int rc_url_login_with_password(char* buffer, size_t size, const char* user_name, const char* password) {
|
||||
char urle_user_name[64];
|
||||
char urle_password[256];
|
||||
int written;
|
||||
|
||||
if (rc_url_encode(urle_user_name, sizeof(urle_user_name), user_name) != 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (rc_url_encode(urle_password, sizeof(urle_password), password) != 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
written = snprintf(
|
||||
buffer,
|
||||
size,
|
||||
RCHEEVOS_URL_PROTOCOL"://retroachievements.org/dorequest.php?r=login&u=%s&p=%s",
|
||||
urle_user_name,
|
||||
urle_password
|
||||
);
|
||||
|
||||
return (size_t)written >= size ? -1 : 0;
|
||||
}
|
||||
|
||||
int rc_url_login_with_token(char* buffer, size_t size, const char* user_name, const char* login_token) {
|
||||
char urle_user_name[64];
|
||||
char urle_login_token[64];
|
||||
int written;
|
||||
|
||||
if (rc_url_encode(urle_user_name, sizeof(urle_user_name), user_name) != 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (rc_url_encode(urle_login_token, sizeof(urle_login_token), login_token) != 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
written = snprintf(
|
||||
buffer,
|
||||
size,
|
||||
RCHEEVOS_URL_PROTOCOL"://retroachievements.org/dorequest.php?r=login&u=%s&t=%s",
|
||||
urle_user_name,
|
||||
urle_login_token
|
||||
);
|
||||
|
||||
return (size_t)written >= size ? -1 : 0;
|
||||
}
|
||||
|
||||
int rc_url_get_unlock_list(char* buffer, size_t size, const char* user_name, const char* login_token, unsigned gameid, int hardcore) {
|
||||
char urle_user_name[64];
|
||||
char urle_login_token[64];
|
||||
int written;
|
||||
|
||||
if (rc_url_encode(urle_user_name, sizeof(urle_user_name), user_name) != 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (rc_url_encode(urle_login_token, sizeof(urle_login_token), login_token) != 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
written = snprintf(
|
||||
buffer,
|
||||
size,
|
||||
RCHEEVOS_URL_PROTOCOL"://retroachievements.org/dorequest.php?r=unlocks&u=%s&t=%s&g=%u&h=%d",
|
||||
urle_user_name,
|
||||
urle_login_token,
|
||||
gameid,
|
||||
hardcore ? 1 : 0
|
||||
);
|
||||
|
||||
return (size_t)written >= size ? -1 : 0;
|
||||
}
|
||||
|
||||
int rc_url_post_playing(char* buffer, size_t size, const char* user_name, const char* login_token, unsigned gameid) {
|
||||
char urle_user_name[64];
|
||||
char urle_login_token[64];
|
||||
int written;
|
||||
|
||||
if (rc_url_encode(urle_user_name, sizeof(urle_user_name), user_name) != 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (rc_url_encode(urle_login_token, sizeof(urle_login_token), login_token) != 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
written = snprintf(
|
||||
buffer,
|
||||
size,
|
||||
RCHEEVOS_URL_PROTOCOL"://retroachievements.org/dorequest.php?r=postactivity&u=%s&t=%s&a=3&m=%u",
|
||||
urle_user_name,
|
||||
urle_login_token,
|
||||
gameid
|
||||
);
|
||||
|
||||
return (size_t)written >= size ? -1 : 0;
|
||||
}
|
||||
|
||||
static int rc_url_append_param_equals(char* buffer, size_t buffer_size, size_t buffer_offset, const char* param)
|
||||
{
|
||||
int written = 0;
|
||||
size_t param_len;
|
||||
|
||||
if (buffer_offset >= buffer_size)
|
||||
return -1;
|
||||
|
||||
if (buffer_offset) {
|
||||
buffer += buffer_offset;
|
||||
buffer_size -= buffer_offset;
|
||||
|
||||
if (buffer[-1] != '?') {
|
||||
*buffer++ = '&';
|
||||
buffer_size--;
|
||||
written = 1;
|
||||
}
|
||||
}
|
||||
|
||||
param_len = strlen(param);
|
||||
if (param_len + 1 >= buffer_size)
|
||||
return -1;
|
||||
memcpy(buffer, param, param_len);
|
||||
buffer[param_len] = '=';
|
||||
|
||||
written += (int)param_len + 1;
|
||||
return written + (int)buffer_offset;
|
||||
}
|
||||
|
||||
static int rc_url_append_unum(char* buffer, size_t buffer_size, size_t* buffer_offset, const char* param, unsigned value)
|
||||
{
|
||||
int written = rc_url_append_param_equals(buffer, buffer_size, *buffer_offset, param);
|
||||
if (written > 0) {
|
||||
char num[16];
|
||||
int chars = snprintf(num, sizeof(num), "%u", value);
|
||||
|
||||
if (chars + written < (int)buffer_size) {
|
||||
memcpy(&buffer[written], num, chars + 1);
|
||||
*buffer_offset = written + chars;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int rc_url_append_str(char* buffer, size_t buffer_size, size_t* buffer_offset, const char* param, const char* value)
|
||||
{
|
||||
int written = rc_url_append_param_equals(buffer, buffer_size, *buffer_offset, param);
|
||||
if (written > 0) {
|
||||
buffer += written;
|
||||
buffer_size -= written;
|
||||
|
||||
if (rc_url_encode(buffer, buffer_size, value) == 0) {
|
||||
written += (int)strlen(buffer);
|
||||
*buffer_offset = written;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int rc_url_build_dorequest(char* url_buffer, size_t url_buffer_size, size_t* buffer_offset,
|
||||
const char* api, const char* user_name)
|
||||
{
|
||||
const char* base_url = RCHEEVOS_URL_PROTOCOL"://retroachievements.org/dorequest.php";
|
||||
size_t written = strlen(base_url);
|
||||
int failure = 0;
|
||||
|
||||
if (url_buffer_size < written + 1)
|
||||
return -1;
|
||||
memcpy(url_buffer, base_url, written);
|
||||
url_buffer[written++] = '?';
|
||||
|
||||
failure |= rc_url_append_str(url_buffer, url_buffer_size, &written, "r", api);
|
||||
if (user_name)
|
||||
failure |= rc_url_append_str(url_buffer, url_buffer_size, &written, "u", user_name);
|
||||
|
||||
*buffer_offset += written;
|
||||
return failure;
|
||||
}
|
||||
|
||||
int rc_url_ping(char* url_buffer, size_t url_buffer_size, char* post_buffer, size_t post_buffer_size,
|
||||
const char* user_name, const char* login_token, unsigned gameid, const char* rich_presence)
|
||||
{
|
||||
size_t written = 0;
|
||||
int failure = rc_url_build_dorequest(url_buffer, url_buffer_size, &written, "ping", user_name);
|
||||
failure |= rc_url_append_unum(url_buffer, url_buffer_size, &written, "g", gameid);
|
||||
|
||||
written = 0;
|
||||
failure |= rc_url_append_str(post_buffer, post_buffer_size, &written, "t", login_token);
|
||||
|
||||
if (rich_presence && *rich_presence)
|
||||
failure |= rc_url_append_str(post_buffer, post_buffer_size, &written, "m", rich_presence);
|
||||
|
||||
if (failure) {
|
||||
if (url_buffer_size)
|
||||
url_buffer[0] = '\0';
|
||||
if (post_buffer_size)
|
||||
post_buffer[0] = '\0';
|
||||
}
|
||||
|
||||
return failure;
|
||||
}
|
||||
|
||||
int rc_url_get_lboard_entries(char* buffer, size_t size, unsigned lboard_id, unsigned first_index, unsigned count)
|
||||
{
|
||||
size_t written = 0;
|
||||
int failure = rc_url_build_dorequest(buffer, size, &written, "lbinfo", NULL);
|
||||
failure |= rc_url_append_unum(buffer, size, &written, "i", lboard_id);
|
||||
if (first_index > 1)
|
||||
failure |= rc_url_append_unum(buffer, size, &written, "o", first_index - 1);
|
||||
failure |= rc_url_append_unum(buffer, size, &written, "c", count);
|
||||
|
||||
return failure;
|
||||
}
|
||||
|
||||
int rc_url_get_lboard_entries_near_user(char* buffer, size_t size, unsigned lboard_id, const char* user_name, unsigned count)
|
||||
{
|
||||
size_t written = 0;
|
||||
int failure = rc_url_build_dorequest(buffer, size, &written, "lbinfo", NULL);
|
||||
failure |= rc_url_append_unum(buffer, size, &written, "i", lboard_id);
|
||||
failure |= rc_url_append_str(buffer, size, &written, "u", user_name);
|
||||
failure |= rc_url_append_unum(buffer, size, &written, "c", count);
|
||||
|
||||
return failure;
|
||||
}
|
||||
|
||||
#undef RCHEEVOS_URL_PROTOCOL
|
|
@ -51,7 +51,7 @@
|
|||
<AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(SolutionDir)3rdparty\rapidyaml\rapidyaml\src</AdditionalIncludeDirectories>
|
||||
<AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(SolutionDir)3rdparty\rapidyaml\rapidyaml\ext\c4core\src</AdditionalIncludeDirectories>
|
||||
<AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(SolutionDir)3rdparty\rapidyaml\rapidyaml\ext\c4core\src\c4\ext\fast_float\include</AdditionalIncludeDirectories>
|
||||
<AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(SolutionDir)3rdparty\rcheevos\rcheevos\include;$(SolutionDir)3rdparty\rainterface</AdditionalIncludeDirectories>
|
||||
<AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(SolutionDir)3rdparty\rcheevos\include;$(SolutionDir)3rdparty\rainterface</AdditionalIncludeDirectories>
|
||||
<AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(SolutionDir)3rdparty\soundtouch\soundtouch</AdditionalIncludeDirectories>
|
||||
<AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(SolutionDir)3rdparty\discord-rpc\include</AdditionalIncludeDirectories>
|
||||
<AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(SolutionDir)3rdparty\jpgd</AdditionalIncludeDirectories>
|
||||
|
|
Loading…
Reference in New Issue