Compare commits

...
44 changed files with 880 additions and 276 deletions
@@ -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
View File
@@ -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)
-15
View File
@@ -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
-65
View File
@@ -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/*
+13
View File
@@ -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
-32
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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 |
\*****************************************************************************/
+2 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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);
+223 -12
View File
@@ -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
View File
@@ -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;
}
+2 -1
View File
@@ -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";
}
}
+1 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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)
{
+14 -2
View File
@@ -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 },
+1
View File
@@ -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);
+5 -4
View File
@@ -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
View File
@@ -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()
+7 -2
View File
@@ -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)
+2
View File
@@ -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);
+10 -7
View File
@@ -1883,7 +1883,7 @@ GSTextureCache::Source* GSTextureCache::LookupSource(const bool is_color, const
// If the sizing is completely wrong on the frame vs the source when reading from alpha then it's likely the target has 2 different sizes for rgb and alpha.
// This is just changing the target width for the rect translation, it has no bearing on the actual source read or the target itself.
// Hitman Blood Money is an example of this in the theatre.
const u32 rt_tbw = (possible_shuffle || bw == 1 || GSUtil::GetChannelMask(psm) != 0x8 || frame.FBW <= bw || frame.FBW == t->m_TEX0.TBW || bw == t->m_TEX0.TBW) ? t->m_TEX0.TBW : frame.FBW;
rt_tbw = (possible_shuffle || bw == 1 || GSUtil::GetChannelMask(psm) != 0x8 || frame.FBW <= bw || frame.FBW == t->m_TEX0.TBW || bw == t->m_TEX0.TBW) ? t->m_TEX0.TBW : frame.FBW;
const bool can_translate = CanTranslate(adj_bp, bw, src_psm, new_rect, t->m_TEX0.TBP0, t->m_TEX0.PSM, rt_tbw);
if (can_translate)
@@ -3483,8 +3483,8 @@ bool GSTextureCache::PreloadTarget(GIFRegTEX0 TEX0, const GSVector2i& size, cons
// When the write covers the entire target, don't bother checking any earlier writes.
if (iter->blit.DBP <= TEX0.TBP0 && transfer_end >= rect_end)
{
// If it was a clear draw then we can use that as our target size.
if (iter->transfer_type == GSRendererHW::GetInstance()->EEGS_TransferType::Clear && iter->blit.DBP == TEX0.TBP0 && iter->blit.DPSM == TEX0.PSM)
// If it was an exact upload then we can assume this the target size.
if (iter->blit.DBP == TEX0.TBP0 && iter->blit.DBW == TEX0.TBW && iter->blit.DPSM == TEX0.PSM)
dst->UpdateValidity(iter->rect);
// Some games clear RT and Z at the same time, only erase if it's specifically this target.
@@ -3877,9 +3877,13 @@ bool GSTextureCache::PreloadTarget(GIFRegTEX0 TEX0, const GSVector2i& size, cons
}
const int height_adjust = ((((dst_end_block + 31) - old_dst->m_TEX0.TBP0) >> 5) / std::max(old_dst->m_TEX0.TBW, 1U)) * GSLocalMemory::m_psm[old_dst->m_TEX0.PSM].pgs.y;
bool delete_target = true;
if (height_adjust < old_dst->m_unscaled_size.y)
// If it's only a partial overlap, we don't want to change the TBP, it needs to be on a page boundary, we can just lump it.
const GSVector4i dst_valid_rounded = GSVector4i(dst_valid.x, dst_valid.y, dst_valid.z, dst_valid.w & ~(GSLocalMemory::m_psm[dst->m_TEX0.PSM].pgs.y - 1));
const u32 dst_end_block_rounded = GSLocalMemory::GetEndBlockAddress(dst->m_TEX0.TBP0, dst->m_TEX0.TBW, dst->m_TEX0.PSM, dst_valid_rounded) + 1;
bool delete_target = (static_cast<int>(dst_end_block_rounded - old_dst->m_TEX0.TBP0) / 16) >= 1;
if (height_adjust < old_dst->m_unscaled_size.y && delete_target)
{
old_dst->m_TEX0.TBP0 = GSLocalMemory::GetStartBlockAddress(old_dst->m_TEX0.TBP0, old_dst->m_TEX0.TBW, old_dst->m_TEX0.PSM, GSVector4i(0, height_adjust, old_dst->m_valid.z, old_dst->m_valid.w));
old_dst->m_valid.w -= height_adjust;
@@ -7215,8 +7219,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)
+7
View File
@@ -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")),
});
+1 -1
View File
@@ -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)