mirror of
https://github.com/PCSX2/pcsx2.git
synced 2026-08-01 03:38:39 +02:00
Compare commits
8
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
626e1b257c | ||
|
|
3973ba6aaf | ||
|
|
74e1dbb27f | ||
|
|
cb9841ac3f | ||
|
|
3468ae1eff | ||
|
|
c637afe7af | ||
|
|
9f27d5889b | ||
|
|
fa50a11eac |
@@ -31,7 +31,7 @@ SDL=SDL3-3.4.12
|
||||
LZ4=1.10.0
|
||||
VULKAN=1.4.328.1
|
||||
ZSTD=1.5.7
|
||||
KDDOCKWIDGETS=2.4.0
|
||||
KDDOCKWIDGETS=2.4.1
|
||||
PLUTOVG=1.3.2
|
||||
PLUTOSVG=0.0.7
|
||||
RAPIDYAML=0.12.1
|
||||
@@ -66,7 +66,7 @@ eee7dea22ed502868017971c86c63c4ed1e6085de0baebfdcc3d3322f00f3eb0 libpng-$LIBPNG
|
||||
13da39edb3a40ed9713ae390ca89faa2f1202c9dda869ef306a8d4383e242bee nv-codec-headers-$NVENC.tar.gz
|
||||
c465aa56757e7746ac707f582b6e2d51546569a4a2488c1172fb543aa5fdfc2c vulkan-sdk-$VULKAN.tar.gz
|
||||
eb33e51f49a15e023950cd7825ca74a4a2b43db8354825ac24fc1b7ee09e6fa3 zstd-$ZSTD.tar.gz
|
||||
51dbf24fe72e43dd7cb9a289d3cab47112010f1a2ed69b6fc8ac0dff31991ed2 KDDockWidgets-$KDDOCKWIDGETS.tar.gz
|
||||
5cd9495d9316cf6cc937bb328a7fd491c3248ef2469cfa42511f1039f6148aca KDDockWidgets-$KDDOCKWIDGETS.tar.gz
|
||||
7bd4e79ce18b1d47517e7e91fbb7cf19d4f01942804a519bc7c0bf32b6325dd5 plutovg-$PLUTOVG.tar.gz
|
||||
78561b571ac224030cdc450ca2986b4de915c2ba7616004a6d71a379bffd15f3 plutosvg-$PLUTOSVG.tar.gz
|
||||
e9efcdd17f86287748793cf21d106e461fcad8d103a3e5a23632afe93828660d rapidyaml-$RAPIDYAML-src.tgz
|
||||
@@ -166,7 +166,7 @@ tar xf "libjpeg-turbo-$LIBJPEGTURBO.tar.gz"
|
||||
cd "libjpeg-turbo-$LIBJPEGTURBO"
|
||||
# On non debian or debian based Linux systems, libjpeg-turbo will set CMAKE_INSTALL_DEFAULT_LIBDIR "lib64" (or libx32)
|
||||
# That will prevent CMake from finding the deps libjpeg later on. if we set CMAKE_INSTALL_DEFAULT_LIBDIR, libjpeg-turbo will leave it as is, so set it to "lib"
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_PREFIX_PATH="$INSTALLDIR" -DCMAKE_INSTALL_PREFIX="$INSTALLDIR" -DENABLE_STATIC=OFF -DENABLE_SHARED=ON -DCMAKE_INSTALL_DEFAULT_LIBDIR="lib" -B build -G Ninja
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_PREFIX_PATH="$INSTALLDIR" -DCMAKE_INSTALL_PREFIX="$INSTALLDIR" -DENABLE_STATIC=OFF -DENABLE_SHARED=ON -DCMAKE_INSTALL_DEFAULT_LIBDIR="lib" -DCMAKE_POSITION_INDEPENDENT_CODE=1 -B build -G Ninja
|
||||
cmake --build build --parallel
|
||||
ninja -C build install
|
||||
cd ..
|
||||
|
||||
@@ -14,8 +14,8 @@
|
||||
{
|
||||
"type": "git",
|
||||
"url": "https://github.com/KDAB/KDDockWidgets.git",
|
||||
"tag": "v2.3.0",
|
||||
"commit": "c38711026e17e34916dd82c6fcbdcc0d2342f541",
|
||||
"tag": "v2.4.1",
|
||||
"commit": "c1d28d25ef5ba077915bcb2b6fa9e14df2a361f8",
|
||||
"disable-submodules": true
|
||||
}
|
||||
],
|
||||
|
||||
@@ -56,7 +56,7 @@ LIBJPEGTURBO=3.2.0
|
||||
LIBWEBP=1.6.0
|
||||
FFMPEG=8.1
|
||||
MOLTENVK=1.4.1
|
||||
KDDOCKWIDGETS=2.4.0
|
||||
KDDOCKWIDGETS=2.4.1
|
||||
PLUTOVG=1.3.2
|
||||
PLUTOSVG=0.0.7
|
||||
RAPIDYAML=0.12.1
|
||||
@@ -103,7 +103,7 @@ eee7dea22ed502868017971c86c63c4ed1e6085de0baebfdcc3d3322f00f3eb0 libpng-$LIBPNG
|
||||
6f30092cef9fb839779646608f4ee14ae3cbac989c47fa05e841b0841f09878e libjpeg-turbo-$LIBJPEGTURBO.tar.gz
|
||||
b072aed6871998cce9b36e7774033105ca29e33632be5b6347f3206898e0756a ffmpeg-$FFMPEG.tar.xz
|
||||
9985f141902a17de818e264d17c1ce334b748e499ee02fcb4703e4dc0038f89c MoltenVK-$MOLTENVK.tar.gz
|
||||
51dbf24fe72e43dd7cb9a289d3cab47112010f1a2ed69b6fc8ac0dff31991ed2 KDDockWidgets-$KDDOCKWIDGETS.tar.gz
|
||||
5cd9495d9316cf6cc937bb328a7fd491c3248ef2469cfa42511f1039f6148aca KDDockWidgets-$KDDOCKWIDGETS.tar.gz
|
||||
7bd4e79ce18b1d47517e7e91fbb7cf19d4f01942804a519bc7c0bf32b6325dd5 plutovg-$PLUTOVG.tar.gz
|
||||
78561b571ac224030cdc450ca2986b4de915c2ba7616004a6d71a379bffd15f3 plutosvg-$PLUTOSVG.tar.gz
|
||||
e9efcdd17f86287748793cf21d106e461fcad8d103a3e5a23632afe93828660d rapidyaml-$RAPIDYAML-src.tgz
|
||||
|
||||
@@ -33,7 +33,7 @@ LIBJPEGTURBO=3.2.0
|
||||
LIBWEBP=1.6.0
|
||||
FFMPEG=8.1
|
||||
MOLTENVK=1.4.1
|
||||
KDDOCKWIDGETS=2.4.0
|
||||
KDDOCKWIDGETS=2.4.1
|
||||
PLUTOVG=1.3.2
|
||||
PLUTOSVG=0.0.7
|
||||
RAPIDYAML=0.12.1
|
||||
@@ -79,7 +79,7 @@ eee7dea22ed502868017971c86c63c4ed1e6085de0baebfdcc3d3322f00f3eb0 libpng-$LIBPNG
|
||||
6f30092cef9fb839779646608f4ee14ae3cbac989c47fa05e841b0841f09878e libjpeg-turbo-$LIBJPEGTURBO.tar.gz
|
||||
b072aed6871998cce9b36e7774033105ca29e33632be5b6347f3206898e0756a ffmpeg-$FFMPEG.tar.xz
|
||||
9985f141902a17de818e264d17c1ce334b748e499ee02fcb4703e4dc0038f89c v$MOLTENVK.tar.gz
|
||||
51dbf24fe72e43dd7cb9a289d3cab47112010f1a2ed69b6fc8ac0dff31991ed2 KDDockWidgets-$KDDOCKWIDGETS.tar.gz
|
||||
5cd9495d9316cf6cc937bb328a7fd491c3248ef2469cfa42511f1039f6148aca KDDockWidgets-$KDDOCKWIDGETS.tar.gz
|
||||
7bd4e79ce18b1d47517e7e91fbb7cf19d4f01942804a519bc7c0bf32b6325dd5 plutovg-$PLUTOVG.tar.gz
|
||||
78561b571ac224030cdc450ca2986b4de915c2ba7616004a6d71a379bffd15f3 plutosvg-$PLUTOSVG.tar.gz
|
||||
e9efcdd17f86287748793cf21d106e461fcad8d103a3e5a23632afe93828660d rapidyaml-$RAPIDYAML-src.tgz
|
||||
|
||||
@@ -69,7 +69,7 @@ set WEBP=1.6.0
|
||||
set ZLIB=1.3.2
|
||||
set ZLIBSHORT=132
|
||||
set ZSTD=1.5.7
|
||||
set KDDOCKWIDGETS=2.4.0
|
||||
set KDDOCKWIDGETS=2.4.1
|
||||
set PLUTOVG=1.3.2
|
||||
set PLUTOSVG=0.0.7
|
||||
set RAPIDYAML=0.12.1
|
||||
@@ -98,7 +98,7 @@ call :downloadfile "%SDL%.zip" "https://libsdl.org/release/%SDL%.zip" 3d4de8967a
|
||||
call :downloadfile "lz4-%LZ4%.zip" "https://github.com/lz4/lz4/archive/refs/tags/v%LZ4%.zip" 3224b4c80f351f194984526ef396f6079bd6332dd9825c72ac0d7a37b3cdc565 || goto error
|
||||
call :downloadfile "zlib%ZLIBSHORT%.zip" "https://github.com/madler/zlib/releases/download/v%ZLIB%/zlib%ZLIBSHORT%.zip" e8bf55f3017aa181690990cb58a994e77885da140609fc8f94abe9b65d2cae28 || goto error
|
||||
call :downloadfile "zstd-%ZSTD%.zip" "https://github.com/facebook/zstd/archive/refs/tags/v%ZSTD%.zip" 7897bc5d620580d9b7cd3539c44b59d78f3657d33663fe97a145e07b4ebd69a4 || goto error
|
||||
call :downloadfile "KDDockWidgets-%KDDOCKWIDGETS%.zip" "https://github.com/KDAB/KDDockWidgets/archive/v%KDDOCKWIDGETS%.zip" 47ddb48197872055f0adf8e90a7235f8a3b795ca1ee3a28ac2c504c673ae3806 || goto error
|
||||
call :downloadfile "KDDockWidgets-%KDDOCKWIDGETS%.zip" "https://github.com/KDAB/KDDockWidgets/archive/v%KDDOCKWIDGETS%.zip" 6f803c533e95687b3cf968ea851fb9b04e253b32e02b5a40a1542cf202dcd9d1 || goto error
|
||||
call :downloadfile "plutovg-%PLUTOVG%.zip" "https://github.com/sammycage/plutovg/archive/v%PLUTOVG%.zip" 4fe4e48f28aa80171b2166d45c0976ab0f21eecedb52cd4c3ef73b5afb48fac9 || goto error
|
||||
call :downloadfile "plutosvg-%PLUTOSVG%.zip" "https://github.com/sammycage/plutosvg/archive/v%PLUTOSVG%.zip" 82dee2c57ad712bdd6d6d81d3e76249d89caa4b5a4214353660fd5adff12201a || goto error
|
||||
call :downloadfile "agility-sdk-%AGILITYSDK%.nupkg" "https://www.nuget.org/api/v2/package/Microsoft.Direct3D.D3D12/%AGILITYSDK%" eb92d90bb23b2ec23410c41d791e41dbdbec942ab946924d1fdcb31eac6f0735 || goto error
|
||||
|
||||
@@ -86,7 +86,7 @@ set WEBP=1.6.0
|
||||
set ZLIB=1.3.2
|
||||
set ZLIBSHORT=132
|
||||
set ZSTD=1.5.7
|
||||
set KDDOCKWIDGETS=2.4.0
|
||||
set KDDOCKWIDGETS=2.4.1
|
||||
set PLUTOVG=1.3.2
|
||||
set PLUTOSVG=0.0.7
|
||||
set RAPIDYAML=0.12.1
|
||||
@@ -126,7 +126,7 @@ call :downloadfile "%SDL%.zip" "https://libsdl.org/release/%SDL%.zip" 3d4de8967a
|
||||
call :downloadfile "lz4-%LZ4%.zip" "https://github.com/lz4/lz4/archive/refs/tags/v%LZ4%.zip" 3224b4c80f351f194984526ef396f6079bd6332dd9825c72ac0d7a37b3cdc565 || goto error
|
||||
call :downloadfile "zlib%ZLIBSHORT%.zip" "https://github.com/madler/zlib/releases/download/v%ZLIB%/zlib%ZLIBSHORT%.zip" e8bf55f3017aa181690990cb58a994e77885da140609fc8f94abe9b65d2cae28 || goto error
|
||||
call :downloadfile "zstd-%ZSTD%.zip" "https://github.com/facebook/zstd/archive/refs/tags/v%ZSTD%.zip" 7897bc5d620580d9b7cd3539c44b59d78f3657d33663fe97a145e07b4ebd69a4 || goto error
|
||||
call :downloadfile "KDDockWidgets-%KDDOCKWIDGETS%.zip" "https://github.com/KDAB/KDDockWidgets/archive/v%KDDOCKWIDGETS%.zip" 47ddb48197872055f0adf8e90a7235f8a3b795ca1ee3a28ac2c504c673ae3806 || goto error
|
||||
call :downloadfile "KDDockWidgets-%KDDOCKWIDGETS%.zip" "https://github.com/KDAB/KDDockWidgets/archive/v%KDDOCKWIDGETS%.zip" 6f803c533e95687b3cf968ea851fb9b04e253b32e02b5a40a1542cf202dcd9d1 || goto error
|
||||
call :downloadfile "plutovg-%PLUTOVG%.zip" "https://github.com/sammycage/plutovg/archive/v%PLUTOVG%.zip" 4fe4e48f28aa80171b2166d45c0976ab0f21eecedb52cd4c3ef73b5afb48fac9 || goto error
|
||||
call :downloadfile "plutosvg-%PLUTOSVG%.zip" "https://github.com/sammycage/plutosvg/archive/v%PLUTOSVG%.zip" 82dee2c57ad712bdd6d6d81d3e76249d89caa4b5a4214353660fd5adff12201a || goto error
|
||||
call :downloadfile "agility-sdk-%AGILITYSDK%.nupkg" "https://www.nuget.org/api/v2/package/Microsoft.Direct3D.D3D12/%AGILITYSDK%" eb92d90bb23b2ec23410c41d791e41dbdbec942ab946924d1fdcb31eac6f0735 || goto error
|
||||
|
||||
+1
@@ -6,4 +6,5 @@ add_library(D3D12MemAlloc
|
||||
target_link_libraries(D3D12MemAlloc PRIVATE Microsoft::DirectX-Headers)
|
||||
|
||||
target_include_directories(D3D12MemAlloc PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include")
|
||||
target_compile_definitions(D3D12MemAlloc PRIVATE "D3D12MA_USING_DIRECTX_HEADERS")
|
||||
disable_compiler_warnings_for_target(D3D12MemAlloc)
|
||||
|
||||
Vendored
-15
@@ -1,15 +0,0 @@
|
||||
# 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
|
||||
@@ -1,65 +0,0 @@
|
||||
# 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/*
|
||||
Vendored
+13
@@ -1,3 +1,16 @@
|
||||
# v12.4.0
|
||||
* add avatar_last_updated field to rc_client_user_t and rc_api_login_response_t
|
||||
* add more fields to fetch_games_list API
|
||||
* add rc_client_begin_fetch_game_list
|
||||
* add extended RAM to memory map for PSP
|
||||
* add memory map for XBOX
|
||||
* add hash generation for .neo files (geolith-specific NeoGeo ROMs)
|
||||
* add validation warning if Mem and Delta conditions exist for same address and value
|
||||
* ignore warning achievements when reporting achievement count in rc_client_get_user_game_summary
|
||||
* fix crash when {recall} exists without a Remember
|
||||
* fix crash when legacy value contains invalid syntax that overflows the conversion buffer
|
||||
* fix handling of SubSource chain starting with a constant
|
||||
|
||||
# v12.3.0
|
||||
* add rc_client_get_next_achievement_info
|
||||
* rc_client image functions will now return RC_INSUFFICENT_BUFFER instead of truncating if buffer is not large enough
|
||||
|
||||
Vendored
-32
@@ -1,32 +0,0 @@
|
||||
// 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")
|
||||
]),
|
||||
]
|
||||
)
|
||||
+49
@@ -84,6 +84,55 @@ RC_EXPORT int RC_CCONV rc_api_process_update_code_note_response(rc_api_update_co
|
||||
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 Code Notes --- */
|
||||
|
||||
typedef struct rc_api_update_code_note_entry_t {
|
||||
/* 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_entry_t;
|
||||
|
||||
/**
|
||||
* API parameters for an update code notes request.
|
||||
*/
|
||||
typedef struct rc_api_update_code_notes_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;
|
||||
/* An array of notes to update */
|
||||
rc_api_update_code_note_entry_t* entries;
|
||||
/* The number of items in the entries array */
|
||||
uint32_t num_entries;
|
||||
}
|
||||
rc_api_update_code_notes_request_t;
|
||||
|
||||
/**
|
||||
* Response data for an update code note request.
|
||||
*/
|
||||
typedef struct rc_api_update_code_notes_response_t {
|
||||
/* The addresses that were successfully updated */
|
||||
uint32_t* updated_addresses;
|
||||
/* The addresses that could not be updated due to permissions errors */
|
||||
uint32_t* access_denied_addresses;
|
||||
/* The number of elements in the updated_addresses array */
|
||||
uint32_t num_updated_addresses;
|
||||
/* The number of elements in the access_denied_addresses array */
|
||||
uint32_t num_access_denied_addresses;
|
||||
|
||||
/* Common server-provided response information */
|
||||
rc_api_response_t response;
|
||||
}
|
||||
rc_api_update_code_notes_response_t;
|
||||
|
||||
RC_EXPORT int RC_CCONV rc_api_init_update_code_notes_request_hosted(rc_api_request_t* request, const rc_api_update_code_notes_request_t* api_params, const rc_api_host_t* host);
|
||||
RC_EXPORT int RC_CCONV rc_api_process_update_code_notes_server_response(rc_api_update_code_notes_response_t* response, const rc_api_server_response_t* server_response);
|
||||
RC_EXPORT void RC_CCONV rc_api_destroy_update_code_notes_response(rc_api_update_code_notes_response_t* response);
|
||||
|
||||
/* --- Update Achievement --- */
|
||||
|
||||
/**
|
||||
|
||||
+18
@@ -164,8 +164,26 @@ rc_api_fetch_games_list_request_t;
|
||||
typedef struct rc_api_game_list_entry_t {
|
||||
/* The unique identifier of the game */
|
||||
uint32_t id;
|
||||
/* The number of achievements in the game */
|
||||
uint32_t num_achievements;
|
||||
/* The number of leaderboards in the game */
|
||||
uint32_t num_leaderboards;
|
||||
/* The number of points in the game */
|
||||
uint32_t points;
|
||||
/* The name of the game */
|
||||
const char* name;
|
||||
/* The image name for the game badge */
|
||||
const char* image_name;
|
||||
/* The URL for the game badge image */
|
||||
const char* image_url;
|
||||
/* An array of supported hashes */
|
||||
const char** supported_hashes;
|
||||
/* An array of unsupported hashes */
|
||||
const char** unsupported_hashes;
|
||||
/* The number of items in the supported_hashes array */
|
||||
uint32_t num_supported_hashes;
|
||||
/* The number of items in the unsupported_hashes array */
|
||||
uint32_t num_unsupported_hashes;
|
||||
}
|
||||
rc_api_game_list_entry_t;
|
||||
|
||||
|
||||
+4
@@ -42,6 +42,8 @@ typedef struct rc_api_login_response_t {
|
||||
const char* display_name;
|
||||
/* A URL to the user's avatar image */
|
||||
const char* avatar_url;
|
||||
/* When the avatar was last updated */
|
||||
time_t avatar_last_updated;
|
||||
|
||||
/* Common server-provided response information */
|
||||
rc_api_response_t response;
|
||||
@@ -187,6 +189,8 @@ typedef struct rc_api_followed_user_t {
|
||||
const char* display_name;
|
||||
/* A URL to the user's avatar image */
|
||||
const char* avatar_url;
|
||||
/* When the avatar was last updated */
|
||||
time_t avatar_last_updated;
|
||||
/* The player's last registered activity */
|
||||
rc_api_followed_user_activity_t recent_activity;
|
||||
/* The current score of the player */
|
||||
|
||||
+57
@@ -190,6 +190,8 @@ typedef struct rc_client_user_t {
|
||||
uint32_t num_unread_messages;
|
||||
/* minimum version: 12.0 */
|
||||
const char* avatar_url;
|
||||
/* minimum version: 12.4 */
|
||||
time_t avatar_last_updated;
|
||||
} rc_client_user_t;
|
||||
|
||||
/**
|
||||
@@ -450,6 +452,61 @@ RC_EXPORT rc_client_async_handle_t* RC_CCONV rc_client_begin_fetch_game_titles(
|
||||
*/
|
||||
RC_EXPORT void RC_CCONV rc_client_destroy_game_title_list(rc_client_game_title_list_t* list);
|
||||
|
||||
/*****************************************************************************\
|
||||
| Fetch Games List |
|
||||
\*****************************************************************************/
|
||||
|
||||
typedef struct rc_client_game_list_entry_t {
|
||||
/* The unique identifier of the game */
|
||||
uint32_t id;
|
||||
/* The number of achievements in the game */
|
||||
uint32_t num_achievements;
|
||||
/* The number of leaderboards in the game */
|
||||
uint32_t num_leaderboards;
|
||||
/* The number of points in the game */
|
||||
uint32_t points;
|
||||
/* The name of the game */
|
||||
const char* name;
|
||||
/* The image name for the game badge */
|
||||
const char* image_name;
|
||||
/* The URL for the game badge image */
|
||||
const char* image_url;
|
||||
/* An array of supported hashes */
|
||||
const char** supported_hashes;
|
||||
/* An array of unsupported hashes */
|
||||
const char** unsupported_hashes;
|
||||
/* The number of items in the supported_hashes array */
|
||||
uint32_t num_supported_hashes;
|
||||
/* The number of items in the unsupported_hashes array */
|
||||
uint32_t num_unsupported_hashes;
|
||||
} rc_client_game_list_entry_t;
|
||||
|
||||
typedef struct rc_client_game_list_t {
|
||||
rc_client_game_list_entry_t* entries;
|
||||
uint32_t num_entries;
|
||||
} rc_client_game_list_t;
|
||||
|
||||
/**
|
||||
* Callback that is fired when a games list request completes. list may be null if the query failed.
|
||||
*/
|
||||
typedef void(RC_CCONV* rc_client_fetch_game_list_callback_t)(int result, const char* error_message,
|
||||
rc_client_game_list_t* list, rc_client_t* client,
|
||||
void* callback_userdata);
|
||||
|
||||
/**
|
||||
* Starts an asynchronous request for all games for the given console.
|
||||
* This request returns the game metadata and supported/unsupported hashes for each game on the console,
|
||||
* described by the rc_client_game_list_entry_t struct. After use, the list should be freed by calling
|
||||
* the rc_client_destroy_game_list() function.
|
||||
*/
|
||||
RC_EXPORT rc_client_async_handle_t* RC_CCONV rc_client_begin_fetch_game_list(
|
||||
rc_client_t* client, uint32_t console_id, rc_client_fetch_game_list_callback_t callback, void* callback_userdata);
|
||||
|
||||
/**
|
||||
* Destroys a previously-allocated result from the rc_client_begin_fetch_game_list() callback.
|
||||
*/
|
||||
RC_EXPORT void RC_CCONV rc_client_destroy_game_list(rc_client_game_list_t* list);
|
||||
|
||||
/*****************************************************************************\
|
||||
| Achievements |
|
||||
\*****************************************************************************/
|
||||
|
||||
Vendored
+2
-1
@@ -49,7 +49,8 @@ enum {
|
||||
RC_INSUFFICIENT_BUFFER = -36,
|
||||
RC_INVALID_VARIABLE_NAME = -37,
|
||||
RC_UNKNOWN_VARIABLE_NAME = -38,
|
||||
RC_NOT_FOUND = -39
|
||||
RC_NOT_FOUND = -39,
|
||||
RC_INVALID_VALUE = -40
|
||||
};
|
||||
|
||||
RC_EXPORT const char* RC_CCONV rc_error_str(int ret);
|
||||
|
||||
+167
-30
@@ -360,7 +360,7 @@ int rc_json_parse_server_response(rc_api_response_t* response, const rc_api_serv
|
||||
if (server_response->http_status_code == RC_API_SERVER_RESPONSE_CLIENT_ERROR ||
|
||||
server_response->http_status_code == RC_API_SERVER_RESPONSE_RETRYABLE_CLIENT_ERROR) {
|
||||
/* client provided error message is passed as the response body */
|
||||
response->error_message = server_response->body;
|
||||
response->error_message = rc_buffer_strncpy(&response->buffer, server_response->body, server_response->body_length);
|
||||
response->succeeded = 0;
|
||||
return RC_NO_RESPONSE;
|
||||
}
|
||||
@@ -467,17 +467,12 @@ static int rc_json_get_array_entry_value(rc_json_field_t* field, rc_json_iterato
|
||||
return 1;
|
||||
}
|
||||
|
||||
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) {
|
||||
rc_json_iterator_t iterator;
|
||||
rc_json_field_t array;
|
||||
rc_json_field_t value;
|
||||
uint32_t* entry;
|
||||
|
||||
memset(&array, 0, sizeof(array));
|
||||
if (!rc_json_get_required_array(num_entries, &array, response, field, field_name))
|
||||
return RC_MISSING_VALUE;
|
||||
|
||||
static int rc_json_get_unum_array(uint32_t** entries, uint32_t* num_entries, rc_api_response_t* response, const rc_json_field_t* array, const char* field_name) {
|
||||
if (*num_entries) {
|
||||
rc_json_iterator_t iterator;
|
||||
rc_json_field_t value;
|
||||
uint32_t* entry;
|
||||
|
||||
*entries = (uint32_t*)rc_buffer_alloc(&response->buffer, *num_entries * sizeof(uint32_t));
|
||||
if (!*entries)
|
||||
return RC_OUT_OF_MEMORY;
|
||||
@@ -485,8 +480,8 @@ int rc_json_get_required_unum_array(uint32_t** entries, uint32_t* num_entries, r
|
||||
value.name = field_name;
|
||||
|
||||
memset(&iterator, 0, sizeof(iterator));
|
||||
iterator.json = array.value_start;
|
||||
iterator.end = array.value_end;
|
||||
iterator.json = array->value_start;
|
||||
iterator.end = array->value_end;
|
||||
|
||||
entry = *entries;
|
||||
while (rc_json_get_array_entry_value(&value, &iterator)) {
|
||||
@@ -503,6 +498,77 @@ int rc_json_get_required_unum_array(uint32_t** entries, uint32_t* num_entries, r
|
||||
return RC_OK;
|
||||
}
|
||||
|
||||
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) {
|
||||
rc_json_field_t array;
|
||||
memset(&array, 0, sizeof(array));
|
||||
|
||||
if (!rc_json_get_required_array(num_entries, &array, response, field, field_name))
|
||||
return RC_MISSING_VALUE;
|
||||
|
||||
return rc_json_get_unum_array(entries, num_entries, response, &array, field_name);
|
||||
}
|
||||
|
||||
int rc_json_get_optional_unum_array(uint32_t** entries, uint32_t* num_entries, rc_api_response_t* response, const rc_json_field_t* field, const char* field_name) {
|
||||
rc_json_field_t array;
|
||||
memset(&array, 0, sizeof(array));
|
||||
|
||||
if (!rc_json_get_optional_array(num_entries, &array, field, field_name))
|
||||
*num_entries = 0;
|
||||
|
||||
return rc_json_get_unum_array(entries, num_entries, response, &array, field_name);
|
||||
}
|
||||
|
||||
static int rc_json_get_string_array(const char*** entries, uint32_t* num_entries, rc_api_response_t* response, const rc_json_field_t* array, const char* field_name) {
|
||||
if (*num_entries) {
|
||||
rc_json_iterator_t iterator;
|
||||
rc_json_field_t value;
|
||||
const char** entry;
|
||||
|
||||
*entries = (const char**)rc_buffer_alloc(&response->buffer, *num_entries * sizeof(const char*));
|
||||
if (!*entries)
|
||||
return RC_OUT_OF_MEMORY;
|
||||
|
||||
value.name = field_name;
|
||||
|
||||
memset(&iterator, 0, sizeof(iterator));
|
||||
iterator.json = array->value_start;
|
||||
iterator.end = array->value_end;
|
||||
|
||||
entry = *entries;
|
||||
while (rc_json_get_array_entry_value(&value, &iterator)) {
|
||||
if (!rc_json_get_string(entry, &response->buffer, &value, field_name))
|
||||
return RC_MISSING_VALUE;
|
||||
|
||||
++entry;
|
||||
}
|
||||
}
|
||||
else {
|
||||
*entries = NULL;
|
||||
}
|
||||
|
||||
return RC_OK;
|
||||
}
|
||||
|
||||
int rc_json_get_required_string_array(const char*** entries, uint32_t* num_entries, rc_api_response_t* response, const rc_json_field_t* field, const char* field_name) {
|
||||
rc_json_field_t array;
|
||||
|
||||
memset(&array, 0, sizeof(array));
|
||||
if (!rc_json_get_required_array(num_entries, &array, response, field, field_name))
|
||||
return RC_MISSING_VALUE;
|
||||
|
||||
return rc_json_get_string_array(entries, num_entries, response, &array, field_name);
|
||||
}
|
||||
|
||||
int rc_json_get_optional_string_array(const char*** entries, uint32_t* num_entries, rc_api_response_t* response, const rc_json_field_t* field, const char* field_name) {
|
||||
rc_json_field_t array;
|
||||
|
||||
memset(&array, 0, sizeof(array));
|
||||
if (!rc_json_get_optional_array(num_entries, &array, field, field_name))
|
||||
*num_entries = 0;
|
||||
|
||||
return rc_json_get_string_array(entries, num_entries, response, &array, 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) {
|
||||
#ifndef NDEBUG
|
||||
if (strcmp(field->name, field_name) != 0)
|
||||
@@ -623,6 +689,12 @@ int rc_json_get_string(const char** out, rc_buffer_t* buffer, const rc_json_fiel
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (len == 0) {
|
||||
/* simple optimization for empty string - don't allocate space */
|
||||
*out = "";
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (len == 4 && memcmp(field->value_start, "null", 4) == 0) {
|
||||
*out = NULL;
|
||||
return 1;
|
||||
@@ -658,7 +730,11 @@ int rc_json_get_string(const char** out, rc_buffer_t* buffer, const rc_json_fiel
|
||||
|
||||
if (*src == 'u') {
|
||||
/* unicode character */
|
||||
uint32_t ucs32_char = rc_json_decode_hex4(src + 1);
|
||||
uint32_t ucs32_char;
|
||||
if (src + 5 >= field->value_end) /* incomplete unicode character */
|
||||
return 0;
|
||||
|
||||
ucs32_char = rc_json_decode_hex4(src + 1);
|
||||
src += 5;
|
||||
|
||||
if (ucs32_char >= 0xD800 && ucs32_char < 0xE000) {
|
||||
@@ -710,7 +786,7 @@ int rc_json_get_string(const char** out, rc_buffer_t* buffer, const rc_json_fiel
|
||||
int rc_json_field_string_matches(const rc_json_field_t* field, const char* text) {
|
||||
int is_quoted = 0;
|
||||
const char* ptr = field->value_start;
|
||||
if (!ptr)
|
||||
if (!ptr || !text)
|
||||
return 0;
|
||||
|
||||
if (*ptr == '"') {
|
||||
@@ -924,7 +1000,7 @@ int rc_json_get_datetime(time_t* out, const rc_json_field_t* field, const char*
|
||||
(void)field_name;
|
||||
#endif
|
||||
|
||||
if (*field->value_start == '\"') {
|
||||
if (field->value_start && *field->value_start == '\"') {
|
||||
memset(&tm, 0, sizeof(tm));
|
||||
if (sscanf_s(field->value_start + 1, "%d-%d-%d %d:%d:%d", /* DB format "2013-10-20 22:12:21" */
|
||||
&tm.tm_year, &tm.tm_mon, &tm.tm_mday, &tm.tm_hour, &tm.tm_min, &tm.tm_sec) == 6 ||
|
||||
@@ -963,6 +1039,61 @@ int rc_json_get_required_datetime(time_t* out, rc_api_response_t* response, cons
|
||||
return rc_json_missing_field(response, field);
|
||||
}
|
||||
|
||||
int rc_json_get_timet(time_t* out, const rc_json_field_t* field, const char* field_name)
|
||||
{
|
||||
const char* src = field->value_start;
|
||||
int64_t value = 0;
|
||||
int negative = 0;
|
||||
|
||||
#ifndef NDEBUG
|
||||
if (strcmp(field->name, field_name) != 0)
|
||||
return 0;
|
||||
#else
|
||||
(void)field_name;
|
||||
#endif
|
||||
|
||||
if (!src) {
|
||||
*out = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* assert: string contains only numerals and an optional sign per rc_json_parse_field */
|
||||
if (*src == '-') {
|
||||
negative = 1;
|
||||
++src;
|
||||
} else if (*src == '+') {
|
||||
++src;
|
||||
} else if (*src < '0' || *src > '9') {
|
||||
*out = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
while (src < field->value_end && *src != '.') {
|
||||
value *= 10;
|
||||
value += *src - '0';
|
||||
++src;
|
||||
}
|
||||
|
||||
if (negative)
|
||||
*out = (time_t)-value;
|
||||
else
|
||||
*out = (time_t)value;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
void rc_json_get_optional_timet(time_t* out, const rc_json_field_t* field, const char* field_name, time_t default_value) {
|
||||
if (!rc_json_get_timet(out, field, field_name))
|
||||
*out = default_value;
|
||||
}
|
||||
|
||||
int rc_json_get_required_timet(time_t* out, rc_api_response_t* response, const rc_json_field_t* field, const char* field_name) {
|
||||
if (rc_json_get_timet(out, field, field_name))
|
||||
return 1;
|
||||
|
||||
return rc_json_missing_field(response, field);
|
||||
}
|
||||
|
||||
int rc_json_get_bool(int* out, const rc_json_field_t* field, const char* field_name) {
|
||||
const char* src = field->value_start;
|
||||
|
||||
@@ -1004,23 +1135,25 @@ int rc_json_get_required_bool(int* out, rc_api_response_t* response, const rc_js
|
||||
}
|
||||
|
||||
void rc_json_extract_filename(rc_json_field_t* field) {
|
||||
if (field->value_end) {
|
||||
if (field->value_end && field->value_end > field->value_start) {
|
||||
const char* str = field->value_end;
|
||||
if (str[-1] == '"') {
|
||||
/* ignore trailing quote */
|
||||
field->value_end = --str;
|
||||
|
||||
/* remove the extension */
|
||||
while (str > field->value_start && str[-1] != '/') {
|
||||
--str;
|
||||
if (*str == '.') {
|
||||
field->value_end = str;
|
||||
break;
|
||||
while (str > field->value_start) {
|
||||
const char c = *(--str);
|
||||
if (c == '.') {
|
||||
/* found an extension. remove it */
|
||||
field->value_end = str;
|
||||
}
|
||||
else if (c == '/' || c == '"') {
|
||||
/* found path separator or opening quote. stop */
|
||||
field->value_start = str + 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* find the path separator */
|
||||
while (str > field->value_start && str[-1] != '/')
|
||||
--str;
|
||||
|
||||
field->value_start = str;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1296,12 +1429,15 @@ int rc_api_init_fetch_image_request(rc_api_request_t* request, const rc_api_fetc
|
||||
int rc_api_init_fetch_image_request_hosted(rc_api_request_t* request, const rc_api_fetch_image_request_t* api_params, const rc_api_host_t* host) {
|
||||
rc_api_url_builder_t builder;
|
||||
|
||||
if (!api_params->image_name || !api_params->image_name[0])
|
||||
return RC_INVALID_STATE;
|
||||
|
||||
rc_buffer_init(&request->buffer);
|
||||
rc_url_builder_init(&builder, &request->buffer, 64);
|
||||
|
||||
if (host && host->media_host) {
|
||||
/* custom media host provided */
|
||||
if (!strstr(host->host, "://"))
|
||||
if (!strstr(host->media_host, "://"))
|
||||
rc_url_builder_append(&builder, "http://", 7);
|
||||
rc_url_builder_append(&builder, host->media_host, strlen(host->media_host));
|
||||
}
|
||||
@@ -1358,6 +1494,7 @@ int rc_api_init_fetch_image_request_hosted(rc_api_request_t* request, const rc_a
|
||||
|
||||
request->url = rc_url_builder_finalize(&builder);
|
||||
request->post_data = NULL;
|
||||
request->content_type = NULL;
|
||||
|
||||
return builder.result;
|
||||
}
|
||||
|
||||
+6
@@ -49,21 +49,27 @@ int rc_json_get_num(int32_t* out, const rc_json_field_t* field, const char* fiel
|
||||
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_timet(time_t* 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);
|
||||
void rc_json_get_optional_timet(time_t* out, const rc_json_field_t* field, const char* field_name, time_t 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_timet(time_t* out, rc_api_response_t* response, const rc_json_field_t* field, const char* field_name);
|
||||
int rc_json_get_required_datetime(time_t* out, rc_api_response_t* response, const rc_json_field_t* field, const char* field_name);
|
||||
int rc_json_get_required_object(rc_json_field_t* fields, size_t field_count, rc_api_response_t* response, rc_json_field_t* field, const char* field_name);
|
||||
int rc_json_get_required_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_optional_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_string_array(const char*** entries, uint32_t* num_entries, rc_api_response_t* response, const rc_json_field_t* field, const char* field_name);
|
||||
int rc_json_get_optional_string_array(const char*** 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);
|
||||
|
||||
+106
-6
@@ -181,11 +181,6 @@ int rc_api_process_update_code_note_server_response(rc_api_update_code_note_resp
|
||||
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));
|
||||
@@ -202,6 +197,111 @@ void rc_api_destroy_update_code_note_response(rc_api_update_code_note_response_t
|
||||
rc_buffer_destroy(&response->response.buffer);
|
||||
}
|
||||
|
||||
/* --- Update Code Notes --- */
|
||||
|
||||
int rc_api_init_update_code_notes_request_hosted(rc_api_request_t* request,
|
||||
const rc_api_update_code_notes_request_t* api_params,
|
||||
const rc_api_host_t* host) {
|
||||
rc_api_update_code_note_entry_t* entry;
|
||||
rc_api_update_code_note_entry_t* stop;
|
||||
rc_api_url_builder_t builder;
|
||||
char buffer[512];
|
||||
|
||||
rc_api_url_build_dorequest_url(request, host);
|
||||
|
||||
if (api_params->game_id == 0 || api_params->num_entries == 0)
|
||||
return RC_INVALID_STATE;
|
||||
|
||||
rc_url_builder_init(&builder, &request->buffer, 128);
|
||||
if (!rc_api_url_build_dorequest(&builder, "submitcodenotes", 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_str_param(&builder, "n", "");
|
||||
|
||||
entry = api_params->entries;
|
||||
stop = entry + api_params->num_entries;
|
||||
for (; entry < stop; ++entry) {
|
||||
int chars = snprintf(buffer, sizeof(buffer), "%u", entry->address);
|
||||
rc_url_builder_append(&builder, buffer, chars);
|
||||
rc_url_builder_append(&builder, ":", 1);
|
||||
|
||||
if (entry->note) {
|
||||
const char* src = entry->note;
|
||||
char* dst = buffer;
|
||||
const char* dst_stop = buffer + sizeof(buffer) - 8;
|
||||
do {
|
||||
while (*src && dst < dst_stop) {
|
||||
switch (*src) {
|
||||
case '\n': /* escape newline as newlines separate notes. normalize to windows style line endings */
|
||||
*dst++ = '\\';
|
||||
*dst++ = 'r';
|
||||
*dst++ = '\\';
|
||||
*dst++ = 'n';
|
||||
++src;
|
||||
break;
|
||||
case '\r': /* swallow line feed - one will be explicitly generated with the \n */
|
||||
++src;
|
||||
break;
|
||||
case '\\': /* escape backslashes so raw backslashes don't grab the next character */
|
||||
*dst++ = '\\';
|
||||
*dst++ = '\\';
|
||||
++src;
|
||||
break;
|
||||
default:
|
||||
*dst++ = *src++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* terminate and reset output pointer to front of buffer */
|
||||
*dst = '\0';
|
||||
dst = buffer;
|
||||
|
||||
/* url encode the normalized/escaped note */
|
||||
rc_url_builder_append_encoded_str(&builder, buffer);
|
||||
} while (*src);
|
||||
}
|
||||
|
||||
rc_url_builder_append_encoded_str(&builder, "\n");
|
||||
}
|
||||
|
||||
request->post_data = rc_url_builder_finalize(&builder);
|
||||
request->content_type = RC_CONTENT_TYPE_URLENCODED;
|
||||
|
||||
return builder.result;
|
||||
}
|
||||
|
||||
int rc_api_process_update_code_notes_server_response(rc_api_update_code_notes_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("SuccessfulAddresses"),
|
||||
RC_JSON_NEW_FIELD("AccessDeniedAddresses")
|
||||
};
|
||||
|
||||
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 || result == RC_ACCESS_DENIED) {
|
||||
int result2 = rc_json_get_required_unum_array(&response->updated_addresses, &response->num_updated_addresses, &response->response, &fields[3], "SuccessfulAddresses");
|
||||
if (result2 != RC_OK)
|
||||
return RC_MISSING_VALUE;
|
||||
|
||||
rc_json_get_optional_unum_array(&response->access_denied_addresses, &response->num_access_denied_addresses, &response->response, &fields[4], "AccessDeniedAddresses");
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void rc_api_destroy_update_code_notes_response(rc_api_update_code_notes_response_t* response) {
|
||||
rc_buffer_destroy(&response->response.buffer);
|
||||
}
|
||||
|
||||
/* --- Update Achievement --- */
|
||||
|
||||
static const char* rc_type_string(uint32_t type) {
|
||||
@@ -456,7 +556,7 @@ int rc_api_process_update_rich_presence_server_response(rc_api_update_rich_prese
|
||||
rc_json_field_t fields[] = {
|
||||
RC_JSON_NEW_FIELD("Success"),
|
||||
RC_JSON_NEW_FIELD("Error"),
|
||||
RC_JSON_NEW_FIELD("Code"),
|
||||
RC_JSON_NEW_FIELD("Code")
|
||||
};
|
||||
|
||||
memset(response, 0, sizeof(*response));
|
||||
|
||||
+55
-30
@@ -56,7 +56,6 @@ int rc_api_process_fetch_achievement_info_server_response(rc_api_fetch_achieveme
|
||||
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[] = {
|
||||
@@ -120,9 +119,8 @@ int rc_api_process_fetch_achievement_info_server_response(rc_api_fetch_achieveme
|
||||
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"))
|
||||
if (!rc_json_get_required_timet(&entry->awarded, &response->response, &entry_fields[1], "DateAwarded"))
|
||||
return RC_MISSING_VALUE;
|
||||
entry->awarded = (time_t)timet;
|
||||
|
||||
rc_json_get_optional_string(&entry->avatar_url, &response->response, &entry_fields[2], "AvatarUrl", NULL);
|
||||
if (!entry->avatar_url)
|
||||
@@ -185,7 +183,6 @@ int rc_api_process_fetch_leaderboard_info_server_response(rc_api_fetch_leaderboa
|
||||
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];
|
||||
@@ -289,9 +286,8 @@ int rc_api_process_fetch_leaderboard_info_server_response(rc_api_fetch_leaderboa
|
||||
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"))
|
||||
if (!rc_json_get_required_timet(&entry->submitted, &response->response, &entry_fields[4], "DateSubmitted"))
|
||||
return RC_MISSING_VALUE;
|
||||
entry->submitted = (time_t)timet;
|
||||
|
||||
rc_json_get_optional_string(&entry->avatar_url, &response->response, &entry_fields[5], "AvatarUrl", NULL);
|
||||
if (!entry->avatar_url)
|
||||
@@ -325,8 +321,8 @@ int rc_api_init_fetch_games_list_request_hosted(rc_api_request_t* request,
|
||||
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);
|
||||
rc_url_builder_append_str_param(&builder, "r", "systemgames");
|
||||
rc_url_builder_append_unum_param(&builder, "s", api_params->console_id);
|
||||
|
||||
request->post_data = rc_url_builder_finalize(&builder);
|
||||
request->content_type = RC_CONTENT_TYPE_URLENCODED;
|
||||
@@ -347,9 +343,8 @@ int rc_api_process_fetch_games_list_response(rc_api_fetch_games_list_response_t*
|
||||
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;
|
||||
rc_json_field_t array_field;
|
||||
int result;
|
||||
char* end;
|
||||
|
||||
rc_json_field_t fields[] = {
|
||||
RC_JSON_NEW_FIELD("Success"),
|
||||
@@ -357,6 +352,18 @@ int rc_api_process_fetch_games_list_server_response(rc_api_fetch_games_list_resp
|
||||
RC_JSON_NEW_FIELD("Response")
|
||||
};
|
||||
|
||||
rc_json_field_t game_fields[] = {
|
||||
RC_JSON_NEW_FIELD("ID"),
|
||||
RC_JSON_NEW_FIELD("Title"),
|
||||
RC_JSON_NEW_FIELD("ImageIcon"),
|
||||
RC_JSON_NEW_FIELD("ImageUrl"),
|
||||
RC_JSON_NEW_FIELD("NumAchievements"),
|
||||
RC_JSON_NEW_FIELD("NumLeaderboards"),
|
||||
RC_JSON_NEW_FIELD("Points"),
|
||||
RC_JSON_NEW_FIELD("SupportedHashes"), /* array */
|
||||
RC_JSON_NEW_FIELD("UnsupportedHashes"), /* array */
|
||||
};
|
||||
|
||||
memset(response, 0, sizeof(*response));
|
||||
rc_buffer_init(&response->response.buffer);
|
||||
|
||||
@@ -364,32 +371,50 @@ int rc_api_process_fetch_games_list_server_response(rc_api_fetch_games_list_resp
|
||||
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");
|
||||
if (!rc_json_get_required_array(&response->num_entries, &array_field, &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)));
|
||||
if (response->num_entries) {
|
||||
/* 8=image_name, 32=title, 64=image_url, 32=one hash */
|
||||
rc_buffer_reserve(&response->response.buffer, response->num_entries * (8 + 32 + 64 + 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;
|
||||
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;
|
||||
memset(&iterator, 0, sizeof(iterator));
|
||||
iterator.json = array_field.value_start;
|
||||
iterator.end = array_field.value_end;
|
||||
|
||||
entry = response->entries;
|
||||
while (rc_json_get_next_object_field(&iterator, &field)) {
|
||||
entry->id = strtol(field.name, &end, 10);
|
||||
entry = response->entries;
|
||||
while (rc_json_get_array_entry_object(game_fields, sizeof(game_fields) / sizeof(game_fields[0]), &iterator)) {
|
||||
if (!rc_json_get_required_unum(&entry->id, &response->response, &game_fields[0], "ID"))
|
||||
return RC_MISSING_VALUE;
|
||||
if (!rc_json_get_required_string(&entry->name, &response->response, &game_fields[1], "Title"))
|
||||
return RC_MISSING_VALUE;
|
||||
if (!rc_json_get_required_unum(&entry->num_achievements, &response->response, &game_fields[4], "NumAchievements"))
|
||||
return RC_MISSING_VALUE;
|
||||
if (!rc_json_get_required_unum(&entry->num_leaderboards, &response->response, &game_fields[5], "NumLeaderboards"))
|
||||
return RC_MISSING_VALUE;
|
||||
if (!rc_json_get_required_unum(&entry->points, &response->response, &game_fields[6], "Points"))
|
||||
return RC_MISSING_VALUE;
|
||||
|
||||
field.name = "";
|
||||
if (!rc_json_get_string(&entry->name, &response->response.buffer, &field, ""))
|
||||
return RC_MISSING_VALUE;
|
||||
/* ImageIcon will be '/Images/0123456.png' - only return the '0123456' */
|
||||
rc_json_extract_filename(&game_fields[2]);
|
||||
if (!rc_json_get_required_string(&entry->image_name, &response->response, &game_fields[2], "ImageIcon"))
|
||||
return RC_MISSING_VALUE;
|
||||
if (!rc_json_get_required_string(&entry->image_url, &response->response, &game_fields[3], "ImageUrl"))
|
||||
return RC_MISSING_VALUE;
|
||||
|
||||
++entry;
|
||||
result = rc_json_get_required_string_array(&entry->supported_hashes, &entry->num_supported_hashes, &response->response, &game_fields[7], "SupportedHashes");
|
||||
if (result != RC_OK)
|
||||
return result;
|
||||
result = rc_json_get_optional_string_array(&entry->unsupported_hashes, &entry->num_unsupported_hashes, &response->response, &game_fields[8], "UnsupportedHashes");
|
||||
if (result != RC_OK)
|
||||
return result;
|
||||
|
||||
++entry;
|
||||
}
|
||||
}
|
||||
|
||||
return RC_OK;
|
||||
@@ -484,7 +509,7 @@ int rc_api_process_fetch_game_titles_server_response(rc_api_fetch_game_titles_re
|
||||
return RC_MISSING_VALUE;
|
||||
|
||||
rc_json_get_optional_string(&entry->image_url, &response->response, &entry_fields[3], "ImageUrl", "");
|
||||
if (!entry->image_url[0])
|
||||
if (!entry->image_url || !entry->image_url[0])
|
||||
entry->image_url = rc_api_build_avatar_url(&response->response.buffer, RC_IMAGE_TYPE_GAME, entry->image_name);
|
||||
|
||||
++entry;
|
||||
|
||||
+5
-8
@@ -113,7 +113,6 @@ static int rc_api_process_fetch_game_data_achievements(rc_api_response_t* respon
|
||||
const char* last_author = "";
|
||||
const char* last_author_field = "";
|
||||
size_t last_author_len = 0;
|
||||
uint32_t timet;
|
||||
size_t len;
|
||||
|
||||
rc_json_field_t achievement_fields[] = {
|
||||
@@ -155,11 +154,11 @@ static int rc_api_process_fetch_game_data_achievements(rc_api_response_t* respon
|
||||
return RC_MISSING_VALUE;
|
||||
|
||||
rc_json_get_optional_string(&achievement->badge_url, response, &achievement_fields[13], "BadgeURL", "");
|
||||
if (!achievement->badge_url[0])
|
||||
if (!achievement->badge_url || !achievement->badge_url[0])
|
||||
achievement->badge_url = rc_api_build_avatar_url(&response->buffer, RC_IMAGE_TYPE_ACHIEVEMENT, achievement->badge_name);
|
||||
|
||||
rc_json_get_optional_string(&achievement->badge_locked_url, response, &achievement_fields[14], "BadgeLockedURL", "");
|
||||
if (!achievement->badge_locked_url[0])
|
||||
if (!achievement->badge_locked_url || !achievement->badge_locked_url[0])
|
||||
achievement->badge_locked_url = rc_api_build_avatar_url(&response->buffer, RC_IMAGE_TYPE_ACHIEVEMENT_LOCKED, achievement->badge_name);
|
||||
|
||||
len = achievement_fields[6].value_end - achievement_fields[6].value_start;
|
||||
@@ -181,12 +180,10 @@ static int rc_api_process_fetch_game_data_achievements(rc_api_response_t* respon
|
||||
}
|
||||
}
|
||||
|
||||
if (!rc_json_get_required_unum(&timet, response, &achievement_fields[8], "Created"))
|
||||
if (!rc_json_get_required_timet(&achievement->created, response, &achievement_fields[8], "Created"))
|
||||
return RC_MISSING_VALUE;
|
||||
achievement->created = (time_t)timet;
|
||||
if (!rc_json_get_required_unum(&timet, response, &achievement_fields[9], "Modified"))
|
||||
if (!rc_json_get_required_timet(&achievement->updated, response, &achievement_fields[9], "Modified"))
|
||||
return RC_MISSING_VALUE;
|
||||
achievement->updated = (time_t)timet;
|
||||
|
||||
if (rc_json_field_string_matches(&achievement_fields[10], ""))
|
||||
achievement->type = RC_ACHIEVEMENT_TYPE_STANDARD;
|
||||
@@ -320,7 +317,7 @@ int rc_api_process_fetch_game_data_server_response(rc_api_fetch_game_data_respon
|
||||
rc_json_extract_filename(&patchdata_fields[3]);
|
||||
rc_json_get_optional_string(&response->image_name, &response->response, &patchdata_fields[3], "ImageIcon", "");
|
||||
rc_json_get_optional_string(&response->image_url, &response->response, &patchdata_fields[4], "ImageIconURL", "");
|
||||
if (!response->image_url[0])
|
||||
if (!response->image_url || !response->image_url[0])
|
||||
response->image_url = rc_api_build_avatar_url(&response->response.buffer, RC_IMAGE_TYPE_GAME, response->image_name);
|
||||
|
||||
/* estimate the amount of space necessary to store the rich presence script, achievements, and leaderboards.
|
||||
|
||||
+12
-14
@@ -62,7 +62,8 @@ int rc_api_process_login_server_response(rc_api_login_response_t* response, cons
|
||||
RC_JSON_NEW_FIELD("Score"),
|
||||
RC_JSON_NEW_FIELD("SoftcoreScore"),
|
||||
RC_JSON_NEW_FIELD("Messages"),
|
||||
RC_JSON_NEW_FIELD("AvatarUrl")
|
||||
RC_JSON_NEW_FIELD("AvatarUrl"),
|
||||
RC_JSON_NEW_FIELD("AvatarUpdatedAt")
|
||||
};
|
||||
|
||||
memset(response, 0, sizeof(*response));
|
||||
@@ -90,6 +91,8 @@ int rc_api_process_login_server_response(rc_api_login_response_t* response, cons
|
||||
if (!response->avatar_url)
|
||||
response->avatar_url = rc_api_build_avatar_url(&response->response.buffer, RC_IMAGE_TYPE_USER, response->username);
|
||||
|
||||
rc_json_get_optional_timet(&response->avatar_last_updated, &fields[9], "AvatarUpdatedAt", 0);
|
||||
|
||||
return RC_OK;
|
||||
}
|
||||
|
||||
@@ -145,7 +148,6 @@ int rc_api_process_start_session_server_response(rc_api_start_session_response_t
|
||||
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[] = {
|
||||
@@ -181,9 +183,8 @@ int rc_api_process_start_session_server_response(rc_api_start_session_response_t
|
||||
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"))
|
||||
if (!rc_json_get_required_timet(&unlock->when, &response->response, &unlock_entry_fields[1], "When"))
|
||||
return RC_MISSING_VALUE;
|
||||
unlock->when = (time_t)timet;
|
||||
|
||||
++unlock;
|
||||
}
|
||||
@@ -202,16 +203,14 @@ int rc_api_process_start_session_server_response(rc_api_start_session_response_t
|
||||
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"))
|
||||
if (!rc_json_get_required_timet(&unlock->when, &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;
|
||||
rc_json_get_optional_timet(&response->server_now, &fields[4], "ServerNow", 0);
|
||||
|
||||
return RC_OK;
|
||||
}
|
||||
@@ -307,11 +306,11 @@ int rc_api_process_fetch_followed_users_server_response(rc_api_fetch_followed_us
|
||||
rc_json_field_t array_field;
|
||||
rc_json_iterator_t iterator;
|
||||
rc_api_followed_user_t* user;
|
||||
uint32_t timet;
|
||||
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("Friends")
|
||||
};
|
||||
|
||||
@@ -324,6 +323,7 @@ int rc_api_process_fetch_followed_users_server_response(rc_api_fetch_followed_us
|
||||
RC_JSON_NEW_FIELD("LastGameId"),
|
||||
RC_JSON_NEW_FIELD("LastGameTitle"),
|
||||
RC_JSON_NEW_FIELD("LastGameIconUrl"),
|
||||
RC_JSON_NEW_FIELD("AvatarUpdatedAt"),
|
||||
};
|
||||
|
||||
memset(response, 0, sizeof(*response));
|
||||
@@ -333,7 +333,7 @@ int rc_api_process_fetch_followed_users_server_response(rc_api_fetch_followed_us
|
||||
if (result != RC_OK || !response->response.succeeded)
|
||||
return result;
|
||||
|
||||
if (!rc_json_get_required_array(&response->num_users, &array_field, &response->response, &fields[2], "Friends"))
|
||||
if (!rc_json_get_required_array(&response->num_users, &array_field, &response->response, &fields[3], "Friends"))
|
||||
return RC_MISSING_VALUE;
|
||||
|
||||
if (response->num_users) {
|
||||
@@ -354,11 +354,9 @@ int rc_api_process_fetch_followed_users_server_response(rc_api_fetch_followed_us
|
||||
if (!rc_json_get_required_unum(&user->score, &response->response, &followed_user_entry_fields[2], "RAPoints"))
|
||||
return RC_MISSING_VALUE;
|
||||
|
||||
rc_json_get_optional_timet(&user->avatar_last_updated, &followed_user_entry_fields[8], "AvatarUpdatedAt", 0);
|
||||
rc_json_get_optional_string(&user->recent_activity.description, &response->response, &followed_user_entry_fields[3], "LastSeen", NULL);
|
||||
|
||||
rc_json_get_optional_unum(&timet, &followed_user_entry_fields[4], "LastSeenTime", 0);
|
||||
user->recent_activity.when = (time_t)timet;
|
||||
|
||||
rc_json_get_optional_timet(&user->recent_activity.when, &followed_user_entry_fields[4], "LastSeenTime", 0);
|
||||
rc_json_get_optional_unum(&user->recent_activity.context_id, &followed_user_entry_fields[5], "LastGameId", 0);
|
||||
rc_json_get_optional_string(&user->recent_activity.context, &response->response, &followed_user_entry_fields[6], "LastGameTitle", NULL);
|
||||
rc_json_get_optional_string(&user->recent_activity.context_image_url, &response->response, &followed_user_entry_fields[7], "LastGameIconUrl", NULL);
|
||||
|
||||
Vendored
+223
-12
@@ -14,15 +14,15 @@
|
||||
#include <stdarg.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
#include <profileapi.h>
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
#else
|
||||
#include <time.h>
|
||||
#include <time.h>
|
||||
#endif
|
||||
|
||||
#define RC_CLIENT_UNKNOWN_GAME_ID (uint32_t)-1
|
||||
#define RC_CLIENT_RECENT_UNLOCK_DELAY_SECONDS (10 * 60) /* ten minutes */
|
||||
#define RC_CLIENT_ACHIEVEMENT_WARNING_ID 101000001
|
||||
|
||||
#define RC_MINIMUM_UNPAUSED_FRAMES 20
|
||||
#define RC_PAUSE_DECAY_MULTIPLIER 4
|
||||
@@ -240,7 +240,7 @@ static void rc_client_log_message_va(const rc_client_t* client, const char* form
|
||||
char buffer[2048];
|
||||
|
||||
#ifdef __STDC_SECURE_LIB__
|
||||
vsprintf_s(buffer, sizeof(buffer), format, args);
|
||||
vsnprintf_s(buffer, sizeof(buffer), _TRUNCATE, format, args);
|
||||
#elif __STDC_VERSION__ >= 199901L /* vsnprintf requires c99 */
|
||||
vsnprintf(buffer, sizeof(buffer), format, args);
|
||||
#else /* c89 doesn't have a size-limited vsprintf function - assume the buffer is large enough */
|
||||
@@ -320,6 +320,22 @@ void rc_client_enable_logging(rc_client_t* client, int level, rc_client_message_
|
||||
|
||||
static rc_clock_t rc_client_clock_get_now_millisecs(const rc_client_t* client)
|
||||
{
|
||||
#if defined(__APPLE__) && defined(__MACH__)
|
||||
#ifdef CLOCK_MONOTONIC
|
||||
/* clock_gettime() was added to Darwin in iOS 10.0 and macOS 10.12.
|
||||
* On earlier deployment targets (like Leopard 10.5), the symbol doesn't exist
|
||||
* in libSystem causing an "undefined reference to clock_gettime" linker error.
|
||||
* To get the code to use the #else block below, forcibly undefine CLOCK_MONOTONIC
|
||||
* when targeting earlier versions. */
|
||||
#include <AvailabilityMacros.h>
|
||||
#if (defined(MAC_OS_X_VERSION_MIN_REQUIRED) && MAC_OS_X_VERSION_MIN_REQUIRED < 101200)
|
||||
#undef CLOCK_MONOTONIC
|
||||
#elif (defined(__IPHONE_OS_VERSION_MIN_REQUIRED) && __IPHONE_OS_VERSION_MIN_REQUIRED < 100000)
|
||||
#undef CLOCK_MONOTONIC
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if defined(CLOCK_MONOTONIC)
|
||||
struct timespec now;
|
||||
(void)client;
|
||||
@@ -703,6 +719,7 @@ static void rc_client_login_callback(const rc_api_server_response_t* server_resp
|
||||
client->user.display_name = rc_buffer_strcpy(&client->state.buffer, login_response.display_name);
|
||||
|
||||
client->user.avatar_url = rc_buffer_strcpy(&client->state.buffer, login_response.avatar_url);
|
||||
client->user.avatar_last_updated = login_response.avatar_last_updated;
|
||||
client->user.token = rc_buffer_strcpy(&client->state.buffer, login_response.api_token);
|
||||
client->user.score = login_response.score;
|
||||
client->user.score_softcore = login_response.score_softcore;
|
||||
@@ -934,6 +951,11 @@ static void rc_client_subset_get_user_game_summary(const rc_client_t* client,
|
||||
for (; achievement < stop; ++achievement) {
|
||||
switch (achievement->public_.category) {
|
||||
case RC_CLIENT_ACHIEVEMENT_CATEGORY_CORE:
|
||||
if (achievement->public_.id >= RC_CLIENT_ACHIEVEMENT_WARNING_ID) {
|
||||
/* ignore warning achievements */
|
||||
continue;
|
||||
}
|
||||
|
||||
++summary->num_core_achievements;
|
||||
summary->points_core += achievement->public_.points;
|
||||
|
||||
@@ -2109,10 +2131,10 @@ static void rc_client_copy_achievements(rc_client_load_state_t* load_state,
|
||||
else {
|
||||
rc_buffer_consume(buffer, (const uint8_t*)preparse.parse.buffer, (uint8_t*)preparse.parse.buffer + preparse.parse.offset);
|
||||
}
|
||||
|
||||
rc_destroy_preparse_state(&preparse);
|
||||
}
|
||||
|
||||
rc_destroy_preparse_state(&preparse);
|
||||
|
||||
achievement->created_time = read->created;
|
||||
achievement->updated_time = read->updated;
|
||||
|
||||
@@ -3881,6 +3903,193 @@ void rc_client_destroy_game_title_list(rc_client_game_title_list_t* list)
|
||||
free(list);
|
||||
}
|
||||
|
||||
/* ===== Fetch Games List ===== */
|
||||
|
||||
typedef struct rc_client_fetch_game_list_callback_data_t {
|
||||
rc_client_t* client;
|
||||
rc_client_fetch_game_list_callback_t callback;
|
||||
void* callback_userdata;
|
||||
rc_client_async_handle_t async_handle;
|
||||
} rc_client_fetch_game_list_callback_data_t;
|
||||
|
||||
static void rc_client_fetch_game_list_callback(const rc_api_server_response_t* server_response, void* callback_data)
|
||||
{
|
||||
rc_client_fetch_game_list_callback_data_t* list_callback_data =
|
||||
(rc_client_fetch_game_list_callback_data_t*)callback_data;
|
||||
rc_client_t* client = list_callback_data->client;
|
||||
rc_api_fetch_games_list_response_t list_response;
|
||||
const char* error_message;
|
||||
int result;
|
||||
|
||||
result = rc_client_end_async(client, &list_callback_data->async_handle);
|
||||
if (result) {
|
||||
if (result != RC_CLIENT_ASYNC_DESTROYED)
|
||||
RC_CLIENT_LOG_VERBOSE(client, "Fetch game list aborted");
|
||||
|
||||
free(list_callback_data);
|
||||
return;
|
||||
}
|
||||
|
||||
result = rc_api_process_fetch_games_list_server_response(&list_response, server_response);
|
||||
error_message =
|
||||
rc_client_server_error_message(&result, server_response->http_status_code, &list_response.response);
|
||||
if (error_message) {
|
||||
RC_CLIENT_LOG_ERR_FORMATTED(client, "Fetch game list failed: %s", error_message);
|
||||
list_callback_data->callback(result, error_message, NULL, client, list_callback_data->callback_userdata);
|
||||
} else {
|
||||
rc_client_game_list_t* list;
|
||||
size_t strings_size = 0, hashes_size = 0;
|
||||
const rc_api_game_list_entry_t* src;
|
||||
const rc_api_game_list_entry_t* stop;
|
||||
size_t list_size;
|
||||
uint32_t i;
|
||||
|
||||
/* calculate string buffer size */
|
||||
for (src = list_response.entries, stop = src + list_response.num_entries; src < stop; ++src) {
|
||||
if (src->name)
|
||||
strings_size += strlen(src->name) + 1;
|
||||
if (src->image_name)
|
||||
strings_size += strlen(src->image_name) + 1;
|
||||
if (src->image_url)
|
||||
strings_size += strlen(src->image_url) + 1;
|
||||
hashes_size += src->num_supported_hashes * sizeof(const char*);
|
||||
for (i = 0; i < src->num_supported_hashes; i++)
|
||||
strings_size += strlen(src->supported_hashes[i]) + 1;
|
||||
hashes_size += src->num_unsupported_hashes * sizeof(const char*);
|
||||
for (i = 0; i < src->num_unsupported_hashes; i++)
|
||||
strings_size += strlen(src->unsupported_hashes[i]) + 1;
|
||||
}
|
||||
|
||||
list_size = sizeof(*list) + sizeof(rc_client_game_list_entry_t) * list_response.num_entries +
|
||||
sizeof(const char*) * hashes_size + strings_size;
|
||||
list = (rc_client_game_list_t*)malloc(list_size);
|
||||
if (!list) {
|
||||
list_callback_data->callback(RC_OUT_OF_MEMORY, rc_error_str(RC_OUT_OF_MEMORY), NULL, client,
|
||||
list_callback_data->callback_userdata);
|
||||
} else {
|
||||
rc_client_game_list_entry_t* entry = list->entries =
|
||||
(rc_client_game_list_entry_t*)((uint8_t*)list + sizeof(*list));
|
||||
const char** hash_list =
|
||||
(const char**)((uint8_t*)entry + sizeof(rc_client_game_list_entry_t) * list_response.num_entries);
|
||||
char* strings = (char*)((uint8_t*)hash_list + sizeof(const char*) * hashes_size);
|
||||
size_t len;
|
||||
|
||||
for (src = list_response.entries, stop = src + list_response.num_entries; src < stop; ++src, ++entry) {
|
||||
entry->id = src->id;
|
||||
entry->num_achievements = src->num_achievements;
|
||||
entry->num_leaderboards = src->num_leaderboards;
|
||||
entry->points = src->points;
|
||||
|
||||
if (src->name) {
|
||||
len = strlen(src->name) + 1;
|
||||
entry->name = strings;
|
||||
memcpy(strings, src->name, len);
|
||||
strings += len;
|
||||
} else {
|
||||
entry->name = NULL;
|
||||
}
|
||||
|
||||
if (src->image_name) {
|
||||
len = strlen(src->image_name) + 1;
|
||||
entry->image_name = strings;
|
||||
memcpy(strings, src->image_name, len);
|
||||
strings += len;
|
||||
} else {
|
||||
entry->image_name = NULL;
|
||||
}
|
||||
|
||||
if (src->image_url) {
|
||||
len = strlen(src->image_url) + 1;
|
||||
entry->image_url = strings;
|
||||
memcpy(strings, src->image_url, len);
|
||||
strings += len;
|
||||
} else {
|
||||
entry->image_url = NULL;
|
||||
}
|
||||
|
||||
if ((entry->num_supported_hashes = src->num_supported_hashes) > 0) {
|
||||
entry->supported_hashes = hash_list;
|
||||
for (i = 0; i < src->num_supported_hashes; i++) {
|
||||
len = strlen(src->supported_hashes[i]) + 1;
|
||||
*(hash_list++) = strings;
|
||||
memcpy(strings, src->supported_hashes[i], len);
|
||||
strings += len;
|
||||
}
|
||||
} else {
|
||||
entry->supported_hashes = NULL;
|
||||
}
|
||||
|
||||
if ((entry->num_unsupported_hashes = src->num_unsupported_hashes) > 0) {
|
||||
entry->unsupported_hashes = hash_list;
|
||||
for (i = 0; i < src->num_unsupported_hashes; i++) {
|
||||
len = strlen(src->unsupported_hashes[i]) + 1;
|
||||
*(hash_list++) = strings;
|
||||
memcpy(strings, src->unsupported_hashes[i], len);
|
||||
strings += len;
|
||||
}
|
||||
} else {
|
||||
entry->unsupported_hashes = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
list->num_entries = list_response.num_entries;
|
||||
|
||||
list_callback_data->callback(RC_OK, NULL, list, client, list_callback_data->callback_userdata);
|
||||
}
|
||||
}
|
||||
|
||||
rc_api_destroy_fetch_games_list_response(&list_response);
|
||||
free(list_callback_data);
|
||||
}
|
||||
|
||||
rc_client_async_handle_t* rc_client_begin_fetch_game_list(rc_client_t* client, uint32_t console_id,
|
||||
rc_client_fetch_game_list_callback_t callback,
|
||||
void* callback_userdata)
|
||||
{
|
||||
rc_api_fetch_games_list_request_t api_params;
|
||||
rc_client_fetch_game_list_callback_data_t* callback_data;
|
||||
rc_client_async_handle_t* async_handle;
|
||||
rc_api_request_t request;
|
||||
int result;
|
||||
const char* error_message;
|
||||
|
||||
if (!client) {
|
||||
callback(RC_INVALID_STATE, "client is required", NULL, client, callback_userdata);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
api_params.console_id = console_id;
|
||||
result = rc_api_init_fetch_games_list_request_hosted(&request, &api_params, &client->state.host);
|
||||
|
||||
if (result != RC_OK) {
|
||||
error_message = rc_error_str(result);
|
||||
callback(result, error_message, NULL, client, callback_userdata);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
callback_data = (rc_client_fetch_game_list_callback_data_t*)calloc(1, sizeof(*callback_data));
|
||||
if (!callback_data) {
|
||||
callback(RC_OUT_OF_MEMORY, rc_error_str(RC_OUT_OF_MEMORY), NULL, client, callback_userdata);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
callback_data->client = client;
|
||||
callback_data->callback = callback;
|
||||
callback_data->callback_userdata = callback_userdata;
|
||||
|
||||
async_handle = &callback_data->async_handle;
|
||||
rc_client_begin_async(client, async_handle);
|
||||
client->callbacks.server_call(&request, rc_client_fetch_game_list_callback, callback_data, client);
|
||||
rc_api_destroy_request(&request);
|
||||
|
||||
return rc_client_async_handle_valid(client, async_handle) ? async_handle : NULL;
|
||||
}
|
||||
|
||||
void rc_client_destroy_game_list(rc_client_game_list_t* list)
|
||||
{
|
||||
free(list);
|
||||
}
|
||||
|
||||
/* ===== Achievements ===== */
|
||||
|
||||
static void rc_client_update_achievement_display_information(rc_client_t* client, rc_client_achievement_info_t* achievement, time_t recent_unlock_time)
|
||||
@@ -3928,7 +4137,7 @@ static void rc_client_update_achievement_display_information(rc_client_t* client
|
||||
if (!achievement->trigger->measured_as_percent) {
|
||||
char* ptr = achievement->public_.measured_progress;
|
||||
const int buffer_size = (int)sizeof(achievement->public_.measured_progress);
|
||||
const int chars = rc_format_value(ptr, buffer_size, (int32_t)new_measured_value, RC_FORMAT_UNSIGNED_VALUE);
|
||||
const int chars = rc_format_value(ptr, buffer_size - 1, (int32_t)new_measured_value, RC_FORMAT_UNSIGNED_VALUE);
|
||||
ptr[chars] = '/';
|
||||
rc_format_value(ptr + chars + 1, buffer_size - chars - 1, (int32_t)achievement->trigger->measured_target, RC_FORMAT_UNSIGNED_VALUE);
|
||||
}
|
||||
@@ -4606,6 +4815,11 @@ static void rc_client_award_achievement(rc_client_t* client, rc_client_achieveme
|
||||
|
||||
rc_mutex_unlock(&client->state.mutex);
|
||||
|
||||
if (achievement->public_.id >= RC_CLIENT_ACHIEVEMENT_WARNING_ID) {
|
||||
RC_CLIENT_LOG_INFO_FORMATTED(client, "Unlocked warning achievement %u: %s", achievement->public_.id, achievement->public_.title);
|
||||
return;
|
||||
}
|
||||
|
||||
if (client->callbacks.can_submit_achievement_unlock &&
|
||||
!client->callbacks.can_submit_achievement_unlock(achievement->public_.id, client)) {
|
||||
RC_CLIENT_LOG_INFO_FORMATTED(client, "Achievement %u unlock blocked by client", achievement->public_.id);
|
||||
@@ -4633,7 +4847,6 @@ static void rc_client_award_achievement(rc_client_t* client, rc_client_achieveme
|
||||
callback_data->client = client;
|
||||
callback_data->id = achievement->public_.id;
|
||||
callback_data->hardcore = client->state.hardcore;
|
||||
callback_data->game_hash = client->game->public_.hash;
|
||||
callback_data->unlock_time = client->callbacks.get_time_millisecs(client);
|
||||
|
||||
if (client->game) /* may be NULL if this gets called while unloading the game (from another thread - events are raised outside the lock) */
|
||||
@@ -5810,9 +6023,6 @@ static void rc_client_update_memref_values(rc_client_t* client) {
|
||||
} while (modified_memref_list);
|
||||
}
|
||||
|
||||
if (client->game->runtime.richpresence && client->game->runtime.richpresence->richpresence)
|
||||
rc_update_values(client->game->runtime.richpresence->richpresence->values, client->state.legacy_peek, client);
|
||||
|
||||
if (invalidated_memref)
|
||||
rc_client_update_active_achievements(client->game);
|
||||
}
|
||||
@@ -5842,6 +6052,7 @@ static void rc_client_do_frame_process_achievements(rc_client_t* client, rc_clie
|
||||
/* if the measured value changed and the achievement hasn't triggered, show a progress indicator */
|
||||
if (trigger->measured_value != old_measured_value && old_measured_value != RC_MEASURED_UNKNOWN &&
|
||||
trigger->measured_value <= trigger->measured_target &&
|
||||
trigger->measured_target != 0 &&
|
||||
rc_trigger_state_active(new_state) && new_state != RC_TRIGGER_STATE_WAITING) {
|
||||
|
||||
/* only show a popup for the achievement closest to triggering */
|
||||
|
||||
+1
@@ -43,6 +43,7 @@ static const char* rc_client_external_build_avatar_url(char buffer[], size_t buf
|
||||
return NULL;
|
||||
|
||||
snprintf(buffer, buffer_size, "%s", request.url);
|
||||
rc_api_destroy_request(&request);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
|
||||
Vendored
+2
-1
@@ -121,7 +121,7 @@ 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);
|
||||
rc_buffer_consume(buffer, dst, dst + len + 1);
|
||||
return (char*)dst;
|
||||
}
|
||||
|
||||
@@ -194,6 +194,7 @@ const char* rc_error_str(int ret)
|
||||
case RC_INVALID_VARIABLE_NAME: return "Invalid variable name";
|
||||
case RC_UNKNOWN_VARIABLE_NAME: return "Unknown variable name";
|
||||
case RC_NOT_FOUND: return "Not found";
|
||||
case RC_INVALID_VALUE: return "Invalid value expression";
|
||||
default: return "Unknown error";
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
+1
-1
@@ -8,7 +8,7 @@
|
||||
RC_BEGIN_C_DECLS
|
||||
|
||||
#define RCHEEVOS_VERSION_MAJOR 12
|
||||
#define RCHEEVOS_VERSION_MINOR 3
|
||||
#define RCHEEVOS_VERSION_MINOR 4
|
||||
#define RCHEEVOS_VERSION_PATCH 0
|
||||
|
||||
#define RCHEEVOS_MAKE_VERSION(major, minor, patch) (major * 1000000 + minor * 1000 + patch)
|
||||
|
||||
+4
@@ -213,6 +213,10 @@ static void rc_preparse_sync_operand(rc_operand_t* operand, rc_parse_state_t* pa
|
||||
{
|
||||
if (rc_operand_is_memref(operand) || rc_operand_is_recall(operand)) {
|
||||
const rc_memref_t* src_memref = operand->value.memref;
|
||||
if (!src_memref) {
|
||||
parse->offset = RC_INVALID_MEMORY_OPERAND;
|
||||
return;
|
||||
}
|
||||
|
||||
if (src_memref->value.memref_type == RC_MEMREF_TYPE_MODIFIED_MEMREF) {
|
||||
const rc_modified_memref_list_t* modified_memref_list = &memrefs->modified_memrefs;
|
||||
|
||||
+4
-1
@@ -411,10 +411,13 @@ void rc_condition_update_parse_state(rc_condition_t* condition, rc_parse_state_t
|
||||
negate = rc_alloc_modified_memref(parse, new_size, &parse->addsource_parent, RC_OPERATOR_SUB_PARENT, &zero);
|
||||
parse->addsource_parent.value.memref = (rc_memref_t*)negate;
|
||||
parse->addsource_parent.size = zero.size;
|
||||
|
||||
if (parse->addsource_parent.type == RC_OPERAND_CONST)
|
||||
parse->addsource_parent.value.num = rc_get_modified_memref_value(negate, NULL, NULL);
|
||||
}
|
||||
|
||||
/* subtract the condition from the chain */
|
||||
parse->addsource_oper = rc_operand_is_memref(&parse->addsource_parent) ? RC_OPERATOR_SUB_ACCUMULATOR : RC_OPERATOR_SUB_PARENT;
|
||||
parse->addsource_oper = RC_OPERATOR_SUB_ACCUMULATOR;
|
||||
rc_condition_convert_to_operand(condition, &cond_operand, parse);
|
||||
rc_operand_addsource(&cond_operand, parse, new_size);
|
||||
memcpy(&parse->addsource_parent, &cond_operand, sizeof(cond_operand));
|
||||
|
||||
+14
-2
@@ -818,12 +818,14 @@ static const rc_memory_region_t _rc_memory_regions_playstation2[] = {
|
||||
static const rc_memory_regions_t rc_memory_regions_playstation2 = { _rc_memory_regions_playstation2, 3 };
|
||||
|
||||
/* ===== PlayStation Portable ===== */
|
||||
/* https://github.com/uofw/upspd/wiki/Memory-map */
|
||||
/* https://github.com/uofw/upspd/wiki/Memory-map
|
||||
* the RAM is extended at loadtime if the game has a MEMSIZE flag in its PARAM.SFO */
|
||||
static const rc_memory_region_t _rc_memory_regions_psp[] = {
|
||||
{ 0x00000000U, 0x007FFFFFU, 0x08000000U, RC_MEMORY_TYPE_SYSTEM_RAM, "Kernel RAM" },
|
||||
{ 0x00800000U, 0x01FFFFFFU, 0x08800000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" },
|
||||
{ 0x02000000U, 0x03FFFFFFU, 0x0A000000U, RC_MEMORY_TYPE_SYSTEM_RAM, "Extended RAM" },
|
||||
};
|
||||
static const rc_memory_regions_t rc_memory_regions_psp = { _rc_memory_regions_psp, 2 };
|
||||
static const rc_memory_regions_t rc_memory_regions_psp = { _rc_memory_regions_psp, 3 };
|
||||
|
||||
/* ===== Pokemon Mini ===== */
|
||||
/* https://www.pokemon-mini.net/documentation/memory-map/ */
|
||||
@@ -993,6 +995,13 @@ static const rc_memory_region_t _rc_memory_regions_wonderswan[] = {
|
||||
};
|
||||
static const rc_memory_regions_t rc_memory_regions_wonderswan = { _rc_memory_regions_wonderswan, 2 };
|
||||
|
||||
/* ===== Xbox ===== */
|
||||
static const rc_memory_region_t _rc_memory_regions_xbox[] = {
|
||||
/* Retail Xbox memory map: https://xboxdevwiki.net/Memory */
|
||||
{ 0x00000000U, 0x03FFFFFFU, 0x00000000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" }
|
||||
};
|
||||
static const rc_memory_regions_t rc_memory_regions_xbox = { _rc_memory_regions_xbox, 1 };
|
||||
|
||||
/* ===== ZX Spectrum ===== */
|
||||
/* https://github.com/TASEmulators/BizHawk/blob/3a3b22c/src/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum16K/ZX16.cs
|
||||
* https://github.com/TASEmulators/BizHawk/blob/3a3b22c/src/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.Memory.cs
|
||||
@@ -1206,6 +1215,9 @@ const rc_memory_regions_t* rc_console_memory_regions(uint32_t console_id)
|
||||
case RC_CONSOLE_WONDERSWAN:
|
||||
return &rc_memory_regions_wonderswan;
|
||||
|
||||
case RC_CONSOLE_XBOX:
|
||||
return &rc_memory_regions_xbox;
|
||||
|
||||
case RC_CONSOLE_ZX_SPECTRUM:
|
||||
return &rc_memory_regions_zx_spectrum;
|
||||
|
||||
|
||||
+1
@@ -742,6 +742,7 @@ uint32_t rc_get_modified_memref_value(const rc_modified_memref_t* memref, rc_pee
|
||||
break;
|
||||
|
||||
case RC_OPERATOR_SUB_ACCUMULATOR:
|
||||
/* sub accumulator is "parent - modifier" */
|
||||
rc_typed_value_negate(&modifier);
|
||||
/* fallthrough */ /* to case RC_OPERATOR_SUB_ACCUMULATOR */
|
||||
|
||||
|
||||
+22
-1
@@ -38,6 +38,7 @@ enum
|
||||
/* warnings about potential logic errors */
|
||||
RC_VALIDATION_ERR_COMPARING_DIFFERENT_MEMORY_SIZES,
|
||||
RC_VALIDATION_ERR_MASK_RESULT_ALWAYS_ZERO,
|
||||
RC_VALIDATION_ERR_SAME_COMPARISON_ACROSS_FRAMES,
|
||||
|
||||
/* warnings that some areas of memory should be avoided */
|
||||
RC_VALIDATION_ERR_KERNAL_RAM_REQUIRES_BIOS,
|
||||
@@ -244,6 +245,13 @@ static int rc_validate_format_error(char buffer[], size_t buffer_size, const rc_
|
||||
snprintf(buffer, buffer_size, "Result of mask is always 0");
|
||||
break;
|
||||
|
||||
case RC_VALIDATION_ERR_SAME_COMPARISON_ACROSS_FRAMES:
|
||||
written = snprintf(buffer, buffer_size, "Same comparison across frames as ");
|
||||
buffer += written;
|
||||
buffer_size -= written;
|
||||
rc_validate_format_cond_index(buffer, buffer_size, state, error->data1, error->data2);
|
||||
break;
|
||||
|
||||
case RC_VALIDATION_ERR_MASK_TOO_LARGE:
|
||||
snprintf(buffer, buffer_size, "Mask has more bits than source");
|
||||
break;
|
||||
@@ -1211,9 +1219,22 @@ static int rc_validate_conflicting_conditions(const rc_condset_t* conditions, co
|
||||
continue;
|
||||
|
||||
operand2 = rc_validate_get_comparison(compare_condition, &comparison2, &value2);
|
||||
if (!operand2 || !rc_operands_are_equal(operand1, operand2))
|
||||
if (!operand2)
|
||||
continue;
|
||||
|
||||
if (!rc_operands_are_equal(operand1, operand2)) {
|
||||
if (compare_condition->type == condition->type &&
|
||||
compare_condition->oper == condition->oper &&
|
||||
operand1->type == RC_OPERAND_ADDRESS &&
|
||||
(operand2->type == RC_OPERAND_DELTA || operand2->type == RC_OPERAND_PRIOR) &&
|
||||
operand1->value.memref->address == operand2->value.memref->address &&
|
||||
rc_operands_are_equal(&compare_condition->operand2, &condition->operand2)) {
|
||||
rc_validate_add_error(state, RC_VALIDATION_ERR_SAME_COMPARISON_ACROSS_FRAMES,
|
||||
group_index, rc_validate_get_condition_index(conditions, condition));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (compare_condition->type) {
|
||||
case RC_CONDITION_PAUSE_IF:
|
||||
if (conditions != compare_conditions) /* PauseIf only affects conditions in same group */
|
||||
|
||||
+8
-2
@@ -38,6 +38,11 @@ static void rc_alloc_helper_variable_memref_value(rc_richpresence_display_part_t
|
||||
/* ensure new needed memrefs are allocated in the primary buffer */
|
||||
rc_preparse_copy_memrefs(parse, &preparse.memrefs);
|
||||
|
||||
if (parse->offset < 0) {
|
||||
rc_destroy_preparse_state(&preparse);
|
||||
return;
|
||||
}
|
||||
|
||||
/* parse the value into the scratch buffer so we can look at it */
|
||||
rc_reset_parse_state(&preparse.parse, rc_buffer_alloc(&preparse.parse.scratch.buffer, (size_t)size));
|
||||
preparse.parse.memrefs = parse->memrefs;
|
||||
@@ -725,14 +730,15 @@ rc_memrefs_t* rc_richpresence_get_memrefs(rc_richpresence_t* self) {
|
||||
void rc_update_richpresence(rc_richpresence_t* richpresence, rc_peek_t peek, void* peek_ud, void* unused_L) {
|
||||
(void)unused_L;
|
||||
|
||||
rc_update_richpresence_memrefs(richpresence, peek, peek_ud);
|
||||
rc_update_values(richpresence->values, peek, peek_ud);
|
||||
rc_update_richpresence_internal(richpresence, peek, peek_ud);
|
||||
}
|
||||
|
||||
void rc_update_richpresence_internal(rc_richpresence_t* richpresence, rc_peek_t peek, void* peek_ud) {
|
||||
rc_richpresence_display_t* display;
|
||||
|
||||
rc_update_richpresence_memrefs(richpresence, peek, peek_ud);
|
||||
rc_update_values(richpresence->values, peek, peek_ud);
|
||||
|
||||
for (display = richpresence->first_display; display; display = display->next) {
|
||||
if (display->has_required_hits)
|
||||
rc_test_trigger(&display->trigger, peek, peek_ud, NULL);
|
||||
|
||||
+2
-2
@@ -510,7 +510,7 @@ int rc_runtime_get_richpresence(const rc_runtime_t* self, char* buffer, size_t b
|
||||
|
||||
void rc_runtime_do_frame(rc_runtime_t* self, rc_runtime_event_handler_t event_handler, rc_runtime_peek_t peek, void* ud, void* unused_L) {
|
||||
rc_runtime_event_t runtime_event;
|
||||
int i;
|
||||
int32_t i;
|
||||
|
||||
runtime_event.value = 0;
|
||||
|
||||
@@ -670,7 +670,7 @@ void rc_runtime_do_frame(rc_runtime_t* self, rc_runtime_event_handler_t event_ha
|
||||
break;
|
||||
|
||||
case RC_LBOARD_STATE_TRIGGERED:
|
||||
if (lboard_state != RC_RUNTIME_EVENT_LBOARD_TRIGGERED) {
|
||||
if (lboard_state != RC_LBOARD_STATE_TRIGGERED) {
|
||||
runtime_event.type = RC_RUNTIME_EVENT_LBOARD_TRIGGERED;
|
||||
runtime_event.id = self->lboards[i].id;
|
||||
event_handler(&runtime_event);
|
||||
|
||||
+8
-2
@@ -120,6 +120,12 @@ static void rc_parse_legacy_value(rc_value_t* self, const char** memaddr, rc_par
|
||||
|
||||
/* extract the next clause */
|
||||
for (;; ++(*memaddr)) {
|
||||
if (ptr == &buffer[sizeof(buffer)]) {
|
||||
/* ran out of local buffer space for converting the condition */
|
||||
parse->offset = RC_INVALID_VALUE;
|
||||
return;
|
||||
}
|
||||
|
||||
switch (**memaddr) {
|
||||
case '_': /* add next */
|
||||
*ptr = '\0';
|
||||
@@ -176,7 +182,7 @@ static void rc_parse_legacy_value(rc_value_t* self, const char** memaddr, rc_par
|
||||
|
||||
if (*buffer_ptr) {
|
||||
/* whatever we copied as a single condition was not fully consumed */
|
||||
parse->offset = RC_INVALID_COMPARISON;
|
||||
parse->offset = RC_INVALID_VALUE;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -429,7 +435,7 @@ rc_value_t* rc_alloc_variable(const char* memaddr, size_t memaddr_len, rc_parse_
|
||||
|
||||
/* no match found, create a new entry */
|
||||
value = RC_ALLOC_SCRATCH(rc_value_t, parse);
|
||||
memset(value, 0, sizeof(value->value));
|
||||
memset(value, 0, sizeof(*value));
|
||||
value->value.size = RC_MEMSIZE_VARIABLE;
|
||||
value->next = NULL;
|
||||
|
||||
|
||||
+32
-7
@@ -648,20 +648,45 @@ static void* cdreader_open_gdi_track(const char* path, uint32_t track, const rc_
|
||||
while (isspace((unsigned char)*ptr))
|
||||
++ptr;
|
||||
|
||||
ptr2 = file;
|
||||
if (*ptr == '\"')
|
||||
{
|
||||
++ptr;
|
||||
ptr2 = ++ptr; /* ignore leading quote */
|
||||
while (*ptr != '\"')
|
||||
*ptr2++ = *ptr++;
|
||||
++ptr;
|
||||
{
|
||||
++ptr;
|
||||
|
||||
if (ptr >= end)
|
||||
{
|
||||
if (iterator->callbacks.filereader.close)
|
||||
iterator->callbacks.filereader.close(file_handle);
|
||||
|
||||
rc_hash_iterator_error(iterator, "Quoted string without closing quote");
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
num_read = ptr - ptr2;
|
||||
++ptr; /* ignore trailing quote */
|
||||
}
|
||||
else
|
||||
{
|
||||
while (*ptr != ' ')
|
||||
*ptr2++ = *ptr++;
|
||||
ptr2 = ptr;
|
||||
while (*ptr != ' ' && ptr < end)
|
||||
++ptr;
|
||||
num_read = ptr - ptr2;
|
||||
}
|
||||
*ptr2 = '\0';
|
||||
|
||||
if (num_read >= sizeof(file))
|
||||
{
|
||||
if (iterator->callbacks.filereader.close)
|
||||
iterator->callbacks.filereader.close(file_handle);
|
||||
|
||||
rc_hash_iterator_error_formatted(iterator, "Cannot copy %u byte filename into %u byte buffer", (unsigned)num_read, (unsigned)sizeof(file));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
memcpy(file, ptr2, num_read);
|
||||
file[num_read] = '\0';
|
||||
|
||||
if (track == current_track)
|
||||
{
|
||||
|
||||
Vendored
+14
-2
@@ -494,7 +494,7 @@ static int rc_hash_file_from_buffer(char hash[33], uint32_t console_id, const rc
|
||||
|
||||
result = rc_hash_from_file(hash, console_id, &buffered_file_iterator);
|
||||
|
||||
buffered_file_iterator.path = NULL;
|
||||
buffered_file_iterator.path = NULL; /* prevent attempt to free static "memory stream" string */
|
||||
rc_hash_destroy_iterator(&buffered_file_iterator);
|
||||
return result;
|
||||
}
|
||||
@@ -543,6 +543,11 @@ static int rc_hash_from_buffer(char hash[33], uint32_t console_id, const rc_hash
|
||||
return rc_hash_buffer(hash, iterator->buffer, iterator->buffer_size, iterator);
|
||||
|
||||
#ifndef RC_HASH_NO_ROM
|
||||
case RC_CONSOLE_ARCADE:
|
||||
/* .neo (Geolith Neo Geo cart) files carry the ROM data; other arcade
|
||||
* formats are archives, which aren't hashed from a buffer. */
|
||||
return rc_hash_neogeo_cart(hash, iterator);
|
||||
|
||||
case RC_CONSOLE_ARDUBOY:
|
||||
return rc_hash_arduboy(hash, iterator);
|
||||
|
||||
@@ -862,6 +867,11 @@ static int rc_hash_from_file(char hash[33], uint32_t console_id, const rc_hash_i
|
||||
|
||||
#ifndef RC_HASH_NO_ROM
|
||||
case RC_CONSOLE_ARCADE:
|
||||
/* .neo files (Geolith Neo Geo cart format) contain the actual ROM data,
|
||||
* so are content-hashed. Everything else (.zip/.7z) hashes by filename. */
|
||||
if (rc_path_compare_extension(path, "neo"))
|
||||
return rc_hash_neogeo_cart(hash, iterator);
|
||||
|
||||
return rc_hash_arcade(hash, iterator);
|
||||
|
||||
case RC_CONSOLE_ARDUBOY:
|
||||
@@ -971,7 +981,7 @@ void rc_hash_merge_callbacks(rc_hash_iterator_t* iterator, const rc_hash_callbac
|
||||
if (callbacks->verbose_message)
|
||||
iterator->callbacks.verbose_message = callbacks->verbose_message;
|
||||
if (callbacks->error_message)
|
||||
iterator->callbacks.verbose_message = callbacks->error_message;
|
||||
iterator->callbacks.error_message = callbacks->error_message;
|
||||
|
||||
if (callbacks->filereader.open)
|
||||
memcpy(&iterator->callbacks.filereader, &callbacks->filereader, sizeof(callbacks->filereader));
|
||||
@@ -1230,6 +1240,7 @@ static const rc_hash_iterator_ext_handler_entry_t rc_hash_iterator_ext_handlers[
|
||||
{ "n64", rc_hash_initialize_iterator_single, RC_CONSOLE_NINTENDO_64 },
|
||||
{ "ndd", rc_hash_initialize_iterator_single, RC_CONSOLE_NINTENDO_64 },
|
||||
{ "nds", rc_hash_initialize_iterator_single, RC_CONSOLE_NINTENDO_DS }, /* handles both DS and DSi */
|
||||
{ "neo", rc_hash_initialize_iterator_single, RC_CONSOLE_ARCADE }, /* Geolith Neo Geo cart format */
|
||||
{ "nes", rc_hash_initialize_iterator_single, RC_CONSOLE_NINTENDO },
|
||||
{ "ngc", rc_hash_initialize_iterator_single, RC_CONSOLE_NEOGEO_POCKET },
|
||||
{ "nib", rc_hash_initialize_iterator_nib, 0 },
|
||||
@@ -1245,6 +1256,7 @@ static const rc_hash_iterator_ext_handler_entry_t rc_hash_iterator_ext_handlers[
|
||||
{ "sg", rc_hash_initialize_iterator_single, RC_CONSOLE_SG1000 },
|
||||
{ "sgx", rc_hash_initialize_iterator_single, RC_CONSOLE_PC_ENGINE },
|
||||
{ "smc", rc_hash_initialize_iterator_single, RC_CONSOLE_SUPER_NINTENDO },
|
||||
{ "sms", rc_hash_initialize_iterator_single, RC_CONSOLE_MASTER_SYSTEM },
|
||||
{ "sv", rc_hash_initialize_iterator_single, RC_CONSOLE_SUPERVISION },
|
||||
{ "swc", rc_hash_initialize_iterator_single, RC_CONSOLE_SUPER_NINTENDO },
|
||||
{ "tap", rc_hash_initialize_iterator_tap, 0 },
|
||||
|
||||
@@ -74,6 +74,7 @@ int rc_hash_buffered_file(char hash[33], uint32_t console_id, const rc_hash_iter
|
||||
int rc_hash_lynx(char hash[33], const rc_hash_iterator_t* iterator);
|
||||
int rc_hash_nes(char hash[33], const rc_hash_iterator_t* iterator);
|
||||
int rc_hash_n64(char hash[33], const rc_hash_iterator_t* iterator);
|
||||
int rc_hash_neogeo_cart(char hash[33], const rc_hash_iterator_t* iterator);
|
||||
int rc_hash_nintendo_ds(char hash[33], const rc_hash_iterator_t* iterator);
|
||||
int rc_hash_pce(char hash[33], const rc_hash_iterator_t* iterator);
|
||||
int rc_hash_scv(char hash[33], const rc_hash_iterator_t* iterator);
|
||||
|
||||
@@ -410,9 +410,6 @@ void GameListWidget::setCustomBackground()
|
||||
delete m_background_movie;
|
||||
m_background_movie = nullptr;
|
||||
}
|
||||
// Cache all frames for small images so loops don't keep re-decoding
|
||||
else if (const s64 file_size = FileSystem::GetPathFileSize(path.c_str()); file_size > 0 && file_size < 64 * 1024 * 1024)
|
||||
m_background_movie->setCacheMode(QMovie::CacheAll);
|
||||
}
|
||||
|
||||
// Invalidate frame cache so the next animated frame triggers full reprocessing
|
||||
@@ -436,6 +433,10 @@ void GameListWidget::setCustomBackground()
|
||||
return;
|
||||
}
|
||||
|
||||
// Cache all frames for small images so loops don't keep re-decoding
|
||||
if (const s64 file_size = FileSystem::GetPathFileSize(path.c_str()); file_size > 0 && file_size < 25 * 1024 * 1024)
|
||||
m_background_movie->setCacheMode(QMovie::CacheAll);
|
||||
|
||||
// Retrieve scaling setting
|
||||
m_background_scaling = QtUtils::ScalingMode::Fit;
|
||||
const std::string ar_value = Host::GetBaseStringSettingValue("UI", "GameListBackgroundMode", InterfaceSettingsWidget::BACKGROUND_SCALE_NAMES[static_cast<u8>(QtUtils::ScalingMode::Fit)]);
|
||||
@@ -792,7 +793,7 @@ void GameListWidget::setShowFullCoverTitles(bool enabled)
|
||||
Host::SetBaseBoolSettingValue("UI", "GameListShowFullCoverTitles", enabled);
|
||||
Host::CommitBaseSettingChanges();
|
||||
m_model->setShowFullCoverTitles(enabled);
|
||||
m_list_view->setWordWrap(enabled);
|
||||
m_list_view->setWordWrap(enabled);
|
||||
if (isShowingGameGrid())
|
||||
m_model->refresh();
|
||||
updateToolbar();
|
||||
|
||||
+3
-15
@@ -1068,27 +1068,15 @@ void EmuThread::updatePerformanceMetrics(bool force)
|
||||
|
||||
if (gpu_usage != m_last_gpu_usage || force)
|
||||
{
|
||||
QString text;
|
||||
if (gpu_usage == 0)
|
||||
text = tr("GPU: N/A");
|
||||
else
|
||||
text = tr("GPU: %1%").arg(gpu_usage, 0, 'f', 0);
|
||||
|
||||
QMetaObject::invokeMethod(g_main_window, "setStatusGPUText", Qt::QueuedConnection,
|
||||
Q_ARG(const QString&, text));
|
||||
Q_ARG(const QString&, tr("GPU: %1%").arg(gpu_usage, 0, 'f', 0)));
|
||||
m_last_gpu_usage = gpu_usage;
|
||||
}
|
||||
|
||||
if (gfps != m_last_game_fps || force)
|
||||
{
|
||||
QString text;
|
||||
if (gfps == 0)
|
||||
text = tr("FPS: N/A");
|
||||
else
|
||||
text = tr("FPS: %1").arg(gfps, 0, 'f', 0);
|
||||
|
||||
QMetaObject::invokeMethod(g_main_window, "setStatusFPSText", Qt::QueuedConnection,
|
||||
Q_ARG(const QString&, text));
|
||||
Q_ARG(const QString&, gfps == 0 ? tr("FPS: N/A") : tr("FPS: %1").arg(gfps, 0, 'f', 0)));
|
||||
m_last_game_fps = gfps;
|
||||
}
|
||||
|
||||
@@ -1267,7 +1255,7 @@ void Host::RunOnGSThread(std::function<void()> function)
|
||||
|
||||
void Host::RefreshGameListAsync(bool invalidate_cache)
|
||||
{
|
||||
QMetaObject::invokeMethod(g_main_window, "refreshGameList", Qt::QueuedConnection, Q_ARG(bool, invalidate_cache));
|
||||
QMetaObject::invokeMethod(g_main_window, "refreshGameList", Qt::QueuedConnection, Q_ARG(bool, invalidate_cache), Q_ARG(bool, true));
|
||||
}
|
||||
|
||||
void Host::CancelGameListRefresh()
|
||||
|
||||
@@ -791,14 +791,19 @@ GSTexture* GSDevice::CreateShaderWriteTarget(const GSVector2i& size, GSTexture::
|
||||
return FetchSurface(GSTexture::ShaderWriteTarget, size.x, size.y, 1, format, clear, prefer_reuse);
|
||||
}
|
||||
|
||||
GSTexture::Usage GSDevice::GetDepthStencilUsage() const
|
||||
{
|
||||
return m_features.depth_feedback ? GSTexture::FeedbackDepth : GSTexture::DepthStencil;
|
||||
}
|
||||
|
||||
GSTexture* GSDevice::CreateDepthStencil(int w, int h, bool clear, bool prefer_reuse)
|
||||
{
|
||||
return FetchSurface(GSTexture::DepthStencil, w, h, 1, GSTexture::Format::DepthStencil, clear, prefer_reuse);
|
||||
return FetchSurface(GetDepthStencilUsage(), w, h, 1, GSTexture::Format::DepthStencil, clear, prefer_reuse);
|
||||
}
|
||||
|
||||
GSTexture* GSDevice::CreateDepthStencil(const GSVector2i& size, bool clear, bool prefer_reuse)
|
||||
{
|
||||
return FetchSurface(GSTexture::DepthStencil, size.x, size.y, 1, GSTexture::Format::DepthStencil, clear, prefer_reuse);
|
||||
return FetchSurface(GetDepthStencilUsage(), size.x, size.y, 1, GSTexture::Format::DepthStencil, clear, prefer_reuse);
|
||||
}
|
||||
|
||||
GSTexture* GSDevice::CreateTexture(int w, int h, int mipmap_levels, GSTexture::Format format, bool prefer_reuse)
|
||||
|
||||
@@ -1637,6 +1637,8 @@ public:
|
||||
virtual void PopDebugGroup() = 0;
|
||||
virtual void InsertDebugMessage(DebugMessageCategory category, const char* fmt, ...) = 0;
|
||||
|
||||
GSTexture::Usage GetDepthStencilUsage() const;
|
||||
|
||||
GSTexture* FetchSurface(GSTexture::Usage usage, int width, int height, int levels, GSTexture::Format format, bool clear, bool prefer_reuse);
|
||||
GSTexture* FetchSurface(GSTexture::Usage usage, const GSVector2i& size, int levels, GSTexture::Format format, bool clear, bool prefer_reuse);
|
||||
GSTexture* CreateRenderTarget(int w, int h, GSTexture::Format format, bool clear = true, bool prefer_reuse = true);
|
||||
|
||||
@@ -7215,8 +7215,7 @@ GSTextureCache::Target* GSTextureCache::Target::Create(GIFRegTEX0 TEX0, int w, i
|
||||
|
||||
const int scaled_w = static_cast<int>(std::ceil(static_cast<float>(w) * scale));
|
||||
const int scaled_h = static_cast<int>(std::ceil(static_cast<float>(h) * scale));
|
||||
GSTexture::Usage usage = type == RenderTarget ? GSTexture::FeedbackTarget :
|
||||
(g_gs_device->Features().depth_feedback ? GSTexture::FeedbackDepth : GSTexture::DepthStencil);
|
||||
GSTexture::Usage usage = type == RenderTarget ? GSTexture::FeedbackTarget : g_gs_device->GetDepthStencilUsage();
|
||||
GSTexture::Format format = type == RenderTarget ? GSTexture::Format::Color : GSTexture::Format::DepthStencil;
|
||||
GSTexture* texture = g_gs_device->FetchSurface(usage, scaled_w, scaled_h, 1, format, clear, PreferReusedLabelledTexture());
|
||||
if (!texture)
|
||||
|
||||
@@ -2606,6 +2606,11 @@ void FullscreenUI::DrawGameListWindow()
|
||||
{
|
||||
OpenCoverDownloaderWindow();
|
||||
}
|
||||
else if (ImGui::IsKeyPressed(ImGuiKey_GamepadL2, false) || ImGui::IsKeyPressed(ImGuiKey_F5, false))
|
||||
{
|
||||
ShowToast(std::string(), FSUI_STR("Scanning for new games..."), 4.0f);
|
||||
Host::RefreshGameListAsync(false);
|
||||
}
|
||||
|
||||
switch (s_game_list_view)
|
||||
{
|
||||
@@ -2640,6 +2645,7 @@ void FullscreenUI::DrawGameListWindow()
|
||||
std::make_pair(glyphs.dpad, FSUI_VSTR("Select Game")),
|
||||
std::make_pair(glyphs.select, FSUI_VSTR("Cover Downloader")),
|
||||
std::make_pair(glyphs.start, FSUI_VSTR("Settings")),
|
||||
std::make_pair(ICON_PF_LEFT_TRIGGER_L2, FSUI_VSTR("Refresh List")),
|
||||
std::make_pair(swapNorthWest ? glyphs.west : glyphs.north, FSUI_VSTR("Change View")),
|
||||
std::make_pair(swapNorthWest ? glyphs.north : glyphs.west, FSUI_VSTR("Launch Options")),
|
||||
std::make_pair(glyphs.confirm(circleOK), FSUI_VSTR("Start Game")),
|
||||
@@ -2654,6 +2660,7 @@ void FullscreenUI::DrawGameListWindow()
|
||||
std::make_pair(ICON_PF_F2, FSUI_VSTR("Settings")),
|
||||
std::make_pair(ICON_PF_F3, FSUI_VSTR("Launch Options")),
|
||||
std::make_pair(ICON_PF_F4, FSUI_VSTR("Cover Downloader")),
|
||||
std::make_pair(ICON_PF_F5, FSUI_VSTR("Refresh List")),
|
||||
std::make_pair(ICON_PF_ENTER, FSUI_VSTR("Start Game")),
|
||||
std::make_pair(ICON_PF_ESC, FSUI_VSTR("Back")),
|
||||
});
|
||||
|
||||
@@ -428,7 +428,7 @@ __ri void ImGuiManager::DrawPerformanceOverlay(float& position_y, float scale, f
|
||||
const CPUInfo& info = GetCPUInfo();
|
||||
const bool has_small = info.num_small_cores > 0;
|
||||
const bool has_smt = info.num_threads != info.num_big_cores + info.num_small_cores;
|
||||
s_hardware_info_cpu_line.format("CPU: {}", info.name);
|
||||
s_hardware_info_cpu_line.format("CPU: {}", !info.name.empty() ? info.name : "Unknown");
|
||||
if (has_smt && has_small)
|
||||
s_hardware_info_cpu_line.append_format(" ({}P/{}E/{}T)", info.num_big_cores, info.num_small_cores, info.num_threads);
|
||||
else if (has_small)
|
||||
|
||||
Reference in New Issue
Block a user