Compare commits

..
Author SHA1 Message Date
refractionpcsx2 c1dfdd454b GameDB: Add required GameDB Entries
S.L.A.I. - Steel Lancer Arena International
Syphon Filter - Logan's Shadow
2026-07-26 08:21:08 +01:00
refractionpcsx2 eee577e694 GS/HW: Improve "jiggle" blur effect detection and handling 2026-07-26 08:20:26 +01:00
refractionpcsx2 e791f43899 GS/HW: Native Scaling detection of mild downscale and write back 2026-07-26 04:37:27 +01:00
41 changed files with 265 additions and 859 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.1
KDDOCKWIDGETS=2.4.0
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
5cd9495d9316cf6cc937bb328a7fd491c3248ef2469cfa42511f1039f6148aca KDDockWidgets-$KDDOCKWIDGETS.tar.gz
51dbf24fe72e43dd7cb9a289d3cab47112010f1a2ed69b6fc8ac0dff31991ed2 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" -DCMAKE_POSITION_INDEPENDENT_CODE=1 -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" -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.4.1",
"commit": "c1d28d25ef5ba077915bcb2b6fa9e14df2a361f8",
"tag": "v2.3.0",
"commit": "c38711026e17e34916dd82c6fcbdcc0d2342f541",
"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.1
KDDOCKWIDGETS=2.4.0
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
5cd9495d9316cf6cc937bb328a7fd491c3248ef2469cfa42511f1039f6148aca KDDockWidgets-$KDDOCKWIDGETS.tar.gz
51dbf24fe72e43dd7cb9a289d3cab47112010f1a2ed69b6fc8ac0dff31991ed2 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.1
KDDOCKWIDGETS=2.4.0
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
5cd9495d9316cf6cc937bb328a7fd491c3248ef2469cfa42511f1039f6148aca KDDockWidgets-$KDDOCKWIDGETS.tar.gz
51dbf24fe72e43dd7cb9a289d3cab47112010f1a2ed69b6fc8ac0dff31991ed2 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.1
set KDDOCKWIDGETS=2.4.0
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" 6f803c533e95687b3cf968ea851fb9b04e253b32e02b5a40a1542cf202dcd9d1 || goto error
call :downloadfile "KDDockWidgets-%KDDOCKWIDGETS%.zip" "https://github.com/KDAB/KDDockWidgets/archive/v%KDDOCKWIDGETS%.zip" 47ddb48197872055f0adf8e90a7235f8a3b795ca1ee3a28ac2c504c673ae3806 || 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.1
set KDDOCKWIDGETS=2.4.0
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" 6f803c533e95687b3cf968ea851fb9b04e253b32e02b5a40a1542cf202dcd9d1 || goto error
call :downloadfile "KDDockWidgets-%KDDOCKWIDGETS%.zip" "https://github.com/KDAB/KDDockWidgets/archive/v%KDDOCKWIDGETS%.zip" 47ddb48197872055f0adf8e90a7235f8a3b795ca1ee3a28ac2c504c673ae3806 || 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
+15
View File
@@ -0,0 +1,15 @@
# More info: http://EditorConfig.org
root = true
# * here means any file type
[*]
end_of_line = crlf
insert_final_newline = true
# latin1 is a type of ASCII, should work with mbcs
[*.{h,c,cpp}]
charset = latin1
indent_style = space
indent_size = 2
trim_trailing_whitespace = true
curly_bracket_next_line = false
+65
View File
@@ -0,0 +1,65 @@
# Prerequisites
*.d
# Object files
*.o
*.ko
*.obj
*.elf
# Linker output
*.ilk
*.map
*.exp
# Precompiled Headers
*.gch
*.pch
# Libraries
*.lib
*.a
*.la
*.lo
# Shared objects (inc. Windows DLLs)
*.dll
*.so
*.so.*
*.dylib
# Executables
*.exe
*.out
*.app
*.i*86
*.x86_64
*.hex
# Debug files
*.dSYM/
*.su
*.idb
*.pdb
# Kernel Module Compile Results
*.mod*
*.cmd
.tmp_versions/
modules.order
Module.symvers
Mkfile.old
dkms.conf
# Visual Studio files
Debug/
Release/
*.user
.vs/
# Repository specific
test/test
test/galaga_nes.h
test/smw_snes.h
validator/validator
.vscode/*
-13
View File
@@ -1,16 +1,3 @@
# 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
@@ -0,0 +1,32 @@
// swift-tools-version: 5.8
// The swift-tools-version declares the minimum version of Swift required to build this package.
import PackageDescription
let package = Package(
name: "rcheevos",
products: [
// Products define the executables and libraries a package produces, and make them visible to other packages.
.library(
name: "rcheevos",
targets: ["rcheevos"]),
],
dependencies: [
// Dependencies declare other packages that this package depends on.
// .package(url: /* package url */, from: "1.0.0"),
],
targets: [
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
// Targets can depend on other targets in this package, and on products in packages this package depends on.
.target(
name: "rcheevos",
dependencies: [],
path: ".",
exclude: ["src/rcheevos/rc_libretro.c"],
sources: ["include", "src/rcheevos", "src/rapi", "src/rhash"],
publicHeadersPath: "include",
cSettings: [
.define("RC_DISABLE_LUA")
]),
]
)
-49
View File
@@ -84,55 +84,6 @@ 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,26 +164,8 @@ 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,8 +42,6 @@ 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;
@@ -189,8 +187,6 @@ 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,8 +190,6 @@ 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;
/**
@@ -452,61 +450,6 @@ 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 |
\*****************************************************************************/
+1 -2
View File
@@ -49,8 +49,7 @@ enum {
RC_INSUFFICIENT_BUFFER = -36,
RC_INVALID_VARIABLE_NAME = -37,
RC_UNKNOWN_VARIABLE_NAME = -38,
RC_NOT_FOUND = -39,
RC_INVALID_VALUE = -40
RC_NOT_FOUND = -39
};
RC_EXPORT const char* RC_CCONV rc_error_str(int ret);
+30 -167
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 = rc_buffer_strncpy(&response->buffer, server_response->body, server_response->body_length);
response->error_message = server_response->body;
response->succeeded = 0;
return RC_NO_RESPONSE;
}
@@ -467,12 +467,17 @@ static int rc_json_get_array_entry_value(rc_json_field_t* field, rc_json_iterato
return 1;
}
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;
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;
if (*num_entries) {
*entries = (uint32_t*)rc_buffer_alloc(&response->buffer, *num_entries * sizeof(uint32_t));
if (!*entries)
return RC_OUT_OF_MEMORY;
@@ -480,8 +485,8 @@ static int rc_json_get_unum_array(uint32_t** entries, uint32_t* num_entries, rc_
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)) {
@@ -498,77 +503,6 @@ static int rc_json_get_unum_array(uint32_t** entries, uint32_t* num_entries, rc_
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)
@@ -689,12 +623,6 @@ 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;
@@ -730,11 +658,7 @@ 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;
if (src + 5 >= field->value_end) /* incomplete unicode character */
return 0;
ucs32_char = rc_json_decode_hex4(src + 1);
uint32_t ucs32_char = rc_json_decode_hex4(src + 1);
src += 5;
if (ucs32_char >= 0xD800 && ucs32_char < 0xE000) {
@@ -786,7 +710,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 || !text)
if (!ptr)
return 0;
if (*ptr == '"') {
@@ -1000,7 +924,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 && *field->value_start == '\"') {
if (*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 ||
@@ -1039,61 +963,6 @@ 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;
@@ -1135,25 +1004,23 @@ 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 && field->value_end > field->value_start) {
if (field->value_end) {
const char* str = field->value_end;
if (str[-1] == '"') {
/* ignore trailing quote */
field->value_end = --str;
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;
}
/* remove the extension */
while (str > field->value_start && str[-1] != '/') {
--str;
if (*str == '.') {
field->value_end = str;
break;
}
}
/* find the path separator */
while (str > field->value_start && str[-1] != '/')
--str;
field->value_start = str;
}
}
@@ -1429,15 +1296,12 @@ 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->media_host, "://"))
if (!strstr(host->host, "://"))
rc_url_builder_append(&builder, "http://", 7);
rc_url_builder_append(&builder, host->media_host, strlen(host->media_host));
}
@@ -1494,7 +1358,6 @@ 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,27 +49,21 @@ 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);
+6 -106
View File
@@ -181,6 +181,11 @@ 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));
@@ -197,111 +202,6 @@ 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) {
@@ -556,7 +456,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));
+30 -55
View File
@@ -56,6 +56,7 @@ 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[] = {
@@ -119,8 +120,9 @@ 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_timet(&entry->awarded, &response->response, &entry_fields[1], "DateAwarded"))
if (!rc_json_get_required_unum(&timet, &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)
@@ -183,6 +185,7 @@ 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];
@@ -286,8 +289,9 @@ 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_timet(&entry->submitted, &response->response, &entry_fields[4], "DateSubmitted"))
if (!rc_json_get_required_unum(&timet, &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)
@@ -321,8 +325,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", "systemgames");
rc_url_builder_append_unum_param(&builder, "s", api_params->console_id);
rc_url_builder_append_str_param(&builder, "r", "gameslist");
rc_url_builder_append_unum_param(&builder, "c", api_params->console_id);
request->post_data = rc_url_builder_finalize(&builder);
request->content_type = RC_CONTENT_TYPE_URLENCODED;
@@ -343,8 +347,9 @@ 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 array_field;
rc_json_field_t field;
int result;
char* end;
rc_json_field_t fields[] = {
RC_JSON_NEW_FIELD("Success"),
@@ -352,18 +357,6 @@ 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);
@@ -371,50 +364,32 @@ int rc_api_process_fetch_games_list_server_response(rc_api_fetch_games_list_resp
if (result != RC_OK)
return result;
if (!rc_json_get_required_array(&response->num_entries, &array_field, &response->response, &fields[2], "Response"))
if (!fields[2].value_start) {
/* call rc_json_get_required_object to generate the error message */
rc_json_get_required_object(NULL, 0, &response->response, &fields[2], "Response");
return RC_MISSING_VALUE;
}
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->num_entries = fields[2].array_size;
rc_buffer_reserve(&response->response.buffer, response->num_entries * (32 + sizeof(rc_api_game_list_entry_t)));
response->entries = (rc_api_game_list_entry_t*)rc_buffer_alloc(&response->response.buffer, response->num_entries * sizeof(rc_api_game_list_entry_t));
if (!response->entries)
return RC_OUT_OF_MEMORY;
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 = array_field.value_start;
iterator.end = array_field.value_end;
memset(&iterator, 0, sizeof(iterator));
iterator.json = fields[2].value_start;
iterator.end = fields[2].value_end;
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;
entry = response->entries;
while (rc_json_get_next_object_field(&iterator, &field)) {
entry->id = strtol(field.name, &end, 10);
/* 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;
field.name = "";
if (!rc_json_get_string(&entry->name, &response->response.buffer, &field, ""))
return RC_MISSING_VALUE;
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;
}
++entry;
}
return RC_OK;
@@ -509,7 +484,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 || !entry->image_url[0])
if (!entry->image_url[0])
entry->image_url = rc_api_build_avatar_url(&response->response.buffer, RC_IMAGE_TYPE_GAME, entry->image_name);
++entry;
+8 -5
View File
@@ -113,6 +113,7 @@ 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[] = {
@@ -154,11 +155,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 || !achievement->badge_url[0])
if (!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 || !achievement->badge_locked_url[0])
if (!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;
@@ -180,10 +181,12 @@ static int rc_api_process_fetch_game_data_achievements(rc_api_response_t* respon
}
}
if (!rc_json_get_required_timet(&achievement->created, response, &achievement_fields[8], "Created"))
if (!rc_json_get_required_unum(&timet, response, &achievement_fields[8], "Created"))
return RC_MISSING_VALUE;
if (!rc_json_get_required_timet(&achievement->updated, response, &achievement_fields[9], "Modified"))
achievement->created = (time_t)timet;
if (!rc_json_get_required_unum(&timet, 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;
@@ -317,7 +320,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 || !response->image_url[0])
if (!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.
+14 -12
View File
@@ -62,8 +62,7 @@ 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("AvatarUpdatedAt")
RC_JSON_NEW_FIELD("AvatarUrl")
};
memset(response, 0, sizeof(*response));
@@ -91,8 +90,6 @@ 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;
}
@@ -148,6 +145,7 @@ 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[] = {
@@ -183,8 +181,9 @@ 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_timet(&unlock->when, &response->response, &unlock_entry_fields[1], "When"))
if (!rc_json_get_required_unum(&timet, &response->response, &unlock_entry_fields[1], "When"))
return RC_MISSING_VALUE;
unlock->when = (time_t)timet;
++unlock;
}
@@ -203,14 +202,16 @@ 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_timet(&unlock->when, &response->response, &unlock_entry_fields[1], "When"))
if (!rc_json_get_required_unum(&timet, &response->response, &unlock_entry_fields[1], "When"))
return RC_MISSING_VALUE;
unlock->when = (time_t)timet;
++unlock;
}
}
rc_json_get_optional_timet(&response->server_now, &fields[4], "ServerNow", 0);
rc_json_get_optional_unum(&timet, &fields[4], "ServerNow", 0);
response->server_now = (time_t)timet;
return RC_OK;
}
@@ -306,11 +307,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")
};
@@ -323,7 +324,6 @@ 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[3], "Friends"))
if (!rc_json_get_required_array(&response->num_users, &array_field, &response->response, &fields[2], "Friends"))
return RC_MISSING_VALUE;
if (response->num_users) {
@@ -354,9 +354,11 @@ 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_timet(&user->recent_activity.when, &followed_user_entry_fields[4], "LastSeenTime", 0);
rc_json_get_optional_unum(&timet, &followed_user_entry_fields[4], "LastSeenTime", 0);
user->recent_activity.when = (time_t)timet;
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);
+12 -223
View File
@@ -14,15 +14,15 @@
#include <stdarg.h>
#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <profileapi.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__
vsnprintf_s(buffer, sizeof(buffer), _TRUNCATE, format, args);
vsprintf_s(buffer, sizeof(buffer), 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,22 +320,6 @@ 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;
@@ -719,7 +703,6 @@ 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;
@@ -951,11 +934,6 @@ 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;
@@ -2131,9 +2109,9 @@ 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;
@@ -3903,193 +3881,6 @@ 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)
@@ -4137,7 +3928,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 - 1, (int32_t)new_measured_value, RC_FORMAT_UNSIGNED_VALUE);
const int chars = rc_format_value(ptr, buffer_size, (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);
}
@@ -4815,11 +4606,6 @@ 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);
@@ -4847,6 +4633,7 @@ 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) */
@@ -6023,6 +5810,9 @@ 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);
}
@@ -6052,7 +5842,6 @@ 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,7 +43,6 @@ 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;
}
+1 -2
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 + 1);
rc_buffer_consume(buffer, dst, dst + len + 2);
return (char*)dst;
}
@@ -194,7 +194,6 @@ 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 4
#define RCHEEVOS_VERSION_MINOR 3
#define RCHEEVOS_VERSION_PATCH 0
#define RCHEEVOS_MAKE_VERSION(major, minor, patch) (major * 1000000 + minor * 1000 + patch)
-4
View File
@@ -213,10 +213,6 @@ 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;
+1 -4
View File
@@ -411,13 +411,10 @@ 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_OPERATOR_SUB_ACCUMULATOR;
parse->addsource_oper = rc_operand_is_memref(&parse->addsource_parent) ? RC_OPERATOR_SUB_ACCUMULATOR : RC_OPERATOR_SUB_PARENT;
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));
+2 -14
View File
@@ -818,14 +818,12 @@ 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
* the RAM is extended at loadtime if the game has a MEMSIZE flag in its PARAM.SFO */
/* https://github.com/uofw/upspd/wiki/Memory-map */
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, 3 };
static const rc_memory_regions_t rc_memory_regions_psp = { _rc_memory_regions_psp, 2 };
/* ===== Pokemon Mini ===== */
/* https://www.pokemon-mini.net/documentation/memory-map/ */
@@ -995,13 +993,6 @@ 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
@@ -1215,9 +1206,6 @@ 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,7 +742,6 @@ 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 */
+1 -22
View File
@@ -38,7 +38,6 @@ 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,
@@ -245,13 +244,6 @@ 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;
@@ -1219,22 +1211,9 @@ static int rc_validate_conflicting_conditions(const rc_condset_t* conditions, co
continue;
operand2 = rc_validate_get_comparison(compare_condition, &comparison2, &value2);
if (!operand2)
if (!operand2 || !rc_operands_are_equal(operand1, 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 */
+2 -8
View File
@@ -38,11 +38,6 @@ 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;
@@ -730,15 +725,14 @@ 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;
int32_t i;
int 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_LBOARD_STATE_TRIGGERED) {
if (lboard_state != RC_RUNTIME_EVENT_LBOARD_TRIGGERED) {
runtime_event.type = RC_RUNTIME_EVENT_LBOARD_TRIGGERED;
runtime_event.id = self->lboards[i].id;
event_handler(&runtime_event);
+2 -8
View File
@@ -120,12 +120,6 @@ 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';
@@ -182,7 +176,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_VALUE;
parse->offset = RC_INVALID_COMPARISON;
return;
}
@@ -435,7 +429,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));
memset(value, 0, sizeof(value->value));
value->value.size = RC_MEMSIZE_VARIABLE;
value->next = NULL;
+7 -32
View File
@@ -648,45 +648,20 @@ static void* cdreader_open_gdi_track(const char* path, uint32_t track, const rc_
while (isspace((unsigned char)*ptr))
++ptr;
ptr2 = file;
if (*ptr == '\"')
{
ptr2 = ++ptr; /* ignore leading quote */
++ptr;
while (*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 */
*ptr2++ = *ptr++;
++ptr;
}
else
{
ptr2 = ptr;
while (*ptr != ' ' && ptr < end)
++ptr;
num_read = ptr - ptr2;
while (*ptr != ' ')
*ptr2++ = *ptr++;
}
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';
*ptr2 = '\0';
if (track == current_track)
{
+2 -14
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; /* prevent attempt to free static "memory stream" string */
buffered_file_iterator.path = NULL;
rc_hash_destroy_iterator(&buffered_file_iterator);
return result;
}
@@ -543,11 +543,6 @@ 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);
@@ -867,11 +862,6 @@ 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:
@@ -981,7 +971,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.error_message = callbacks->error_message;
iterator->callbacks.verbose_message = callbacks->error_message;
if (callbacks->filereader.open)
memcpy(&iterator->callbacks.filereader, &callbacks->filereader, sizeof(callbacks->filereader));
@@ -1240,7 +1230,6 @@ 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 },
@@ -1256,7 +1245,6 @@ 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,7 +74,6 @@ 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);
+6
View File
@@ -12926,6 +12926,7 @@ SCUS-97584:
autoFlush: 2
preloadFrameData: 1
nativeScaling: 2 # Fixes post-processing.
halfPixelOffset: 4 # Aligns post bloom.
SCUS-97589:
name: "NBA '08 featuring The Life Vol.3"
region: "NTSC-U"
@@ -21921,6 +21922,7 @@ SLES-52940:
region: "PAL-M3"
gsHWFixes:
halfPixelOffset: 5 # Fixes double image and aligns glow effects.
nativeScaling: 1 # Fixes depth blur.
SLES-52941:
name: "Gungrave - Overdose"
region: "PAL-M3"
@@ -45824,6 +45826,7 @@ SLPM-65791:
region: "NTSC-J"
gsHWFixes:
halfPixelOffset: 5 # Fixes double image and aligns glow effects.
nativeScaling: 1 # Fixes depth blur.
SLPM-65793:
name: "SSX3 [EA BEST HITS]"
name-sort: "SSX3 [EA BEST HITS]"
@@ -69367,6 +69370,7 @@ SLUS-20969:
compat: 5
gsHWFixes:
halfPixelOffset: 5 # Fixes double image and aligns glow effects.
nativeScaling: 1 # Fixes depth blur.
SLUS-20970:
name: "Rumble Roses"
region: "NTSC-U"
@@ -75897,6 +75901,7 @@ SLUS-29132:
region: "NTSC-U"
gsHWFixes:
halfPixelOffset: 5 # Fixes double image and aligns glow effects.
nativeScaling: 1 # Fixes depth blur.
SLUS-29133:
name: "Namco Transmission Demo Disc Vol.2"
region: "NTSC-U"
@@ -76699,6 +76704,7 @@ TLES-52940:
region: "PAL-E"
gsHWFixes:
halfPixelOffset: 5 # Fixes double image and aligns glow effects.
nativeScaling: 1 # Fixes depth blur.
TLES-53544:
name: "Pro Evolution Soccer 5 Beta Trial Code"
region: "PAL-E"
+2 -7
View File
@@ -791,19 +791,14 @@ 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(GetDepthStencilUsage(), w, h, 1, GSTexture::Format::DepthStencil, clear, prefer_reuse);
return FetchSurface(GSTexture::DepthStencil, w, h, 1, GSTexture::Format::DepthStencil, clear, prefer_reuse);
}
GSTexture* GSDevice::CreateDepthStencil(const GSVector2i& size, bool clear, bool prefer_reuse)
{
return FetchSurface(GetDepthStencilUsage(), size.x, size.y, 1, GSTexture::Format::DepthStencil, clear, prefer_reuse);
return FetchSurface(GSTexture::DepthStencil, 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,8 +1637,6 @@ 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);
+8 -5
View File
@@ -10709,8 +10709,10 @@ int GSRendererHW::IsScalingDraw(GSTextureCache::Source* src, bool no_gaps)
IsMipMapDraw() || GSLocalMemory::m_psm[m_cached_ctx.TEX0.PSM].trbpp <= 8)
return 0;
const bool is_smaller = draw_size.x <= (tex_size.x * 0.75f) && draw_size.y <= (tex_size.y * 0.75f);
const bool is_smaller_and_writing_back = draw_size.x < tex_size.x && draw_size.y < tex_size.y && next_ctx.FRAME.Block() == m_cached_ctx.TEX0.TBP0 && next_ctx.TEX0.TBP0 == m_cached_ctx.FRAME.Block();
// Should usually be 2x but some games like Monster House goes from 512x448 -> 128x128
const bool is_downscale = m_cached_ctx.TEX0.TBW >= m_cached_ctx.FRAME.FBW && draw_size.x <= (tex_size.x * 0.75f) && draw_size.y <= (tex_size.y * 0.75f);
const bool is_downscale = m_cached_ctx.TEX0.TBW >= m_cached_ctx.FRAME.FBW && (is_smaller || is_smaller_and_writing_back);
// Check we're getting most of the texture and not just stenciling a part of it.
// Only allow non-bilineared downscales if it's most of the target (misdetections of shadows in Naruto, Transformers etc), otherwise it's fine.
const GSVector4i src_valid = src->m_from_target ? src->m_from_target->m_valid : src->m_valid_rect;
@@ -10734,7 +10736,8 @@ int GSRendererHW::IsScalingDraw(GSTextureCache::Source* src, bool no_gaps)
}
// Last ditched check if it's doing a lot of small draws exactly the same which could be recursive lighting bloom.
if (m_vt.m_primclass == GS_SPRITE_CLASS && m_index->tail > 2 && !no_gaps_or_single_sprite && m_context->TEX1.MMAG == 1 && !m_context->ALPHA.IsOpaque())
if (m_vt.m_primclass == GS_SPRITE_CLASS && m_index->tail > 2 && !no_gaps_or_single_sprite && m_context->TEX1.MMAG == 1 && is_target_src && (m_cached_ctx.TEST.ZTST == ZTST_ALWAYS || m_cached_ctx.TEST.ZTE == 0) &&
!m_context->ALPHA.IsOpaque() && GSLocalMemory::m_psm[m_cached_ctx.FRAME.PSM].bpp == GSLocalMemory::m_psm[m_cached_ctx.TEX0.PSM].bpp)
{
GSVertex* v = &m_vertex->buff[0];
float tw = 1 << src->m_TEX0.TW;
@@ -10745,7 +10748,7 @@ int GSRendererHW::IsScalingDraw(GSTextureCache::Source* src, bool no_gaps)
const int first_x = (v[1].XYZ.X - v[0].XYZ.X) >> 4;
const int first_y = (v[1].XYZ.Y - v[0].XYZ.Y) >> 4;
if (first_x > first_u && first_y > first_v && !no_resize && std::abs(draw_size.x - first_x) <= 4 && std::abs(draw_size.y - first_y) <= 4)
if (first_x >= first_u && first_y >= first_v && !no_resize && ((draw_size.x != first_x && draw_size.y != first_y) || (tex_size.x != first_u && tex_size.y != first_v)) && std::abs(draw_size.x - first_x) <= 4 && std::abs(draw_size.y - first_y) <= 4)
{
for (u32 i = 2; i < m_index->tail; i += 2)
{
@@ -10754,14 +10757,14 @@ int GSRendererHW::IsScalingDraw(GSTextureCache::Source* src, bool no_gaps)
const int next_x = (v[i + 1].XYZ.X - v[i].XYZ.X) >> 4;
const int next_y = (v[i + 1].XYZ.Y - v[i].XYZ.Y) >> 4;
if (std::abs(draw_size.x - next_x) > 4 || std::abs(draw_size.y - next_y) > 4)
if (std::abs(draw_size.x - next_x) > 4 || std::abs(draw_size.y - next_y) > 4 || (draw_size.x == next_x && tex_size.x == next_u) || (draw_size.y == next_y && tex_size.y == next_v))
break;
if (next_u != first_u || next_v != first_v || next_x != first_x || next_y != first_y)
break;
if (i + 2 >= m_index->tail)
return 2;
return (is_target_src && src->m_from_target->m_downscaled) ? -1 : 2;
}
}
}
+2 -1
View File
@@ -7215,7 +7215,8 @@ 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->GetDepthStencilUsage();
GSTexture::Usage usage = type == RenderTarget ? GSTexture::FeedbackTarget :
(g_gs_device->Features().depth_feedback ? GSTexture::FeedbackDepth : GSTexture::DepthStencil);
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)